Audio und Video in HTML5 aufnehmen

Einleitung

Die Aufnahme von Audio und Video ist schon lange der „Heilige Gral“ der Webentwicklung. Viele Jahre waren wir auf Browser-Plug-ins (Flash oder Silverlight) angewiesen, um Aufgaben zu erledigen. Komm schon!

HTML5 die Lösung. Es ist vielleicht nicht offensichtlich, aber die Zunahme von HTML5 hat zu einem plötzlichen Zugriff auf Gerätehardware geführt. Standortbestimmung (GPS), die Orientation API (Beschleunigungsmesser), WebGL (GPU) und die Web Audio API (Audiohardware) sind dafür perfekte Beispiele. Diese Funktionen sind unglaublich leistungsstark und es stehen High-Level-JavaScript-APIs zur Verfügung, die die zugrunde liegenden Hardwarefunktionen des Systems ergänzen.

In dieser Anleitung wird die neue API GetUserMedia vorgestellt, mit der Webanwendungen auf die Kamera und das Mikrofon eines Nutzers zugreifen können.

Der Weg zu getUserMedia()

Wie wir zur getUserMedia() API gelangt sind, ist eine interessante Geschichte.

In den letzten Jahren haben sich mehrere Varianten von Media Capture APIs entwickelt. Viele Leute erkannten die Notwendigkeit, auf native Geräte im Web zugreifen zu können. Doch das hat alle dazu veranlasst, neue Spezifikationen zu erstellen. Das war so chaotisch, dass sich das W3C schließlich zur Gründung einer Arbeitsgruppe beschloss. Zu welchem Zweck dient sie nur? Erfahre mehr über den Wahnsinn! Die Arbeitsgruppe für Device APIs Policy (DAP) wurde beauftragt, eine Vielzahl von Vorschlägen zu konsolidieren und zu standardisieren.

Ich versuche zusammenzufassen, was 2011 passiert ist...

1. Runde: HTML Media Capture

HTML Media Capture war der erste Versuch des DAP zur Standardisierung der Medienaufnahme im Web. Dabei wird <input type="file"> überlastet und es werden neue Werte für den Parameter accept hinzugefügt.

Wenn Nutzer mit der Webcam ein Schnappschuss von sich selbst machen möchten, ist das mit capture=camera möglich:

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

Bei der Aufnahme eines Videos oder Audios ist es ähnlich:

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

Schön, oder? Besonders gut gefällt mir, dass eine Dateieingabe wiederverwendet wird. Semantisch ist das sehr sinnvoll. Allerdings sind mit dieser API keine Echtzeiteffekte möglich, z.B. das Rendern von Live-Webcam-Daten in einem <canvas> und das Anwenden von WebGL-Filtern. HTML Media Capture ermöglicht nur das Aufzeichnen von Mediendateien oder die Erstellung von Momentaufnahmen im Zeitverlauf.

Support:

  • Android 3.0-Browser – eine der ersten Implementierungen. In diesem Video kannst du sie in Aktion sehen.
  • Chrome für Android (0.16)
  • Firefox Mobile 10.0
  • iOS6 Safari und Chrome (teilweise Unterstützung)

Runde 2: device-Element

Viele waren der Meinung, dass HTML Media Capture zu viele Einschränkungen mit sich brachte. Daher entstand eine neue Spezifikation, die alle Arten von (zukünftigen) Geräten unterstützt. Wenig überraschend erforderte das Design ein neues Element, das <device>-Element, das zum Vorgänger von getUserMedia() wurde.

Opera gehörte zu den ersten Browsern, die anfängliche Implementierungen von Videoaufnahmen auf Grundlage des <device>-Elements erstellten. Kurz darauf (am selben Tag, um genau zu sein) beschloss die WhatWG, das <device>-Tag zu entfernen und stattdessen ein anderes Produkt zu verwenden – diesmal eine JavaScript API namens navigator.getUserMedia(). Eine Woche später veröffentlichte Opera neue Builds, die die aktualisierte getUserMedia()-Spezifikation unterstützten. Im Laufe des Jahres schloss sich Microsoft der Partei an, indem es ein Lab für IE9 veröffentlichte, das die neue Spezifikation unterstützte.

So hätte <device> ausgesehen:

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

Support:

Leider hat kein veröffentlichter Browser jemals <device> enthalten. Eine API weniger, über die man sich wahrscheinlich Gedanken machen muss, hat <device> zwei Vorteile: 1.) Es war semantisch und 2.) Es konnte problemlos auf weitere Audio-/Videogeräte erweitert werden.

Atmen Sie tief durch. Das geht ganz schnell!

Runde 3: WebRTC

Das <device>-Element ging schließlich den Weg des Dodo.

Dank der größeren Neuerungen bei WebRTC (Web Real Time Communications) konnten wir noch schneller eine geeignete Capture API finden. Diese Spezifikation wird von der W3C WebRTC Working Group überwacht. Google, Opera, Mozilla und einige andere haben Implementierungen.

getUserMedia() ist mit WebRTC verwandt, da es das Gateway für diese Gruppe von APIs ist. Sie ermöglicht den Zugriff auf den lokalen Kamera-/Mikrofonstream des Nutzers.

Support:

getUserMedia() wird seit Chrome 21, Opera 18 und Firefox 17 unterstützt.

Erste Schritte

Mit navigator.mediaDevices.getUserMedia() können wir endlich die Webcam- und Mikrofoneingabe ohne Plug-in nutzen. Der Kamerazugriff ist jetzt nur noch einen Anruf entfernt und keine Installation erforderlich. Sie ist direkt in den Browser integriert. Sind Sie schon gespannt?

Funktionserkennung

Die Featureerkennung ist eine einfache Prüfung des Vorhandenseins von navigator.mediaDevices.getUserMedia:

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

Zugriff auf ein Eingabegerät erhalten

Zur Verwendung der Webcam oder des Mikrofons muss eine Berechtigung angefordert werden. Der erste Parameter für navigator.mediaDevices.getUserMedia() ist ein Objekt, das die Details und Anforderungen für jeden Medientyp angibt, auf den Sie zugreifen möchten. Wenn Sie beispielsweise auf die Webcam zugreifen möchten, sollte der erste Parameter {video: true} sein. Wenn Sie sowohl das Mikrofon als auch die Kamera verwenden möchten, übergeben Sie {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>

Okay. Was ist hier los? Die Medienerfassung ist ein perfektes Beispiel dafür, wie die neuen HTML5-APIs zusammenwirken. Sie können es zusammen mit unseren anderen HTML5-Partnern, <audio> und <video>, verwenden. Hinweis: Wir legen kein src-Attribut fest und nehmen kein <source>-Element für das <video>-Element auf. Anstatt dem Video eine URL in eine Mediendatei zu übertragen, setzen wir srcObject auf das LocalMediaStream-Objekt, das die Webcam darstellt.

Außerdem lege ich für <video> autoplay fest, sonst wäre es im ersten Frame eingefroren. Das Hinzufügen von controls funktioniert auch wie erwartet.

Medienbeschränkungen (Auflösung, Höhe, Breite) festlegen

Der erste Parameter für getUserMedia() kann auch verwendet werden, um weitere Anforderungen (oder Einschränkungen) für den zurückgegebenen Mediastream anzugeben. Anstatt nur anzugeben, dass Sie grundlegenden Zugriff auf Videos wünschen (z.B. {video: true}), können Sie zusätzlich festlegen, dass der Stream in HD sein muss:

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

Weitere Konfigurationen finden Sie in der Einschränkungs-API.

Medienquelle auswählen

Mit der Methode enumerateDevices() der Schnittstelle MediaDevices wird eine Liste der verfügbaren Medieneingabe- und -ausgabegeräte wie Mikrofone, Kameras, Headsets usw. angefordert. Das zurückgegebene Promise wird mit einem Array von MediaDeviceInfo-Objekten aufgelöst, die die Geräte beschreiben.

In diesem Beispiel werden das letzte gefundene Mikrofon und die zuletzt gefundene Kamera als Medienstreamquelle ausgewählt:

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

In der großartigen Demo von Sam Duton erfahren Sie, wie Nutzer die Medienquelle auswählen können.

Sicherheit

Browser zeigen beim Aufrufen von navigator.mediaDevices.getUserMedia() ein Berechtigungsdialogfeld an, in dem Nutzer den Zugriff auf ihre Kamera bzw. ihr Mikrofon erlauben oder verweigern können. Hier sehen Sie zum Beispiel das Berechtigungsdialogfeld von Chrome:

Berechtigungsdialogfeld in Chrome
Dialogfeld „Berechtigungen“ in Chrome

Fallback bereitstellen

Nutzer, die navigator.mediaDevices.getUserMedia() nicht unterstützen, können ein Fallback auf eine vorhandene Videodatei einrichten, wenn die API nicht unterstützt wird und/oder der Aufruf aus irgendeinem Grund fehlschlägt:

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