Dans le cadre du développement pour le Web, la norme WebRTC fournit des API permettant d'accéder aux caméras et micros connectés à l'ordinateur ou au smartphone. Ces appareils sont communément appelés "appareils multimédias" et sont accessibles via JavaScript via l'objet navigator.mediaDevices
, qui implémente l'interface MediaDevices
. À partir de cet objet, nous pouvons énumérer tous les appareils connectés, écouter les modifications des appareils (lorsqu'ils sont connectés ou déconnectés) et ouvrir un appareil pour récupérer un flux multimédia (voir ci-dessous).
La méthode la plus courante consiste à utiliser la fonction getUserMedia()
, qui renvoie une promesse qui se résout en MediaStream
pour les appareils multimédias correspondants. Cette fonction accepte un seul objet MediaStreamConstraints
qui spécifie nos exigences. Par exemple, pour ouvrir le micro et la caméra par défaut, procédez comme suit :
Utiliser des promesses
const constraints = {
'video': true,
'audio': true
}
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
console.log('Got MediaStream:', stream);
})
.catch(error => {
console.error('Error accessing media devices.', error);
});
Utiliser async/await
const openMediaDevices = async (constraints) => {
return await navigator.mediaDevices.getUserMedia(constraints);
}
try {
const stream = openMediaDevices({'video':true,'audio':true});
console.log('Got MediaStream:', stream);
} catch(error) {
console.error('Error accessing media devices.', error);
}
L'appel de getUserMedia()
déclenche une demande d'autorisation. Si l'utilisateur accepte l'autorisation, la promesse est résolue avec un MediaStream
contenant une vidéo et une piste audio. Si l'autorisation est refusée, une erreur PermissionDeniedError
est générée. Si aucun appareil correspondant n'est connecté, une erreur NotFoundError
est générée.
La documentation de référence complète de l'API pour l'interface MediaDevices
est disponible dans la documentation Web de MDN.
Interroger des appareils multimédias
Dans une application plus complexe, il est très probable que nous vérifions l'ensemble des caméras et des micros connectés, et que nous transmettions les retours appropriés à l'utilisateur. Pour ce faire, appelez la fonction enumerateDevices()
. Cette commande renvoie une promesse qui se résout en un tableau de MediaDevicesInfo
décrivant chaque appareil multimédia connu. Nous pouvons l'utiliser pour présenter une interface utilisateur à l'utilisateur pour lui permettre de choisir celle qu'il préfère. Chaque MediaDevicesInfo
contient une propriété nommée kind
avec la valeur audioinput
, audiooutput
ou videoinput
, indiquant le type d'appareil multimédia.
Utiliser des promesses
function getConnectedDevices(type, callback) {
navigator.mediaDevices.enumerateDevices()
.then(devices => {
const filtered = devices.filter(device => device.kind === type);
callback(filtered);
});
}
getConnectedDevices('videoinput', cameras => console.log('Cameras found', cameras));
Utiliser async/await
async function getConnectedDevices(type) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === type)
}
const videoCameras = getConnectedDevices('videoinput');
console.log('Cameras found:', videoCameras);
Changements liés à la détection des appareils
La plupart des ordinateurs permettent de brancher divers appareils pendant l’exécution. Il peut s'agir d'une webcam connectée en USB, d'un casque Bluetooth ou d'un ensemble de haut-parleurs externes. Pour une compatibilité adéquate, une application Web doit écouter les modifications des périphériques multimédias. Pour ce faire, ajoutez un écouteur à navigator.mediaDevices
pour l'événement devicechange
.
// Updates the select element with the provided set of cameras
function updateCameraList(cameras) {
const listElement = document.querySelector('select#availableCameras');
listElement.innerHTML = '';
cameras.map(camera => {
const cameraOption = document.createElement('option');
cameraOption.label = camera.label;
cameraOption.value = camera.deviceId;
}).forEach(cameraOption => listElement.add(cameraOption));
}
// Fetch an array of devices of a certain type
async function getConnectedDevices(type) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === type)
}
// Get the initial set of cameras connected
const videoCameras = getConnectedDevices('videoinput');
updateCameraList(videoCameras);
// Listen for changes to media devices and update the list accordingly
navigator.mediaDevices.addEventListener('devicechange', event => {
const newCameraList = getConnectedDevices('video');
updateCameraList(newCameraList);
});
Contraintes liées aux médias
L'objet de contraintes, qui doit implémenter l'interface MediaStreamConstraints
, que nous transmettons en tant que paramètre à getUserMedia()
, nous permet d'ouvrir un périphérique multimédia répondant à une certaine exigence. Cette exigence peut être définie de manière très vague (audio et/ou vidéo) ou très spécifique (résolution minimale de la caméra ou ID exact d'appareil). Il est recommandé que les applications qui utilisent l'API getUserMedia()
vérifient d'abord les appareils existants, puis spécifient une contrainte qui correspond à l'appareil exact à l'aide de la contrainte deviceId
.
Les appareils seront également, si possible, configurés en fonction des contraintes. Nous pouvons activer l'annulation de l'écho sur les micros, ou définir une largeur et une hauteur minimales ou spécifiques pour la vidéo depuis la caméra.
async function getConnectedDevices(type) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === type)
}
// Open camera with at least minWidth and minHeight capabilities
async function openCamera(cameraId, minWidth, minHeight) {
const constraints = {
'audio': {'echoCancellation': true},
'video': {
'deviceId': cameraId,
'width': {'min': minWidth},
'height': {'min': minHeight}
}
}
return await navigator.mediaDevices.getUserMedia(constraints);
}
const cameras = getConnectedDevices('videoinput');
if (cameras && cameras.length > 0) {
// Open first available video camera with a resolution of 1280x720 pixels
const stream = openCamera(cameras[0].deviceId, 1280, 720);
}
La documentation complète de l'interface MediaStreamConstraints
est disponible dans la documentation Web de MDN.
Lecture en local
Une fois qu'un appareil multimédia a été ouvert et qu'un MediaStream
est disponible, nous pouvons l'attribuer à un élément vidéo ou audio pour lire le flux localement.
async function playVideoFromCamera() {
try {
const constraints = {'video': true, 'audio': true};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const videoElement = document.querySelector('video#localVideo');
videoElement.srcObject = stream;
} catch(error) {
console.error('Error opening video camera.', error);
}
}
Le code HTML nécessaire pour un élément vidéo classique utilisé avec getUserMedia()
comporte généralement les attributs autoplay
et playsinline
. L'attribut autoplay
entraîne la lecture automatique des nouveaux flux attribués à l'élément.
L'attribut playsinline
permet de lire la vidéo de manière intégrée, et non pas uniquement en plein écran, sur certains navigateurs mobiles. Il est également recommandé d'utiliser controls="false"
pour les diffusions en direct, sauf si l'utilisateur peut les mettre en veille.
<html>
<head><title>Local video playback</title></head>
<body>
<video id="localVideo" autoplay playsinline controls="false"/>
</body>
</html>