When you work with WebRTC, you will need to do a lot of stuff that offers the most basic functionality to the users to interact with your app. One of those features is the possibility to select a different video or audio stream source manually. For example, viewing the demo on this website, on the Samsung Galaxy S10+, you will see the following options available (The Galaxy S10 has three cameras on the back: a main 12-megapixel with an aperture that shifts between f/1.5 and f/2.4 depending on the light. An ultra-wide 16-megapixel unit, and a telephoto 12-megapixel for zooming):
Although the example looks that it may require complicated logic to make it work, it really isn't. You need to understand the basics of the interaction with the MediaStream API in the browser and you will be up and running in a few minutes. This article will explain how to easily list all the media devices on your device to use their stream.
Requirements
The browser where you will work needs to support the NavigatorUserMedia API. You can verify this with a simple conditional in your code:
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
console.log("enumerateDevices is not supported.");
}
If it's supported, you may continue.
1. Listing Media Devices Without User's Permission
Note: this approach only works in Chrome and Firefox. It's meant to work only to verify if the user has a microphone or a camera connected as it won't list the details about the device like the label, deviceId, and so on.
The first thing that you need to learn is how to list all the available video and audio input devices of the computer. In this example, we will use a very simple approach of listing all of them in 2 selects, one of them will contain the list of the audioinput
devices (microphones) and the one will list all the videoinput
devices (cameras). The markup will look like this:
<label>Select Video Source (Camera)</label>
<select id="video-source"></select>
<label>Select Audio Source (Microphone)</label>
<select id="audio-source"></select>
And the JavaScript that will be executed to list the devices will be the following one:
navigator.mediaDevices.enumerateDevices().then((devices) => {
let videoSourcesSelect = document.getElementById("video-source");
let audioSourcesSelect = document.getElementById("audio-source");
// Iterate over all the list of devices (InputDeviceInfo and MediaDeviceInfo)
devices.forEach((device) => {
let option = new Option();
option.value = device.deviceId;
// According to the type of media device
switch(device.kind){
// Append device to list of Cameras
case "videoinput":
option.text = device.label || `Camera ${videoSourcesSelect.length + 1}`;
videoSourcesSelect.appendChild(option);
break;
// Append device to list of Microphone
case "audioinput":
option.text = device.label || `Microphone ${videoSourcesSelect.length + 1}`;
audioSourcesSelect.appendChild(option);
break;
}
console.log(device);
});
}).catch(function (e) {
console.log(e.name + ": " + e.message);
});
Using the Promises API obtained from the navigator.mediaDevices.enumerateDevices
, we will obtain an array that contains the InputDeviceInfo and MediaDeviceInfo instances of each of them. We will iterate over them and will append a new option element to the defined selects, where each of them will have a simple label if it's available as text or the number of the device and as value the deviceId property, which contains the ID of the device that you can use to switch from source later.
The problem with this approach is that you won't get real information about the devices and even if you have multiple devices connected, you will always obtain a single item for every category (audioinput
, videoinput
, audiooutput
):
2. Listing Media Devices With User's Permission
If you want detailed information about all the available devices on the user's computer/mobile, you will need to ask for the user's permissions:
This can be requested through the method MediaDevices.getUserMedia()
that will prompt for user's permission to use a media input which produces a MediaStream with tracks containing the requested types of media. In this example, we are going to create a helper that allows us to execute everything with an understandable workflow. As markup for this example, we will have 2 selects and a video element that will play the stream:
<label>Select Video Source (Camera)</label>
<select id="video-source"></select>
<label>Select Audio Source (Microphone)</label>
<select id="audio-source"></select>
<label>Live Preview:</label>
<!-- Important to set autoplay to true, otherwise the video won't play anything at the beginning -->
<video autoplay="true" id="player" controls></video>
The helper that we will use to request permissions will be the following one:
let videoSourcesSelect = document.getElementById("video-source");
let audioSourcesSelect = document.getElementById("audio-source");
let videoPlayer = document.getElementById("player");
// Create Helper to ask for permission and list devices
let MediaStreamHelper = {
// Property of the object to store the current stream
_stream: null,
// This method will return the promise to list the real devices
getDevices: function() {
return navigator.mediaDevices.enumerateDevices();
},
// Request user permissions to access the camera and video
requestStream: function() {
if (this._stream) {
this._stream.getTracks().forEach(track => {
track.stop();
});
}
const audioSource = audioSourcesSelect.value;
const videoSource = videoSourcesSelect.value;
const constraints = {
audio: {
deviceId: audioSource ? {exact: audioSource} : undefined
},
video: {
deviceId: videoSource ? {exact: videoSource} : undefined
}
};
return navigator.mediaDevices.getUserMedia(constraints);
}
};
With this helper, you should be able to request permission from the user with the method MediaStreamHelper.requestStream
like this:
// Request streams (audio and video), ask for permission and display streams in the video element
MediaStreamHelper.requestStream().then(function(stream){
// Store Current Stream
MediaStreamHelper._stream = stream;
// Select the Current Streams in the list of devices
audioSourcesSelect.selectedIndex = [...audioSourcesSelect.options].findIndex(option => option.text === stream.getAudioTracks()[0].label);
videoSourcesSelect.selectedIndex = [...videoSourcesSelect.options].findIndex(option => option.text === stream.getVideoTracks()[0].label);
videoPlayer.srcObject = stream;
// You can now list the devices using the Helper here
// MediaStreamHelper.getDevices().then(...);
}).catch(function(err){
console.error(err);
});
In this example, we will display the Audio and Video stream on a Video element of the document (player). After obtaining the stream, you will have now access to the list of devices with the MediaStreamHelper.getDevices
promise:
// Request streams (audio and video), ask for permission and display streams in the video element
MediaStreamHelper.requestStream().then(function(stream){
console.log(stream);
// Store Current Stream
MediaStreamHelper._stream = stream;
// Select the Current Streams in the list of devices
audioSourcesSelect.selectedIndex = [...audioSourcesSelect.options].findIndex(option => option.text === stream.getAudioTracks()[0].label);
videoSourcesSelect.selectedIndex = [...videoSourcesSelect.options].findIndex(option => option.text === stream.getVideoTracks()[0].label);
// Play the current stream in the Video element
videoPlayer.srcObject = stream;
// You can now list the devices using the Helper
MediaStreamHelper.getDevices().then((devices) => {
// Iterate over all the list of devices (InputDeviceInfo and MediaDeviceInfo)
devices.forEach((device) => {
let option = new Option();
option.value = device.deviceId;
// According to the type of media device
switch(device.kind){
// Append device to list of Cameras
case "videoinput":
option.text = device.label || `Camera ${videoSourcesSelect.length + 1}`;
videoSourcesSelect.appendChild(option);
break;
// Append device to list of Microphone
case "audioinput":
option.text = device.label || `Microphone ${videoSourcesSelect.length + 1}`;
audioSourcesSelect.appendChild(option);
break;
}
console.log(device);
});
}).catch(function (e) {
console.log(e.name + ": " + e.message);
});
}).catch(function(err){
console.error(err);
});
This will list the devices to which we didn't have access previously:
3. Handling Audio and Video source change
Till this point, the code allows the user to see the list of available devices, now it's necessary to handle the stream change when the user selects a new video or audio source in the selects. This can be done easily by attaching an event listener to selects. When the change is triggered, the helper should request the stream once again using the currently selected sources and that's it! Be sure to update the stream of the video element as well:
let videoSourcesSelect = document.getElementById("video-source");
let audioSourcesSelect = document.getElementById("audio-source");
let videoPlayer = document.getElementById("player");
videoSourcesSelect.onchange = function(){
MediaStreamHelper.requestStream().then(function(stream){
MediaStreamHelper._stream = stream;
videoPlayer.srcObject = stream;
});
};
audioSourcesSelect.onchange = function(){
MediaStreamHelper.requestStream().then(function(stream){
MediaStreamHelper._stream = stream;
videoPlayer.srcObject = stream;
});
};
Happy coding ❤️!