Pierwsze kroki z urządzeniami multimedialnymi

Tworząc aplikacje do internetu, standard WebRTC udostępnia interfejsy API umożliwiające dostęp do kamer i mikrofonów podłączonych do komputera lub smartfona. Urządzenia te nazywane są często urządzeniami multimedialnymi i można do nich uzyskać dostęp za pomocą JavaScriptu za pomocą obiektu navigator.mediaDevices, który zawiera interfejs MediaDevices. Na podstawie tego obiektu możemy wyliczyć wszystkie podłączone urządzenia, nasłuchiwać zmian w urządzeniach (gdy jest ono połączone lub odłączone) oraz otworzyć urządzenie, aby pobrać strumień multimediów (patrz poniżej).

Najczęściej jest to używane za pomocą funkcji getUserMedia(), która zwraca obietnicę, która w przypadku pasujących urządzeń multimedialnych osiągnie wartość MediaStream. Ta funkcja pobiera pojedynczy obiekt MediaStreamConstraints, który określa nasze wymagania. Na przykład, żeby otworzyć domyślny mikrofon i kamerę, wykonamy te czynności.

Korzystanie z obietnic

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);
    });

Korzystanie z trybu asynchronicznego/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);
}

Wywołanie metody getUserMedia() wywoła prośbę o uprawnienia. Jeśli użytkownik zaakceptuje uprawnienie, obietnica zostanie zrealizowana dzięki elementowi MediaStream zawierającym 1 film i 1 ścieżkę audio. W przypadku odmowy uprawnień wysyłany jest PermissionDeniedError. Jeśli nie ma żadnych pasujących urządzeń, wysyłany jest komunikat NotFoundError.

Pełne informacje o interfejsie API dla interfejsu MediaDevices znajdziesz w dokumentacji internetowej MDN.

Wysyłanie zapytań o urządzenia multimedialne

W bardziej złożonych aplikacjach najprawdopodobniej będziemy chcieli sprawdzić wszystkie podłączone kamery i mikrofony oraz przekazać użytkownikowi odpowiednie informacje zwrotne. Możesz to zrobić, wywołując funkcję enumerateDevices(). Zwróci to obietnicę przekształcającą się w tablicę MediaDevicesInfo opisującego każde znane urządzenie multimedialne. Dzięki temu możemy przedstawić użytkownikowi interfejs użytkownika, który pozwoli mu wybrać ten, który najbardziej mu się podoba. Każdy MediaDevicesInfo zawiera właściwość o nazwie kind z wartością audioinput, audiooutput lub videoinput, która wskazuje typ urządzenia multimedialnego.

Korzystanie z obietnic

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));

Korzystanie z trybu asynchronicznego/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);

Nasłuchiwanie zmian na urządzeniach

Większość komputerów obsługuje podłączanie różnych urządzeń w czasie działania. Może to być kamera internetowa podłączona przez USB, zestaw słuchawkowy Bluetooth lub głośniki zewnętrzne. Aby ta funkcja była obsługiwana, aplikacja internetowa powinna nasłuchiwać zmian na urządzeniach multimedialnych. Aby to zrobić, dodaj do navigator.mediaDevices odbiornik zdarzenia 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);
});

Ograniczenia dotyczące multimediów

Obiekt ograniczeń, który musi implementować interfejs MediaStreamConstraints, przekazywany jako parametr do getUserMedia() umożliwia otwarcie urządzenia multimedialnego spełniającego określone wymagania. To wymaganie może być bardzo luźno zdefiniowane (audio lub wideo) lub bardzo szczegółowe (minimalna rozdzielczość kamery lub dokładny identyfikator urządzenia). Zalecamy, aby aplikacje korzystające z interfejsu API getUserMedia() najpierw sprawdziły istniejące urządzenia, a następnie określiły ograniczenie dokładnie dopasowane do urządzenia za pomocą ograniczenia deviceId. W miarę możliwości urządzenia zostaną też skonfigurowane zgodnie z ograniczeniami. Możemy włączyć usuwanie echa z mikrofonów lub ustawić określoną lub minimalną szerokość i wysokość obrazu z kamery.

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);
}

Pełną dokumentację interfejsu MediaStreamConstraints znajdziesz w dokumentacji MDN.

Odtwarzanie lokalne

Gdy otworzymy urządzenie multimedialne i będziemy mieli do dyspozycji MediaStream urządzenie, możemy je przypisać do elementu wideo lub audio, aby odtwarzać strumień lokalnie.

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);
    }
}

Kod HTML potrzebny w przypadku typowego elementu wideo używanego z elementem getUserMedia() zwykle ma atrybuty autoplay i playsinline. Atrybut autoplay spowoduje automatyczne odtwarzanie nowych strumieni przypisanych do elementu. Atrybut playsinline umożliwia odtwarzanie filmu w treści strony, a nie tylko na pełnym ekranie w niektórych przeglądarkach mobilnych. Zalecamy też stosowanie controls="false" w przypadku transmisji na żywo, chyba że użytkownik powinien mieć możliwość ich wstrzymania.

<html>
<head><title>Local video playback</title></head>
<body>
    <video id="localVideo" autoplay playsinline controls="false"/>
</body>
</html>