Aufnahme von Standbildern 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.
Sie können auch direkt zum Demo springen, wenn Sie möchten.
Das HTML-Markup
Unsere HTML-Oberfläche hat zwei Hauptbetriebssektionen: das Strom- und Aufnahmepanel und das Präsentationspanel.
Jede dieser Sektionen wird nebeneinander in ihrem eigenen <div>
präsentiert, um das Styling und die Kontrolle zu erleichtern.
Es gibt ein <button>
-Element (permissions-button
), das wir später in JavaScript verwenden können, um dem Benutzer zu erlauben, die Kameraerlaubnisse für jedes Gerät mit getUserMedia()
zuzulassen oder zu blockieren.
Das Feld auf der linken Seite enthält zwei Komponenten: ein <video>
-Element, das den Strom von navigator.mediaDevices.getUserMedia()
empfangen wird, und ein <button>
, um die Videoaufnahme zu starten. Dies ist einfach, und wir werden sehen, wie es zusammenpasst, wenn wir in den JavaScript-Code eintauchen.
<div class="camera">
<video id="video">Video stream not available.</video>
<button id="start-button">Capture photo</button>
</div>
Weiter haben wir ein <canvas>
-Element, in das die aufgenommenen Frames gespeichert, möglicherweise auf irgendeine Weise manipuliert und dann in eine Ausgabebilddatei umgewandelt werden. Diese Leinwand wird durch Styling mit display: none
ausgeblendet, um den Bildschirm nicht zu überladen — 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 Anzeige, die dem Benutzer gezeigt wird.
<canvas id="canvas"></canvas>
<div class="output">
<img id="photo" alt="The screen capture will appear in this box." />
</div>
Der JavaScript-Code
Sehen wir uns nun den JavaScript-Code an. Wir werden ihn in ein paar leicht erklärbare Teile aufteilen.
Initialisierung
Wir beginnen mit der Einrichtung verschiedener Variablen, die wir verwenden werden.
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 werden wir das resultierende Bild auf 320 Pixel Breite skalieren.
height
-
Die Ausgabebreite des Bildes wird unter Berücksichtigung der
width
und des Seitenverhältnisses des Stroms berechnet. streaming
-
Gibt an, ob gerade ein aktiver Videostrom 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. -
Eine Referenz auf das
<button>
-Element, das zum Auslösen der Aufnahme verwendet wird. -
Eine Referenz auf das
<button>
-Element, das steuert, ob die Seite auf Geräte zugreifen kann oder nicht.
Holen des Medienstroms
Die nächste Aufgabe besteht darin, den Medienstrom zu erhalten: Wir definieren einen Ereignis-Listener, der MediaDevices.getUserMedia()
aufruft und einen Videostream (ohne Audio) anfordert, wenn der Benutzer auf die Schaltfläche "Kamera zulassen" klickt.
Es gibt ein Versprechen zurück, an das wir Erfolgs- und Fehler-Callbacks anhängen:
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}`);
});
});
Der Erfolgs-Callback erhält ein stream
-Objekt als Eingabe, das als Quelle unseres <video>
-Elements festgelegt wird.
Sobald der Strom mit dem <video>
-Element verknüpft ist, starten wir es, indem wir HTMLMediaElement.play()
aufrufen.
Der Fehler-Callback wird aufgerufen, wenn das Öffnen des Stroms nicht funktioniert. Dies geschieht beispielsweise, wenn keine kompatible Kamera angeschlossen ist oder der Benutzer den Zugriff verweigert hat.
Auf das Starten des Videos hören
Nach dem Aufrufen von HTMLMediaElement.play()
auf dem <video>
, vergeht (hoffentlich nur eine kurze) Zeit, bevor der Videostream zu fließen beginnt. Damit wir nicht blockieren, bis das passiert, fügen wir einen Ereignis-Listener für das video
-Element für das canplay
-Ereignis hinzu, das ausgeliefert wird, wenn die Videowiedergabe tatsächlich beginnt. Zu diesem Zeitpunkt sind alle Eigenschaften im video
-Objekt basierend auf dem Format des Stroms konfiguriert.
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;
}
},
false,
);
Dieser Callback tut nichts, es sei denn, es ist das erste Mal, dass er aufgerufen wird; dies wird getestet, indem der Wert unserer streaming
-Variable überprüft wird, der beim ersten Aufruf dieser Methode false
ist.
Wenn dies tatsächlich der erste Lauf ist, setzen wir die Höhe des Videos basierend auf dem Größenunterschied zwischen der tatsächlichen Größe des Videos, video.videoWidth
, und der Breite, mit der wir es rendern, width
.
Schließlich werden die width
und height
sowohl des Videos als auch der Leinwand aufeinander abgestimmt, indem Element.setAttribute()
für jede der beiden Eigenschaften auf jedem Element aufgerufen wird, und Breiten und Höhen entsprechend gesetzt. Schließlich setzen wir die streaming
-Variable auf true
, um zu verhindern, dass wir dieses Setup versehentlich erneut ausführen.
Klicken auf den Button handhaben
Um bei jedem Klick auf den startButton
ein Standbild aufzunehmen, müssen wir der Schaltfläche einen Ereignis-Listener hinzufügen, der beim Klicken aufgerufen wird:
startButton.addEventListener(
"click",
(ev) => {
takePicture();
ev.preventDefault();
},
false,
);
Diese Methode ist einfach: Sie ruft die takePicture()
-Funktion auf, die im Abschnitt Ein Frame aus dem Stream aufnehmen unten definiert ist, und ruft dann Event.preventDefault()
auf dem empfangenen Ereignis auf, um zu verhindern, dass der Klick mehr als einmal behandelt wird.
Löschen des Fotokastens
Das Löschen des Fotokastens beinhaltet das Erstellen eines Bildes und dann das Konvertieren in ein Format, das vom <img>
-Element verwendet werden kann, das den zuletzt aufgenommenen Frame anzeigt. Der Code sieht folgendermaßen aus:
function clearPhoto() {
const context = canvas.getContext("2d");
context.fillStyle = "#AAA";
context.fillRect(0, 0, canvas.width, canvas.height);
const data = canvas.toDataURL("image/png");
photo.setAttribute("src", data);
}
clearPhoto();
Wir beginnen mit dem Abrufen einer Referenz auf das versteckte <canvas>
-Element, das wir für das Rendern außerhalb des Bildschirms verwenden. Als nächstes setzen wir das fillStyle
auf #AAA
(ein ziemlich helles Grau) und füllen die gesamte Leinwand mit dieser Farbe, indem wir fillRect()
aufrufen.
Zuletzt in dieser Funktion konvertieren wir die Leinwand in ein PNG-Bild und rufen photo.setAttribute()
auf, um unsere aufgezeichnete Standbildbox das Bild anzeigen zu lassen.
Ein Frame aus dem Stream aufnehmen
Es gibt eine letzte Funktion zu definieren, und dies ist der Punkt der gesamten Übung: die takePicture()
-Funktion, deren Aufgabe es ist, den derzeit angezeigten Videoframe aufzunehmen, in eine PNG-Datei zu konvertieren und im aufgenommenen Framekasten anzuzeigen. Der Code sieht folgendermaßen aus:
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 den Inhalten einer Leinwand arbeiten müssen, beginnen wir damit, den 2D-Zeichenkontext für die versteckte Leinwand zu erhalten.
Dann, wenn die Breite und Höhe beide ungleich Null sind (d.h. es gibt potenziell gültige Bilddaten), setzen wir die Breite und Höhe der Leinwand auf die des aufgenommenen Frames, und rufen dann drawImage()
auf, um den aktuellen Frame des Videos in den Kontext zu zeichnen und die gesamte Leinwand mit dem Framebild zu füllen.
Hinweis:
Dies nutzt die Tatsache, dass die HTMLVideoElement
-Schnittstelle jedem API, das ein HTMLImageElement
als Parameter akzeptiert, wie ein HTMLImageElement
aussieht, wobei der aktuelle Frame des Videos als Inhalt des Bildes präsentiert wird.
Sobald die Leinwand 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, damit unsere aufgezeichnete Standbildbox das Bild anzeigt.
Wenn kein gültiges Bild verfügbar ist (d.h. sowohl die width
als auch die height
sind 0), löschen wir die Inhalte des aufgenommenen Framekastens, 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 startet, können Sie auf "Foto aufnehmen" klicken, um ein Standbild aus dem Stream als auf die Leinwand auf der rechten Seite gezeichnetes Bild aufzunehmen:
Spaß mit Filtern
Da wir Bilder aus der Webcam des Benutzers aufnehmen, indem wir Frames von einem <video>
-Element erfassen, können wir lustige CSS filter
-Effekte auf das Video mit Filtern anwenden. Diese Filter reichen von einfach (das Bild schwarz-weiß machen) bis komplex (Gaussianische Weichzeichnungen und Farbtonrotation).
#video {
filter: grayscale(100%);
}
Damit die Videofilter auf das Foto angewendet werden, benötigt die takePicture()
-Funktion die folgenden Änderungen.
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 beispielsweise die Stil-Editor der Firefox-Entwicklertools verwenden; siehe CSS-Filter bearbeiten für Details, wie man dies tut.
Verwendung spezieller Geräte
Sie können bei Bedarf die Menge der erlaubten Videoquellen auf ein bestimmtes Gerät oder eine bestimmte Menge von Geräten beschränken. Dies erreichen Sie, indem Sie MediaDevices.enumerateDevices
aufrufen. Wenn das Versprechen mit einem Array von MediaDeviceInfo
-Objekten erfüllt wird, die die verfügbaren Geräte beschreiben, finden Sie die, die Sie zulassen möchten, und spezifizieren die entsprechenden deviceId
oder deviceId
s im MediaTrackConstraints
-Objekt, das in getUserMedia()
übergeben wird.