Premiers pas avec les périphériques multimédias

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>