Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

Aufnehmen von Fotos mit getUserMedia()

Dieser Artikel zeigt, wie Sie navigator.mediaDevices.getUserMedia() verwenden, um auf die Kamera eines Computers oder Mobiltelefons mit getUserMedia()-Unterstützung zuzugreifen und ein Foto damit aufzunehmen.

getUserMedia-basierte Bildaufnahme-App — links haben wir einen Videostream von einer Webcam und einen Fotoaufnahme-Button, rechts das Standbild-Ergebnis der Fotoaufnahme

Sie können auch direkt zum Demo springen, wenn Sie mögen.

Das HTML-Markup

Unsere HTML-Oberfläche hat zwei Hauptarbeitsbereiche: das Stream- und Aufnahme-Panel sowie das Präsentations-Panel. Jedes davon wird nebeneinander in seinem eigenen <div> dargestellt, um Styling und Steuerung zu erleichtern. Es gibt ein <button>-Element (permissions-button), das wir später in JavaScript verwenden können, um es dem Benutzer zu ermöglichen, Kameraerlaubnisse pro Gerät mit getUserMedia() zu erlauben oder zu blockieren.

Die Box auf der linken Seite enthält zwei Komponenten: ein <video>-Element, das den Stream von navigator.mediaDevices.getUserMedia() erhält, und einen <button>, um die Videoaufnahme zu starten. Dies ist unkompliziert, und wir werden sehen, wie es zusammenpasst, wenn wir zum JavaScript-Code kommen.

html
<div class="camera">
  <video id="video">Video stream not available.</video>
  <button id="start-button">Capture photo</button>
</div>

Als nächstes haben wir ein <canvas>-Element, in dem die aufgenommenen Frames gespeichert, möglicherweise auf irgendeine Weise bearbeitet und dann in eine Bilddatei konvertiert werden. Dieses Canvas wird durch Styling mit display: none verborgen, um das Bildschirmlayout nicht zu stören — der Benutzer muss diese Zwischenstufe nicht sehen.

Wir haben auch ein <img>-Element, in das wir das Bild zeichnen werden — dies ist die endgültige Darstellung, die dem Benutzer gezeigt wird.

html
<canvas id="canvas"></canvas>
<div class="output">
  <img id="photo" alt="The screen capture will appear in this box." />
</div>

Der JavaScript-Code

Werfen wir nun einen Blick auf den JavaScript-Code. Wir werden ihn in einige leicht verständliche Abschnitte aufteilen, um die Erklärung zu erleichtern.

Initialisierung

Wir beginnen mit dem Einrichten verschiedener Variablen, die wir verwenden werden.

js
const width = 320; // We will scale the photo width to this
let height = 0; // This will be computed based on the input stream

let streaming = false;

const video = document.getElementById("video");
const canvas = document.getElementById("canvas");
const photo = document.getElementById("photo");
const startButton = document.getElementById("start-button");
const allowButton = document.getElementById("permissions-button");

Diese Variablen sind:

width

Unabhängig von der Größe des eingehenden Videos skalieren wir das resultierende Bild auf eine Breite von 320 Pixeln.

height

Die Ausgabehöhe des Bildes wird basierend auf width und dem Seitenverhältnis des Streams berechnet.

streaming

Gibt an, ob derzeit ein aktiver Videostream läuft oder nicht.

video

Eine Referenz auf das <video>-Element.

canvas

Eine Referenz auf das <canvas>-Element.

photo

Eine Referenz auf das <img>-Element.

startButton

Eine Referenz auf das <button>-Element, das zum Starten der Aufnahme verwendet wird.

allowButton

Eine Referenz auf das <button>-Element, das zum Steuern dient, ob die Seite auf Geräte zugreifen kann oder nicht.

Den Mediastream abrufen

Die nächste Aufgabe besteht darin, den Mediastream abzurufen: Wir definieren einen Event-Listener, der MediaDevices.getUserMedia() aufruft und einen Videostream (ohne Audio) anfordert, wenn der Benutzer auf den Button "Kamera erlauben" klickt. Dies gibt ein Versprechen zurück, an das wir Erfolgs- und Fehler-Callbacks anhängen:

js
allowButton.addEventListener("click", () => {
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: false })
    .then((stream) => {
      video.srcObject = stream;
      video.play();
    })
    .catch((err) => {
      console.error(`An error occurred: ${err}`);
    });
});

Das Erfolgs-Callback erhält ein stream-Objekt als Eingabe, das als Quelle für unser <video>-Element gesetzt wird. Sobald der Stream mit dem <video>-Element verlinkt ist, starten wir es, indem wir HTMLMediaElement.play() aufrufen.

Das Fehler-Callback wird aufgerufen, wenn das Öffnen des Streams nicht funktioniert. Dies passiert zum Beispiel, wenn keine kompatible Kamera angeschlossen ist oder der Benutzer den Zugriff verweigert hat.

Auf das Abspielen des Videos hören

Nach dem Aufruf von HTMLMediaElement.play() auf dem <video> gibt es eine (hoffentlich kurze) Zeitspanne, bevor der Videostream zu fließen beginnt. Um ein Blockieren bis dahin zu vermeiden, fügen wir dem video-Element einen Event-Listener für das canplay-Event hinzu, das ausgeliefert wird, wenn die Videowiedergabe tatsächlich beginnt. Zu diesem Zeitpunkt wurden alle Eigenschaften im video-Objekt basierend auf dem Format des Streams konfiguriert.

js
video.addEventListener("canplay", (ev) => {
  if (!streaming) {
    height = video.videoHeight / (video.videoWidth / width);

    video.setAttribute("width", width);
    video.setAttribute("height", height);
    canvas.setAttribute("width", width);
    canvas.setAttribute("height", height);
    streaming = true;
  }
});

Dieses Callback tut nichts, es sei denn, es ist das erste Mal, dass es aufgerufen wird; dies wird durch Überprüfen des Werts unserer streaming-Variable getestet, die beim ersten Ausführen dieser Methode false ist.

Wenn dies tatsächlich der erste Lauf ist, setzen wir die Höhe des Videos basierend auf dem Größendifferenz zwischen der tatsächlichen Größe des Videos, video.videoWidth, und der Breite, in der wir es rendern werden, width.

Schließlich werden die width und height sowohl des Videos als auch des Canvas entsprechend einander angepasst, indem Element.setAttribute() auf jedes der beiden Eigenschaften der beiden Elemente angewendet wird und Breiten und Höhen entsprechend festgelegt werden. Schließlich setzen wir die streaming-Variable auf true, um zu verhindern, dass dieser Setup-Code versehentlich erneut ausgeführt wird.

Klicks auf den Button behandeln

Um jedes Mal, wenn der Benutzer auf den startButton klickt, ein Standbild aufzunehmen, müssen wir dem Button einen Event-Listener hinzufügen, der aufgerufen wird, wenn das click-Event ausgelöst wird:

js
startButton.addEventListener("click", (ev) => {
  takePicture();
  ev.preventDefault();
});

Diese Methode ist unkompliziert: Sie ruft die takePicture()-Funktion auf, die im Abschnitt Einen Frame aus dem Stream aufnehmen unten definiert ist, und ruft dann Event.preventDefault() auf das empfangene Event auf, um zu verhindern, dass der Klick mehr als einmal verarbeitet wird.

Das Fotofeld leeren

Das Leeren des Fotofeldes beinhaltet das Erstellen eines Bildes, um es dann in ein Format umzuwandeln, das vom <img>-Element verwendet werden kann, das das zuletzt aufgenommene Bild anzeigt. Dieser Code sieht so aus:

js
function clearPhoto() {
  const context = canvas.getContext("2d");
  context.fillStyle = "#aaaaaa";
  context.fillRect(0, 0, canvas.width, canvas.height);

  const data = canvas.toDataURL("image/png");
  photo.setAttribute("src", data);
}

clearPhoto();

Wir beginnen damit, eine Referenz auf das versteckte <canvas>-Element zu erhalten, das wir für das Offscreen-Rendering verwenden. Als Nächstes setzen wir das fillStyle auf #aaaaaa (ein ziemlich helles Grau) und füllen das gesamte Canvas mit dieser Farbe, indem wir fillRect() aufrufen.

Zuletzt in dieser Funktion konvertieren wir das Canvas in ein PNG-Bild und rufen photo.setAttribute() auf, um unsere aufgenommene Standbild-Box anzuzeigen.

Einen Frame aus dem Stream aufnehmen

Es gibt eine letzte Funktion zu definieren, die den Kern der ganzen Übung bildet: die takePicture()-Funktion, deren Aufgabe es ist, den aktuell angezeigten Videoframe aufzunehmen, in eine PNG-Datei zu konvertieren und im aufgenommenen Framefeld anzuzeigen. Der Code sieht so aus:

js
function takePicture() {
  const context = canvas.getContext("2d");
  if (width && height) {
    canvas.width = width;
    canvas.height = height;
    context.drawImage(video, 0, 0, width, height);

    const data = canvas.toDataURL("image/png");
    photo.setAttribute("src", data);
  } else {
    clearPhoto();
  }
}

Wie immer, wenn wir mit dem Inhalt eines Canvas arbeiten müssen, beginnen wir damit, den 2D-Zeichenkontext für das versteckte Canvas abzurufen.

Dann, wenn sowohl die Breite als auch die Höhe ungleich Null sind (was bedeutet, dass zumindest potenziell gültige Bilddaten vorhanden sind), setzen wir die Breite und Höhe des Canvas auf die des aufgenommenen Frames und rufen dann drawImage() auf, um den aktuellen Videoframe in den Kontext zu zeichnen und das gesamte Canvas mit dem Framebild zu füllen.

Hinweis: Dies nutzt die Tatsache aus, dass die HTMLVideoElement-Schnittstelle für jede API, die ein HTMLImageElement als Parameter akzeptiert, wie ein HTMLImageElement aussieht, wobei der aktuelle Frame des Videos als Inhalt des Bildes präsentiert wird.

Sobald das Canvas das aufgenommene Bild enthält, konvertieren wir es in das PNG-Format, indem wir HTMLCanvasElement.toDataURL() darauf aufrufen; schließlich rufen wir photo.setAttribute() auf, um unsere aufgenommene Standbild-Box anzuzeigen.

Wenn kein gültiges Bild verfügbar ist (das heißt, die width und height sind beide 0), löschen wir den Inhalt der aufgenommenen Frame-Box, indem wir clearPhoto() aufrufen.

Demo

Klicken Sie auf "Kamera erlauben", um ein Eingabegerät auszuwählen und der Seite den Zugriff auf die Kamera zu erlauben. Sobald das Video gestartet ist, können Sie auf "Foto aufnehmen" klicken, um ein Standbild aus dem Stream aufzunehmen, das als Bild auf das Canvas rechts gezeichnet wird:

Spaß mit Filtern

Da wir Bilder von der Webcam des Benutzers erfassen, indem wir Frames aus einem <video>-Element übernehmen, können wir lustige CSS-filter-Effekte mit Filtern auf das Video anwenden. Diese Filter reichen von einfach (das Bild schwarzweiß machen) bis komplex (Gaußsche Unschärfen und Farbtonrotation).

css
#video {
  filter: grayscale(100%);
}

Damit die Video-Filter auf das Foto angewendet werden, benötigt die takePicture()-Funktion die folgenden Änderungen.

js
function takePicture() {
  const context = canvas.getContext("2d");
  if (width && height) {
    canvas.width = width;
    canvas.height = height;

    // Get the computed CSS filter from the video element.
    // For example, it might return "grayscale(100%)"
    const videoStyles = window.getComputedStyle(video);
    const filterValue = videoStyles.getPropertyValue("filter");

    // Apply the filter to the canvas drawing context.
    // If there's no filter (i.e., it returns "none"), default to "none".
    context.filter = filterValue !== "none" ? filterValue : "none";

    context.drawImage(video, 0, 0, width, height);

    const dataUrl = canvas.toDataURL("image/png");
    photo.setAttribute("src", dataUrl);
  } else {
    clearPhoto();
  }
}

Sie können mit diesem Effekt spielen, indem Sie zum Beispiel die Style Editor von Firefox Developer Tools verwenden; Details dazu finden Sie unter Bearbeiten von CSS-Filtern.

Verwenden bestimmter Geräte

Sie können, falls erforderlich, die Menge der zulässigen Videoquellen auf ein bestimmtes Gerät oder eine Gruppe von Geräten beschränken. Um dies zu tun, rufen Sie MediaDevices.enumerateDevices auf. Wenn das Versprechen mit einem Array von MediaDeviceInfo-Objekten erfüllt wird, die die verfügbaren Geräte beschreiben, finden Sie die Geräte, die Sie erlauben möchten, und geben Sie die entsprechenden deviceId oder deviceIds im MediaTrackConstraints-Objekt an, das an getUserMedia() übergeben wird.

Siehe auch