Przechwytywanie dźwięku i obrazu w HTML5

Wprowadzenie

Nagrywanie dźwięku i obrazu to „Święty Graal” programowania stron internetowych. Przez wiele lat korzystaliśmy z wtyczek do przeglądarek (Flash Silverlight) wykonując swoją pracę. Szybciej!

HTML5 na ratunek. Może się to wydawać oczywiste, ale rozwój HTML5 sprawił, gwałtowny wzrost dostępu do sprzętu. Geolokalizacja (GPS), Orientation API (akcelerometr), WebGL (GPU), oraz Web Audio API (sprzęt audio) to doskonałych przykładów. Funkcje te które oferują zaawansowane funkcje, ujawniają ogólne interfejsy API JavaScript, poza możliwościami sprzętowymi systemu.

W tym samouczku przedstawiamy nowy interfejs API, GetUserMedia, który umożliwia aplikacji internetowych, aby uzyskać dostęp do aparatu i mikrofonu użytkownika.

Droga do getUserMedia()

Być może nie wiesz nic o jego historii, ale sposób, w jaki dotarliśmy do interfejsu API getUserMedia(), jest ciekawostką.

Kilka wariantów „Media Capture API” ale ewoluowała w ciągu ostatnich kilku lat. Wiele osób wiedziało, że trzeba mieć dostęp do urządzeń natywnych w internecie, który skłonił wszystkich i ich mamę do stworzenia nowej specyfikacji. Zmiany tak chaotyczny, że organizacja W3C w końcu postanowiła utworzyć grupę roboczą. Ich jedynym przeznaczeniem? Rozumcie szaleństwo! Grupa robocza DAP (Device APIs Policy), mieliśmy za zadanie skonsolidowanie i ustandaryzowanie mnóstwa propozycji.

Spróbuję podsumować, co wydarzyło się w 2011 roku...

Runda 1: Przechwytywanie multimediów HTML

HTML Media Capture to pierwsza wizyta agencji DAP na standaryzacji przechwytywania multimediów w internecie. Działanie tego mechanizmu polega na przeciążeniu interfejsu <input type="file"> i dodanie nowych wartości w parametrze accept.

Jeśli chcesz umożliwić użytkownikom zrobienie sobie zdjęcia kamerą, to możliwe dzięki capture=camera:

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

Nagrywanie filmu lub dźwięku jest podobne do:

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

Nieźle, prawda? Szczególnie podoba mi się to, że wykorzystuje on dane wejściowe pliku. Semantycznie: ma to sens. Gdzie ten „interfejs API” jest niedoskonałości, jest możliwość dodawania efektów w czasie rzeczywistym (np. renderowanie danych z kamery internetowej na żywo w <canvas> i zastosowanie filtrów WebGL). Funkcja HTML Media Capture umożliwia zarejestrowanie tylko pliku multimedialnego lub wykonanie zrzutu w czasie.

Pomoc:

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

Runda 2: element urządzenia

Wielu użytkowników uważało, że kod HTML Media Capture jest zbyt restrykcyjny, więc nowa specyfikacja które obsługują wszystkie typy (przyszłe) urządzenia. Nic dziwnego, że nazwa dla nowego elementu: elementu <device>, która stała się poprzednikiem getUserMedia().

Opera była jedną z pierwszych przeglądarek, które stworzyły wstępne implementacje nagrania wideo na podstawie elementu <device>. Wkrótce po (dokładnie tego samego dnia), Zespół WhatWG postanowił usunąć tag <device> na rzecz innego tagu „up” – tym razem nowego interfejsu JavaScript API o nazwie navigator.getUserMedia() Tydzień później Opera wydała nowe kompozycje, i obsługują zaktualizowaną specyfikację getUserMedia(). Później w tym samym roku Firma Microsoft dołącza do tej strony, udostępniając Laboratorium dla IE9 które obsługują nową specyfikację.

Tak wyglądałby URL: <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 żadna z opublikowanych przeglądarek nie korzystała dotąd z <device>. Jeden interfejs API mniej. Wygląda na to, że :) <device> ma dwie świetne rzeczy, 1) była semantyczna i 2) można było łatwo rozszerzyć nie tylko urządzenia audio/wideo.

Weź oddech. A to wszystko szybko!

Runda 3. WebRTC

Element <device> w końcu stał się celem Dodo.

Szybszy sposób na znalezienie odpowiedniego interfejsu API do przechwytywania jest szybszy dzięki większemu nakładowi pracy w ramach WebRTC (Web Real Time Communications). Tą specyfikacją nadzoruje grupa robocza W3C WebRTC. Google, Opera, Mozilla i kilka innych mają już takie implementacje.

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

Pomoc:

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

Pierwsze kroki

Dzięki navigator.mediaDevices.getUserMedia() możemy wreszcie korzystać z kamery internetowej i mikrofonu bez użycia wtyczki. Aby korzystać z kamery, wystarczy telefon, a nie instalacja. Jest on bezpośrednio wypiekany w przeglądarce. Nie możesz się już doczekać?

Wykrywanie cech

Wykrywanie cech pozwala w prosty sposób sprawdzić, czy 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 móc korzystać z kamery internetowej lub mikrofonu, musimy poprosić o pozwolenie. Pierwszym parametrem navigator.mediaDevices.getUserMedia() jest obiekt określający szczegóły wymagania dotyczące różnych typów multimediów, do których chcesz uzyskać dostęp. Jeśli na przykład chcesz uzyskać dostęp do kamery internetowej, pierwszym parametrem powinien być {video: true}. Aby używać mikrofonu i kamery, przekaż {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? Przechwytywanie multimediów to doskonały przykład nowych interfejsów API HTML5. co umożliwia współpracę. Działa w połączeniu z innymi rozwiązaniami HTML5: <audio> i <video>. Zwróć uwagę, że nie ustawiamy atrybutu src ani nie uwzględniamy elementów <source> w elemencie <video>. Zamiast podać URL filmu do pliku multimedialnego, ustawiamy srcObject do obiektu LocalMediaStream reprezentującego kamerę internetową.

Wysyłam też komunikatowi <video> do funkcji autoplay – w przeciwnym razie zawiesiłby się na pierwszej klatki. Dodanie controls również przebiega zgodnie z oczekiwaniami.

Ustawianie ograniczeń dotyczących multimediów (rozdzielczości, wysokości, szerokości)

Pierwszy parametr 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 wskazać, że chcesz mieć podstawowy dostęp do filmu (np. {video: true}), możesz dodatkowo wymagać strumienia do wersji 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);

Więcej konfiguracji znajdziesz w artykule na temat interfejsu API ograniczeń.

Wybieranie źródła multimediów

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

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

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 świetny pokaz Sama Duttona, jak to zrobić aby umożliwić użytkownikom wybór źródła multimediów.

Bezpieczeństwo

Po wywołaniu funkcji navigator.mediaDevices.getUserMedia() przeglądarki wyświetlają okno uprawnień który daje użytkownikom możliwość przyznania lub odmowy dostępu do kamery/mikrofonu. Na przykład tutaj jest okno dialogowe uprawnień Chrome:

Okno uprawnień w Chrome
Okno uprawnień w Chrome

Udostępniam kreację zastępczą

W przypadku użytkowników, którzy nie obsługują navigator.mediaDevices.getUserMedia(), istnieje możliwość użycia kreacji zastępczej. do istniejącego pliku wideo, jeśli interfejs API nie jest obsługiwany lub wywołanie się nie uda:

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