Capturing Audio & Video in HTML5

HTML5 Rocks

This article discusses APIs that are not yet fully standardized and still in flux. Be cautious when using experimental APIs in your own projects.

Einführung

Die Aufnahme von Audio und Video ist bereits seit längerem das Nonplusultra in der Webentwicklung. Viele Jahre lang mussten wir uns auf die Browser-Plug-ins Flash oder Silverlight verlassen, um unsere Aufgaben zu erledigen. Come on!

Aber jetzt kommt HTML5. Vielleicht ist es nicht offensichtlich, aber der Anstieg bei HTML5 hat zu einem gesteigerten Zugriff auf Gerätehardware geführt. Geolocation (GPS), das Orientation API (Beschleunigungsmesser), WebGL (GPU) sowie das Web Audio API (Audio-Hardware) sind hierfür die perfekten Beispiele. Diese Funktionen sind enorm leistungsstark und nutzen anspruchsvolle JavaScript-APIs, die auf den grundlegenden Hardwarefähigkeiten des Systems beruhen.

In dieser Anleitung wird ein neues API vorgestellt, navigator.getUserMedia(), mit der Webanwendungen auf die Kamera und das Mikrofon eines Nutzers zugreifen können.

Der Weg zu getUserMedia()

Wenn Sie die bisherige Geschichte nicht kennen, wird es Sie bestimmt interessieren, wie wir zum getUserMedia()-API gelangt sind.

Über die letzten Jahre hinweg wurden mehrere "Media Capture APIs" entwickelt. Viele Leute erkannten, dass es notwendig ist, auf native Geräte im Web zugreifen zu können. Dadurch entwickelte dann aber jeder neue Spezifikationen. Das Durcheinander war so groß, dass sich W3C schließlich zur Gründung einer Arbeitsgruppe entschloss. Deren Aufgabe war es einzig und allein, ein wenig Ordnung in dieses Chaos zu bringen. Die Device APIs Policy (DAP) Working Group macht sich die Standardisierung einer Vielzahl von Vorschlägen zur Aufgabe.

Ich versuche zusammenzufassen, was im Jahr 2011 passierte...

1. Runde: HTML Media Capture

HTML Media Capture war der erste Versuch der DAP zur Standardisierung der Medienaufnahme im Internet. Dabei wird <input type="file"> überlastet und es werden 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, ist das mit capture=camera möglich:

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

Ebenso gelingt das Aufzeichnen von Video oder Audio:

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

Ganz nett, oder? Mir gefällt vor allem, dass eine Dateieingabe verwendet wird. Semantisch ist das sehr sinnvoll. Leider bietet dieses bestimmte API keine Möglichkeit für Echtzeiteffekte, z. B. das Darstellen von Live-Webcam-Daten in einem <canvas> und die Anwendung von WebGL-Filtern. HTML Media Capture ermöglicht lediglich die Aufnahme einer Mediendatei oder das Erstellen einer Momentaufnahme.

Support:

Ich empfehle Ihnen, diese Option nicht zu nutzen, es sei denn, sie arbeiten mit einem der zuvor erwähnten mobilen Browser. Immer mehr Anbieter entscheiden sich für getUserMedia(). Es ist sehr unwahrscheinlich, dass HTML Media Capture langfristig weiterhin eingesetzt wird.

2. Runde: device-Element

Viele waren der Ansicht, dass HTML Media Capture zu viele Einschränkungen mit sich brachte. Daher erschien eine neue Spezifikation, die alle künftigen Geräte unterstützen sollte. Natürlich verlangte das neue Design nach einem neuen Element, dem <device>-Element, das zum Vorgänger von getUserMedia() wurde.

Opera war der erste Browser, der anfängliche Implementierungen von Videoaufnahmen auf Grundlage des <device>-Elements erstellte. Kurz danach (am selben Tag, um genau zu sein) entschied die WhatWG, das <device>-Tag aufzugeben und stattdessen einen Newcomer zu verwenden. Hierbei handelte es sich um ein 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 stieß Microsoft hinzu und veröffentlichte ein Lab for IE9, das die neue Spezifikation unterstützte.

So hätte die <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. Nun ja, eigentlich ist es ein API weniger, über das man sich Gedanken machen muss. :) <device> bot jedoch zwei herausragende Vorteile: 1.) Es war semantisch und 2.) es ließ sich ganz leicht erweitern, um mehr als lediglich Audio- und Videogeräte zu unterstützen.

Atmen Sie mal tief durch. Diese Entwicklung geht wirklich in rasender Geschwindigkeit voran.

3. Runde: WebRTC

Das <device>-Element ging schließlich den Weg alles Irdischen.

In den letzten Monaten wurde die Suche nach einem passenden Capture API aufgrund einer größeren Initiative namens WebRTC (Web Real Time Communications) noch weiter vorangetrieben. Die Spezifikation wird von der W3C WebRTC Working Group überwacht. Google, Opera, Mozilla und ein paar andere arbeiten aktuell an Implementationen in ihren Browsern.

getUserMedia() bezieht sich auf WebRTC, da es das Gateway in diesem Satz von APIs darstellt. Es bietet die Möglichkeit für den Zugriff auf den lokalen Kamera-/Mikrofon-Stream des Nutzers.

Support:

WebRTC kann in Chrome 18.0.1008+ unter about:flags aktiviert werden.

Erste Schritte

Durch navigator.getUserMedia(), kann endlich die Webcam- und die Mikrofoneingabe ohne Plug-in genutzt werden. Der Kamerazugriff ist nun nur noch einen Abruf entfernt und es ist keine Installation erforderlich. Alles ist direkt im Browser enthalten. Das gefällt Ihnen, stimmt's?

Aktivieren

Das getUserMedia()-API ist immer noch sehr neu. Nur Google und Opera verfügen über Entwickler-Builds, die es enthalten. In Chrome 18+ kann das API unter about:flags aktiviert werden.

getUserMedia() auf der Chrome-Seite about:flags aktivieren

Laden Sie für Opera einen der experimentellen Android- und Desktop-Builds herunter.

Funktionserkennung

Die Funktionserkennung ist eine einfache Überprüfung des Vorhandenseins von navigator.getUserMedia:

function hasGetUserMedia() {
  // Note: Opera builds are unprefixed.
  return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
            navigator.mozGetUserMedia || navigator.msGetUserMedia);
}

if (hasGetUserMedia()) {
  // Good to go!
} else {
  alert('getUserMedia() is not supported in your browser');
}

Auf ein Eingabegerät zugreifen

Für die Verwendung der Webcam oder des Mikrofons muss eine Berechtigung angefordert werden. Der erste Parameter für getUserMedia() ist für die Angabe des Medientyps gedacht, auf den Sie zugreifen möchten. Wenn Sie beispielsweise die Webcam anfordern möchten, sollte der erste Parameter "video" lauten. Wenn Sie sowohl das Mikrofon als auch die Kamera verwenden möchten, übergeben Sie "video, audio":

<video autoplay></video>

<script>
  var onFailSoHard = function(e) {
    console.log('Reeeejected!', e);
  };

  // Not showing vendor prefixes.
  navigator.getUserMedia('video, audio', function(localMediaStream) {
    var video = document.querySelector('video');
    video.src = window.URL.createObjectURL(localMediaStream);

    // Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
    // See crbug.com/110938.
    video.onloadedmetadata = function(e) {
      // Ready to go. Do some stuff.
    };
  }, onFailSoHard);
</script>

OK. Was passiert hier also? Die Medienerfassung ist ein ideales Beispiel für die Zusammenarbeit der neuen HTML5-APIs. Sie funktioniert gemeinsam mit den anderen HTML5-Elementen <audio> und <video>. Beachten Sie, dass wir weder ein src-Attribut festlegen, noch <source>-Elemente für das <video>-Element einschließen. Statt einem Video eine URL für eine Mediendatei zuzuweisen, weisen wir eine Blob-URL von einem LocalMediaStream-Objekt zu, das die Webcam repräsentiert.

Ich lege für das <video> außerdem autoplay fest, sonst friert es im ersten Frame ein. Das Hinzufügen von controls funktioniert auch wie erwartet.

Hinweis: In Chrome gibt es einen Fehler, durch den die Übergabe von ausschließlich "audio" nicht funktioniert: crbug.com/112367. Ich konnte <audio> auch in Opera nicht zum Funktionieren bringen.

Sowohl Opera als auch Chrome implementieren verschiedene Versionen der Spezifikation. Dadurch wird die praktische Verwendung ein wenig "schwieriger" als es letztendlich sein wird.

In Chrome:

Dieses Snippet funktioniert in Chrome 18+ und wird in about:flags aktiviert:

navigator.webkitGetUserMedia('audio, video', function(localMediaStream) {
  var video = document.querySelector('video');
  video.src = window.webkitURL.createObjectURL(localMediaStream);
}, onFailSoHard);

In Opera:

Die Opera-Entwickler-Builds richten sich gegen eine aktualisierte Version der Spezifikation. Dieses Snippet funktioniert in Opera:

navigator.getUserMedia({audio: true, video: true}, function(localMediaStream) {
  video.src = localMediaStream;
}, onFailSoHard);

Die hauptsächlichen Unterschiede lauten:

  • getUserMedia() ist ohne Präfix.
  • Als erstes Argument wird ein Objekt statt einer Stringliste übergeben.
  • video.src wird direkt auf das LocalMediaStream-Objekt festgelegt statt auf eine Blob-URL. Ich habe erfahren, dass Opera schließlich eine Aktualisierung vornehmen wird, so dass eine Blob-URL erforderlich wird.

Bei beiden:

Wenn Sie an einer Lösung interessiert sind, die zwar sehr instabil ist, aber browserübergreifend funktioniert, versuchen Sie Folgendes:

var video = document.querySelector('video');

if (navigator.getUserMedia) {
  navigator.getUserMedia({audio: true, video: true}, function(stream) {
    video.src = stream;
  }, onFailSoHard);
} else if (navigator.webkitGetUserMedia) {
  navigator.webkitGetUserMedia('audio, video', function(stream) {
    video.src = window.webkitURL.createObjectURL(stream);
  }, onFailSoHard);
} else {
  video.src = 'somevideo.webm'; // fallback.
}

Überprüfen Sie unbedingt das gUM Shield von Mike Taylor und Mike Robinson. Es "normalisiert" die Inkonsistenzen zwischen Browser-Implementierungen.

Sicherheit

Künftig werden Browser vielleicht eine Infoleiste anzeigen, die getUserMedia() aufruft. So könnten die Nutzer den Zugriff auf ihre Kamera/ihr Mikrofon erlauben oder verweigern. Die Spezifikation ist leider bei der Sicherheit sehr zurückhaltend. Aktuell implementiert niemand eine Berechtigungsleiste.

Fallback anbieten

Nutzer, die keine Unterstützung für getUserMedia() haben, können auf eine vorhandene Videodatei zurückgreifen, falls das API nicht unterstützt wird oder wenn die Abfrage aus irgendeinem Grund fehlschlägt:

// Not showing vendor prefixes or code that works cross-browser:

function fallback(e) {
  video.src = 'fallbackvideo.webm';
}

function success(stream) {
  video.src = window.URL.createObjectURL(stream);
}

if (!navigator.getUserMedia) {
  fallback();
} else {
  navigator.getUserMedia({video: true}, success, fallback);
}

Einfache Demo

Screenshots erstellen

Die <canvas> API ctx.drawImage(video, 0, 0)-Methode macht das Zeichnen von <video>-Frames in <canvas> kinderleicht. Jetzt, da wir eine Video-Eingabe über getUserMedia() haben, ist es natürlich genauso einfach, eine Photo Booth-Anwendung mit Videos in Echtzeit zu erstellen:

<video autoplay></video>
<img src="">
<canvas style="display:none;"></canvas>

var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var localMediaStream = null;

function snapshot() {
  if (localMediaStream) {
    ctx.drawImage(video, 0, 0);
    // "image/webp" works in Chrome 18. In other browsers, this will fall back to image/png.
    document.querySelector('img').src = canvas.toDataURL('image/webp');
  }
}

video.addEventListener('click', snapshot, false);

// Not showing vendor prefixes or code that works cross-browser.
navigator.getUserMedia({video: true}, function(stream) {
  video.src = window.URL.createObjectURL(stream);
  localMediaStream = stream;
}, onFailSoHard);

Effekte anwenden

CSS-Filter

CSS-Filter werden aktuell in WebKit-Nightly-Versionen und in Chrome 18+ unterstützt.

Anhand von CSS-Filtern können Sie dem <video> bei der Aufnahme ein paar raue Effekte hinzufügen:

<style>
video {
  width: 307px;
  height: 250px;
  background: rgba(255,255,255,0.5);
  border: 1px solid #ccc;
}
.grayscale {
  +filter: grayscale(1);
}
.sepia {
  +filter: sepia(1);
}
.blur {
  +filter: blur(3px);
}
...
</style>

<video autoplay></video>

<script>
var idx = 0;
var filters = ['grayscale', 'sepia', 'blur', 'brightness', 'contrast', 'hue-rotate',
               'hue-rotate2', 'hue-rotate3', 'saturate', 'invert', ''];

function changeFilter(e) {
  var el = e.target;
  el.className = '';
  var effect = filters[idx++ % filters.length]; // loop through filters.
  if (effect) {
    el.classList.add(effect);
  }
}

document.querySelector('video').addEventListener('click', changeFilter, false);
</script>

Klicken Sie auf das Video, um durch die CSS-Filter zu wechseln.

WebGL-Texturen

Ein besonders beeindruckender Einsatzbereich für die Videoaufnahme ist die Darstellung von Live-Aufnahmen als WebGL-Textur. Da ich absolut nichts über WebGL weiß (außer dass es klasse ist), schlage ich vor, Sie sehen sich Jerome Etiennes Anleitung und Demo an. Hier wird erläutert, wie Sie mit getUserMedia() und Three.js Live-Videos in WebGL darstellen.

getUserMedia mit dem Web Audio API nutzen

In diesem Abschnitt werden künftige Verbesserungen und Erweiterungen für das aktuelle API besprochen.

Ein Traum von mir ist die Integration von AutoTune im Browser ausschließlich mit Open-Web-Technologie! Von dieser Möglichkeit sind wir gar nicht so weit entfernt. Wir nutzen bereits getUserMedia() für die Mikrofoneingabe. Jetzt brauchen wir nur noch das Web Audio API für die Echtzeit-Effekte – fertig! Die Integration dieser beiden Elemente fehlt hierbei noch (crbug.com/112404). Aber es gibt bereits einen vorläufigen Vorschlag für deren Umsetzung.

Die Weiterleitung der Mikrofoneingabe an das Web Audio API könnte künftig wie folgt aussehen:

var context = new window.webkitAudioContext();

navigator.webkitGetUserMedia({audio: true}, function(stream) {
  var microphone = context.createMediaStreamSource(stream);
  var filter = context.createBiquadFilter();

  // microphone -> filter -> destination.
  microphone.connect(filter);
  filter.connect(context.destination);
}, onFailSoHard);

Wenn Sie getUserMedia() in Verbindung mit dem Web Audio API sehen möchten, markieren Sie crbug.com/112404.

Fazit

Im Allgemeinen kann man sagen, dass der Gerätezugriff im Web ein schwieriges Unterfangen ist. Viele Nutzer haben es versucht; nur wenige waren dabei erfolgreich. Die meisten der anfänglichen Ideen gelangten niemals an die Öffentlichkeit.

Das eigentliche Problem ist, dass sich das Sicherheitsmodell des Internets sehr stark von der tatsächlichen Welt unterscheidet. Schließlich möchte man wahrscheinlich eher nicht, dass Max Mustermann beliebig Zugriff auf die eigene Videokamera hat. Das ist wirklich ein schwieriges Problem.

Überbrückungs-Frameworks, wie PhoneGap tragen dazu bei, neue Möglichkeiten zu schaffen. Sie sind jedoch nur der Anfang und bieten eine vorübergehende Lösung für ein grundlegendes Problem. Damit Webanwendungen mit ihren Desktopentsprechungen mithalten können, benötigen wir Zugriff auf native Geräte.

getUserMedia() ist nur die erste Zugriffswelle auf neue Gerätetypen. Ich hoffe, dass wir bald mehr davon sehen!

Zusätzliche Ressourcen

Demos

Comments

0