v136 · media
Sink Permission Flow
Enumerate audio output devices, understand why some appear as empty strings until permission is granted, and walk the full flow: getUserMedia → enumerate → setPreferredSinkId. Shows the difference between sinkId: "" (default) and a real device ID, and what happens when permission is absent.
checking MediaDevices…
checking setPreferredSinkId…
probeEnum">checking enumerateDevices…
setPreferredSinkId requires the user to have granted microphone permission via getUserMedia before enumerateDevices returns real device labels and IDs for output devices. Without permission, deviceId is an empty string and label is blank — the API exists but you can't pick a device.
Permission & enumeration flow
1
—
Check MediaDevices API availability
navigator.mediaDevices and enumerateDevices must exist
2
—
Enumerate devices (no permission yet)
audiooutput devices have empty deviceId and label without permission
3
—
Request microphone permission (getUserMedia)
This unlocks device label/ID enumeration for output devices too
4
—
Re-enumerate with permission
audiooutput entries now have real deviceId and label
5
—
Call setPreferredSinkId with a real device ID
Sets the origin's preferred audio output for future audio elements
Audio output devices found
Run the flow to enumerate devices.
sinkId semantics
// sinkId values and what they mean:
//
// "" (empty string) — default device; no explicit selection
// Also returned by enumerateDevices when no permission
//
// "default" — explicitly the system default output
// (may differ from "" in some OS configurations)
//
// "communications" — communications device (headset, VoIP preferred)
//
// "<device-id>" — specific device; only visible after getUserMedia grants
// access (microphone permission unlocks output IDs too)
//
// Chrome 136: setPreferredSinkId(deviceId) persists the choice for the origin.
// Future HTMLAudioElement instances get this sink without calling setSinkId().
//
// Difference from setSinkId():
// setSinkId() — per-element, per-session
// setPreferredSinkId() — origin-level, persists across navigation