Audio und Video in HTML5 aufnehmen

Die Aufnahme von Audio und Video ist seit Langem der „Heilige Gral“ in der Webentwicklung. Viele Jahre lang waren wir auf Browser-Plug-ins (Flash oder Silverlight) angewiesen, um unsere Aufgaben zu erledigen. 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 äußerst leistungsfähig und stellen anspruchsvolle JavaScript-APIs bereit, die auf den zugrunde liegenden Hardwarefunktionen des Systems basieren.

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 verschiedene Varianten der 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? Erkenne den Wahnsinn! 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 mal, zusammenzufassen, was 2011 passiert ist...

1. Runde: HTML Media Capture

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 zulassen möchten, dass Nutzer mit der Webcam einen Schnappschuss von sich selbst machen können, ist das mit capture=camera möglich:

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

Die Video- oder Audioaufnahme 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 eine Dateieingabe wiederverwendet wird. Semantisch macht das sehr viel Sinn. Diese „API“ bietet keine Möglichkeit, Echtzeiteffekte zu erstellen (z. B. Live-Webcamdaten in eine <canvas> zu rendern und WebGL-Filter anzuwenden). 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. Kurz darauf (am selben Tag, um genau zu sein) beschloss die WhatWG, das <device>-Tag zugunsten eines anderen Up-and-Comedian abzuschaffen – diesmal eine JavaScript API mit dem Namen navigator.getUserMedia(). Eine Woche später veröffentlichte Opera neue Builds, die die aktualisierte getUserMedia()-Spezifikation unterstützten. Später in diesem Jahr veröffentlichte Microsoft ein Lab für IE9 zur Unterstützung der neuen Spezifikation.

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 war leicht erweiterbar, um nicht nur Audio-/Videogeräte zu unterstützen.

Atme tief durch. Das ist alles schnell!

Runde 3: WebRTC

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

Die Entwicklung einer geeigneten API für die Aufnahme konnte dank WebRTC (Web Real Time Communications) schneller erledigt werden. 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. Sie 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 Kamerazugriff ist jetzt nur noch einen Abruf entfernt und es ist keine Installation erforderlich. 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 zusammen mit unseren anderen HTML5-Elementen <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.

Außerdem weise ich für das <video> den Wert autoplay an, da es sonst im 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) an 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

Über die Methode enumerateDevices() der MediaDevices-Schnittstelle wird eine Liste der verfügbaren Medieneingabe- und -ausgabegeräte wie Mikrofone, Kameras und Headsets 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 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 Sam Duttons toller Demo erfährst du, 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 zum Beispiel das Berechtigungsdialogfeld von Chrome:

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