Audio und Video in HTML5 aufnehmen

Die Audio-/Videoaufnahme ist seit langem der „Heilige Gral“ der Webentwicklung. Viele Jahre lang mussten wir uns auf Browser-Plug-ins wie Flash oder Silverlight verlassen. Komm schon!

HTML5 als Retter in der Not Es ist vielleicht nicht offensichtlich, aber der Aufstieg von HTML5 hat zu einem Anstieg des Zugriffs auf die Gerätehardware geführt. Geolocation (GPS), die Orientation API (Beschleunigungsmesser), WebGL (GPU) und die Web Audio API (Audiohardware) sind perfekte Beispiele. Diese Funktionen sind unglaublich leistungsstark und bieten hochrangige JavaScript-APIs, die auf den zugrunde liegenden Hardwarefunktionen des Systems aufbauen.

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

Falls Sie nicht mit der Geschichte der getUserMedia() API vertraut sind, ist das eine interessante Geschichte.

In den letzten Jahren haben sich mehrere Varianten von „Media Capture APIs“ entwickelt. Viele erkannten die Notwendigkeit, auf native Geräte im Web zugreifen zu können, aber das führte dazu, dass jeder eine neue Spezifikation erstellte. Die Situation wurde so unübersichtlich, dass das W3C schließlich beschloss, eine Arbeitsgruppe zu bilden. Was ist ihr einziger Zweck? Bringen Sie Ordnung ins Chaos! Die Arbeitsgruppe für die Richtlinie zu Device APIs (DAP) wurde damit beauftragt, die Vielzahl der Vorschläge zu konsolidieren und zu standardisieren.

Ich versuche, das Jahr 2011 zusammenzufassen…

Runde 1: HTML-Medienaufnahme

HTML Media Capture war der erste Versuch des DAP, die Medienaufnahme im Web zu standardisieren. Dazu wird <input type="file"> überladen und neue Werte für den Parameter accept hinzugefügt.

Wenn Sie Nutzern erlauben möchten, mit der Webcam einen Schnappschuss von sich aufzunehmen, ist das mit capture=camera möglich:

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

Die Aufnahme von Videos oder Audioinhalten funktioniert ähnlich:

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

Ziemlich praktisch, oder? Besonders gut gefällt mir, dass die Dateieingabe wiederverwendet wird. Semantisch macht das sehr viel Sinn. Diese „API“ bietet keine Echtzeiteffekte, z.B. das Rendern von Live-Webcamdaten in eine <canvas> und das Anwenden von WebGL-Filtern. Mit HTML Media Capture können Sie nur eine Mediendatei aufnehmen oder einen Zeitraffer erstellen.

Support:

  • Android 3.0-Browser: Eine der ersten Implementierungen. In diesem Video zeigen wir dir, wie das geht.
  • Chrome für Android (0.16)
  • Firefox für Mobilgeräte 10.0
  • iOS 6 Safari und Chrome (teilweise Unterstützung)

Runde 2: Geräteelement

Viele empfanden HTML Media Capture als zu eingeschränkt. Daher wurde eine neue Spezifikation entwickelt, die alle Arten von (zukünftigen) Geräten unterstützt. Es überrascht nicht, dass das Design ein neues Element erforderte: das <device>-Element, das zum Vorgänger von getUserMedia() wurde.

Opera war einer der ersten Browser, der erste Implementierungen der Videoaufzeichnung auf der Grundlage des <device>-Elements erstellte. Bald darauf, genauer gesagt am selben Tag, beschloss die WhatWG, das <device>-Tag zugunsten eines anderen neuen Standards aufzugeben, diesmal einer JavaScript API namens navigator.getUserMedia(). Eine Woche später veröffentlichte Opera neue Builds mit Unterstützung für die aktualisierte getUserMedia()-Spezifikation. Später im selben Jahr schloss sich Microsoft an und veröffentlichte ein Lab für IE9, 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 wurde <device> in keinem veröffentlichten Browser unterstützt. Eine API weniger, um die man sich kümmern muss. <device> hatte aber zwei tolle Vorteile: 1. Sie war semantisch und 2. sie ließ sich leicht erweitern, um nicht nur Audio-/Videogeräte zu unterstützen.

Atmen Sie tief durch. Das geht schnell!

Runde 3: WebRTC

Das <device>-Element wurde schließlich eingestellt.

Die Suche nach einer geeigneten Capture API wurde durch die größeren WebRTC-Aktivitäten (Web Real Time Communications) beschleunigt. Diese Spezifikation wird von der W3C WebRTC Working Group verwaltet. Google, Opera, Mozilla und einige andere haben Implementierungen.

getUserMedia() ist mit WebRTC verbunden, da es das Gateway zu diesen APIs ist. Er bietet die Möglichkeit, auf den lokalen Kamera-/Mikrofonstream des Nutzers zuzugreifen.

Support:

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

Erste Schritte

Mit navigator.mediaDevices.getUserMedia() können wir endlich Webcam- und Mikrofoneingabe ohne Plug-in nutzen. Der Zugriff auf die Kamera ist jetzt per Anruf möglich, nicht mehr per Installation. Sie ist direkt in den Browser eingebunden. Schon gespannt?

Funktionserkennung

Die Feature-Erkennung ist eine einfache Prüfung, ob navigator.mediaDevices.getUserMedia vorhanden ist:

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

Zugriff auf ein Eingabegerät erhalten

Wenn wir die Webcam oder das Mikrofon verwenden möchten, müssen wir um Erlaubnis bitten. Der erste Parameter für navigator.mediaDevices.getUserMedia() ist ein Objekt, das die Details und Anforderungen für jede Art von Medien angibt, auf die du zugreifen möchtest. 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, passen Sie {video: true, audio: true} so an:

<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. Was ist hier los? Die Medienaufnahme ist ein perfektes Beispiel für die Zusammenarbeit neuer HTML5-APIs. Sie funktioniert in Kombination mit unseren anderen HTML5-Freunden <audio> und <video>. Beachten Sie, dass wir kein src-Attribut festlegen und keine <source>-Elemente in das <video>-Element einfügen. Anstatt dem Video eine URL zu einer Mediendatei zu übergeben, setzen wir srcObject auf das LocalMediaStream-Objekt, das die Webcam darstellt.

Ich sage auch <video> zu autoplay, da sie sonst auf dem ersten Frame eingefroren wäre. Das Hinzufügen von controls funktioniert ebenfalls wie erwartet.

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

Mit dem ersten Parameter für getUserMedia() können auch weitere Anforderungen (oder Einschränkungen) für den zurückgegebenen Medienstream angegeben werden. Anstatt nur anzugeben, dass du grundlegenden Zugriff auf das Video benötigst (z.B. {video: true}), kannst du 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 Constraints API.

Medienquelle auswählen

Die enumerateDevices()-Methode der MediaDevices-Benutzeroberfläche ruft eine Liste der verfügbaren Medieneingabe- und ‑ausgabegeräte ab, z. B. Mikrofone, Kameras und Headsets. Das zurückgegebene Promise wird mit einem Array von MediaDeviceInfo-Objekten aufgelöst, die die Geräte beschreiben.

In diesem Beispiel werden das letzte Mikrofon und die letzte Kamera, die gefunden werden, als Quelle für den Medienstream 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 tollen Demo von Sam Dutton wird gezeigt, 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/ihr Mikrofon gewähren oder verweigern können. Hier ist beispielsweise das Berechtigungsdialogfeld von Chrome zu sehen:

Berechtigungsdialogfeld in Chrome
Berechtigungsdialogfeld in Chrome

Fallback bereitstellen

Wenn navigator.mediaDevices.getUserMedia() nicht unterstützt wird, können Nutzer auf eine vorhandene Videodatei zurückgreifen, 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;
}