Przechwytywanie dźwięku i obrazu w HTML5

Wstęp

Nagrywanie dźwięku i wideo od dawna jest „Holy Grailem” w dziedzinie tworzenia stron internetowych. Przez wiele lat do tego musieliśmy używać wtyczek przeglądarek (Flash lub Silverlight). Daj spokój!

HTML5 przychodzi z pomocą. Może to wydawać się niepozorne, ale wzrost popularności HTML5 spowodował gwałtowny wzrost dostępu do sprzętu sprzętowego. Doskonałymi przykładami są geolokalizacja (GPS), Orientation API (akcelerometr), WebGL (GPU) i Web Audio API (sprzęt audio). Funkcje te mają niesamowitą wydajność i udostępniają interfejsy API JavaScript wysokiego poziomu, które są współdziałane z podstawowymi możliwościami sprzętowymi systemu.

W tym samouczku przedstawiamy nowy interfejs API GetUserMedia, który umożliwia aplikacjom internetowym dostęp do kamery i mikrofonu użytkownika.

Droga do getUserMedia()

Jeśli nie znasz jego historii, to ciekawe, jak udało nam się uzyskać interfejs getUserMedia() API.

W ciągu ostatnich kilku lat zmieniło się kilka wariantów interfejsów „Media Capture API”. Wiele osób zauważyło, że trzeba mieć dostęp do natywnych urządzeń w internecie, ale dzięki temu wszyscy i ich mama stworzyli nową specyfikację. Sprawy były tak skomplikowane, że organizacja W3C w końcu postanowiła utworzyć grupę roboczą. ich jedynym celem? Poczuj szaleństwo! Zadaniem grupy roboczej ds. zasad dotyczących interfejsów API urządzeń (DAP) jest skonsolidowanie i ujednolicenie mnóstwa ofert pakietowych.

Postaram się podsumować, co wydarzyło się w 2011 roku...

Runda 1. Rejestrowanie multimediów HTML

HTML Media Capture to pierwsze rozwiązanie DAP w dziedzinie standaryzacji przechwytywania multimediów w internecie. Działanie tej funkcji polega na przeciążeniu interfejsu <input type="file"> i dodaniu nowych wartości parametru accept.

Jeśli chcesz umożliwić użytkownikom robienie sobie zdjęć kamerą internetową, możesz to zrobić w capture=camera:

<input type="file" accept="image/*;capture=camera">

Nagrywanie filmu lub dźwięku przebiega podobnie:

<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">

Całkiem nieźle, prawda? Szczególnie podoba mi się to, że wykorzystuje dane wejściowe pliku. Z punktu widzenia semantyki ma to bardzo sens. Gdy ten konkretny „interfejs API” nie spełnia wymagań, to możliwość wykonywania efektów w czasie rzeczywistym (np. renderowania danych z kamery internetowej na żywo w obiekcie <canvas> i stosowania filtrów WebGL). Przechwytywanie multimediów w formacie HTML umożliwia jedynie nagranie pliku multimedialnego lub zrobienie zrzutu w określonym czasie.

Pomoc:

  • Przeglądarka na Androida 3.0 – jedna z pierwszych implementacji. Obejrzyj ten film, aby zobaczyć, jak to działa.
  • Chrome na Androida (0.16)
  • Firefox Mobile 10.0
  • Safari i Chrome na iOS6 (częściowa obsługa)

Runda 2. Urządzenie

Wielu uważało, że przechwytywanie multimediów za bardzo ogranicza nasze możliwości, dlatego opracowaliśmy nową specyfikację, która będzie obsługiwać wszelkie urządzenia (w przyszłości). Nic dziwnego, że w projekcie zastosowano nowy element – element <device>, który stał się poprzednim elementem getUserMedia().

Opera była jedną z pierwszych przeglądarek, które opracowały wstępne implementacje przechwytywania wideo na podstawie elementu <device>. Niedługo potem (dokładnie tego samego dnia) zespół WhatWG zdecydował się na zastąpienie tagu <device> innym, tym razem JavaScript API o nazwie navigator.getUserMedia(). Tydzień później wprowadziła nowe kompilacje do zaktualizowanej specyfikacji getUserMedia(). Później w tym roku do gry dołączyła firma Microsoft, wydając Laboratorium dla IE9 obsługującą nową specyfikację.

Tak wyglądałoby pole <device>:

<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
  function update(stream) {
    document.querySelector('video').src = stream.url;
  }
</script>

Pomoc:

Niestety w żadnej z opublikowanej przeglądarki nie została jeszcze dodana <device>. O jeden API mniej zmartwień :) <device> ma jednak 2 korzyści: 1) jego znaczenie i 2.) można go łatwo rozszerzyć i obsługiwać nie tylko urządzenia audio/wideo.

Weź oddech. Poruszaj się szybko!

Runda 3. WebRTC

W końcu element <device> trafił do dodo.

Szybsze znalezienie odpowiedniego interfejsu API przechwytywania dzięki większemu wysiłkom WebRTC (Web Real Time Communications). Specyfikacją zarządza grupa robocza W3C WebRTC. Google, Opera, Mozilla i kilka innych firm mają swoje implementacje.

getUserMedia() jest powiązany z WebRTC, ponieważ jest bramą do tego zestawu interfejsów API. Daje dostęp do strumienia danych z kamery/mikrofonu użytkownika.

Pomoc:

Usługa getUserMedia() jest obsługiwana od wersji Chrome 21, Opera 18 i Firefox od wersji 17.

Wprowadzenie

Dzięki navigator.mediaDevices.getUserMedia() możemy w końcu korzystać z kamery internetowej i mikrofonu bez konieczności używania wtyczki. Dostęp do aparatu wymaga połączenia, nie instalacji. Jest on wbudowany bezpośrednio w przeglądarkę. Już nie możesz się doczekać?

Wykrywanie funkcji

Wykrywanie cech to prosty sposób na sprawdzenie, czy obiekt navigator.mediaDevices.getUserMedia istnieje:

if (navigator.mediaDevices?.getUserMedia) {
  // Good to go!
} else {
  alert("navigator.mediaDevices.getUserMedia() is not supported");
}

Uzyskiwanie dostępu do urządzenia wejściowego

Aby korzystać z kamery internetowej lub mikrofonu, musisz poprosić o odpowiednie uprawnienia. Pierwszy parametr navigator.mediaDevices.getUserMedia() to obiekt określający szczegóły i wymagania dotyczące każdego typu multimediów, do których chcesz uzyskać dostęp. Jeśli np. chcesz uzyskać dostęp do kamery internetowej, pierwszym parametrem powinien być {video: true}. Aby używać zarówno mikrofonu, jak i kamery, musisz przejść {video: true, audio: true}:

<video autoplay></video>

<script>
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: true })
    .then((localMediaStream) => {
      const video = document.querySelector("video");
      video.srcObject = localMediaStream;
    })
    .catch((error) => {
      console.log("Rejected!", error);
    });
</script>

OK. O co tu chodzi? Funkcja przechwytywania multimediów to doskonały przykład współdziałania nowych interfejsów API HTML5. Działa we współpracy z innymi znajomymi w zakresie HTML5: <audio> i <video>. Zwróć uwagę, że nie ustawiamy atrybutu src ani nie dodajemy elementów <source> w elemencie <video>. Zamiast przekazywać filmowi adres URL do pliku multimedialnego, ustawiamy właściwość srcObject na obiekt LocalMediaStream reprezentujący kamerę internetową.

Przekazuję też element <video> do autoplay. W przeciwnym razie zostałby zablokowany w pierwszej klatce. Dodawanie elementu controls również działa zgodnie z oczekiwaniami.

ustawianie ograniczeń dotyczących multimediów (rozdzielczość, wysokość i szerokość);

Pierwszy parametr funkcji getUserMedia() może też służyć do określania dodatkowych wymagań (lub ograniczeń) dotyczących zwróconego strumienia multimediów. Na przykład zamiast wskazywać, że chcesz uzyskać podstawowy dostęp do filmu (np. {video: true}), możesz dodatkowo wymagać, aby strumień był przesyłany w jakości HD:

const hdConstraints = {
  video: { width: { exact:  1280} , height: { exact: 720 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
const vgaConstraints = {
  video: { width: { exact:  640} , height: { exact: 360 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);

Aby dowiedzieć się więcej o konfiguracjach, poczytaj o interfejsie API ograniczeń.

Wybieranie źródła multimediów

Metoda enumerateDevices() interfejsu MediaDevices prosi o listę dostępnych urządzeń wejściowych i wyjściowych, takich jak mikrofony, kamery, zestawy słuchawkowe itp. Zwrócona obietnica jest rozpoznawana za pomocą tablicy obiektów MediaDeviceInfo opisującej urządzenia.

W tym przykładzie jako źródło strumienia multimediów są wybrany ostatni mikrofon i kamera:

if (!navigator.mediaDevices?.enumerateDevices) {
  console.log("enumerateDevices() not supported.");
} else {
  // List cameras and microphones.
  navigator.mediaDevices
    .enumerateDevices()
    .then((devices) => {
      let audioSource = null;
      let videoSource = null;

      devices.forEach((device) => {
        if (device.kind === "audioinput") {
          audioSource = device.deviceId;
        } else if (device.kind === "videoinput") {
          videoSource = device.deviceId;
        }
      });
      sourceSelected(audioSource, videoSource);
    })
    .catch((err) => {
      console.error(`${err.name}: ${err.message}`);
    });
}

async function sourceSelected(audioSource, videoSource) {
  const constraints = {
    audio: { deviceId: audioSource },
    video: { deviceId: videoSource },
  };
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
}

Obejrzyj świetną prezentację Sama Duttona, która pokazuje, jak umożliwić użytkownikom wybór źródła multimediów.

Bezpieczeństwo

Podczas wywoływania funkcji navigator.mediaDevices.getUserMedia() przeglądarki wyświetlają okno uprawnień, które umożliwia przyznanie lub zablokowanie dostępu do kamery/mikrofonu. Tak wygląda np. okno uprawnień w Chrome:

Okno uprawnień w Chrome
Okno uprawnień w Chrome

Stosowanie kreacji zastępczych

W przypadku użytkowników, którzy nie obsługują navigator.mediaDevices.getUserMedia(), jedną z możliwości jest skorzystanie z dotychczasowego pliku wideo, jeśli interfejs API nie jest obsługiwany lub wywołanie nie powiedzie się z jakiegoś powodu:

if (!navigator.mediaDevices?.getUserMedia) {
  video.src = "fallbackvideo.webm";
} else {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  video.srcObject = stream;
}