A web component for selecting media devices. It aims to deal with the complexities of handling audio/visual hardware in the browser, so you don't have to :)
- Convenience methods for rendering and updating chosen devices
- Automatically updates when devices are added or removed
- Automatically stores and recalls user's preferred device choices
Include siphon-select.js
in your project, then render a select
element, adding the required attributes:
is="siphon-select"
and- a
data-type
attribute (one ofaudioinput
,audiooutput
, orvideoinput
)
<select is="siphon-select" data-type="audioinput"></select>
Note: siphon-select
is a customized built-in element which is not yet supported in some browsers. For broad browser support, include a custom elements polyfill.
Dispatched after the component has connected, initially rendered, and any recalled preferences have been set. It's important to wait for this event if you're retrieving a value shortly after the element is rendered.
A Promise
that resolves to true
or false
depending on whether permission has been granted i.e. if the labels for a given media type have labels.
Calling render
will update the options with labels, as well as adding the data-permission-granted
(if applicable). render
is typically called after the initial call to navigator.mediaDevices.getUserMedia(…)
.
Returns a Promise
that resolves to undefined
.
When a stream has been requested successfully, pass it in to setValueFromStream
and siphon-select
will determine which device has been chosen, and select the option accordingly. This is typically called after the initial call to navigator.mediaDevices.getUserMedia(…)
. Be sure that siphon-select
has rendered, for example:
let stream = await navigator.mediaDevices.getUserMedia({ audio: true })
const select = document.querySelector('[is="siphon-select"]')
// Re-render select to display labels
await select.render()
// Select the chosen device
select.setValueFromStream(stream)
The data-permission-granted
is added if siphon-select
can determine whether permission has been granted (see above).
Suggested usage pattern for audio input devices as demonstrated in index.html
.
<select is="siphon-select" data-type="audioinput"></select>
<button id="enable">Enable</button>
The data-permission-granted
provides a nice way for declarative styles: initially hide the select
dropdown; once permission is granted, display it and hide the enable button.
[is="siphon-select"] {
display: none;
}
[is="siphon-select"][data-permission-granted] {
display: block;
}
[is="siphon-select"][data-permission-granted] ~ #enable {
display: none;
}
Use a click handler to request access to the currently chosen audio device (by default, this will be the first option in the dropdown). Once requested successfully, re-render the select
. As permission is now granted, this will display the select
with device labels, and hide the enable button.
Some browsers, like Firefox, allow the user to select a device when asking for permission; others, like Chrome, let the user specify a default device in their browser settings. To ensure the select
is accurate with the newly chosen device, we call setValueFromStream
.
let stream
const select = document.querySelector('[is=siphon-select]')
const enable = document.getElementById('enable')
enable.onclick = async function () {
stream = await navigator.mediaDevices.getUserMedia({
audio: { deviceId: select.value }
})
await select.render()
select.setValueFromStream(stream)
}
Re-set the stream when the select
changes. We'll create a reusable setStream
function for this:
let stream
const select = document.querySelector('[is=siphon-select]')
const enable = document.getElementById('enable')
enable.onclick = async function () {
await setStream()
await select.render()
select.setValueFromStream(stream)
}
select.onchange = function () {
setStream()
}
async function setStream () {
// End an existing stream
if (stream) stream.getTracks().forEach(track => track.stop())
stream = await navigator.mediaDevices.getUserMedia({
audio: { deviceId: select.value }
})
}
If the user has previously granted permission, we can request access immediately. We'll need to be aware that device preferences are recalled when the select
element is initially rendered. To ensure the correct device is requested, we'll need to wait for the select
to be ready. So putting this all together:
let stream
const select = document.querySelector('[is=siphon-select]')
const enable = document.getElementById('enable')
// If permission is granted, access the stream straightaway
select.addEventListener('ready', async function () {
if (await select.permissionGranted) setStream()
})
// Request access to stream, re-render once access is granted (and labels are available), then set the value
enable.onclick = async function () {
await setStream()
await select.render()
select.setValueFromStream(stream)
}
select.onchange = function () {
setStream()
}
async function setStream () {
// End an existing stream
if (stream) stream.getTracks().forEach(track => track.stop())
stream = await navigator.mediaDevices.getUserMedia({
audio: { deviceId: select.value }
})
}
Copyright © 2021+ Dom Christie and released under the MIT license.