demo · v133
Cross-origin iframe routing — without postMessage
The whole reason setPreferredSinkId() shipped: a top-level page can route audio for every cross-origin iframe inside it without negotiating per-frame. Pick a device on the parent — the embedded player's output follows.
sandbox="allow-scripts" and deliberately omits
allow-same-origin. That gives the child frame an opaque origin, so the parent cannot
reach into its DOM, but the top-level setPreferredSinkId() preference still applies to
audio renderers created in the frame tree.
embedded player (sandboxed opaque origin)
before this shipped
Each cross-origin iframe was an island. To honour a user-picked output device, the parent had to postMessage the device ID into every frame, and every frame had to listen for it and call HTMLMediaElement.setSinkId on each of its audio elements. SDKs (call apps, podcast players, ad widgets) had to publish a documented message protocol for this. setPreferredSinkId on the top frame replaces all of that — the chosen device is the new default sink for every renderer in the frame tree, including cross-origin children, until the page picks another.
// Top-level page:
await navigator.mediaDevices.setPreferredSinkId(deviceId);
// Cross-origin iframe inside it: every <audio> / Web Audio context
// it creates now plays through the chosen device by default.
// No postMessage handshake. No per-frame setSinkId.