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 damit ein Foto zu machen.
Sie können auch direkt zum Demo springen, wenn Sie möchten.
Das HTML-Markup
Unser HTML-Interface hat zwei Hauptfunktionsbereiche: das Stream- und Aufnahme-Panel sowie das Präsentations-Panel. Jeder dieser Bereiche wird in einem eigenen <div>
nebeneinander präsentiert, um das Styling und die Kontrolle zu erleichtern.
Das erste Panel links enthält zwei Komponenten: ein <video>
-Element, das den Stream von navigator.mediaDevices.getUserMedia()
empfängt, und einen <button>
, den der Benutzer anklickt, um einen Videorahmen zu erfassen.
<div class="camera">
<video id="video">Video stream not available.</video>
<button id="start-button">Take photo</button>
</div>
Das ist einfach und wir werden sehen, wie es zusammenwirkt, wenn wir in den JavaScript-Code eintauchen.
Als nächstes haben wir ein <canvas>
-Element, in das die erfassten Frames gespeichert, möglicherweise in irgendeiner Weise manipuliert und dann in eine Ausgabebilddatei konvertiert werden. Diese Leinwand wird versteckt, indem sie mit display: none
gestylt wird, 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>
Das sind alle relevanten HTML-Teile. Der Rest ist nur etwas Seitengestaltungsdeko und ein wenig Text mit einem Link zurück zu dieser Seite.
Der JavaScript-Code
Nun lassen Sie uns den JavaScript-Code betrachten. Wir teilen ihn in einige kleine Stücke auf, um es einfacher zu erklären.
Initialisierung
Wir beginnen damit, das gesamte Skript in eine anonyme Funktion zu kapseln, um globale Variablen zu vermeiden, und dann verschiedene Variablen einzurichten, die wir verwenden werden.
(() => {
const width = 320; // We will scale the photo width to this
const height = 0; // This will be computed based on the input stream
const streaming = false;
let video = null;
let canvas = null;
let photo = null;
let startButton = null;
Diese Variablen sind:
width
-
Egal wie groß das eingehende Video ist, wir werden das resultierende Bild so skalieren, dass es 320 Pixel breit ist.
height
-
Die Ausgangshöhe des Bildes wird unter Berücksichtigung der
width
und des Seitenverhältnisses des Streams berechnet. streaming
-
Gibt an, ob derzeit ein aktiver Videostream läuft oder nicht.
video
-
Dies wird eine Referenz auf das
<video>
-Element sein, nachdem die Seite geladen ist. canvas
-
Dies wird eine Referenz auf das
<canvas>
-Element sein, nachdem die Seite geladen ist. photo
-
Dies wird eine Referenz auf das
<img>
-Element sein, nachdem die Seite geladen ist. -
Dies wird eine Referenz auf das
<button>
-Element sein, das zur Auslösung der Erfassung verwendet wird. Wir bekommen es, nachdem die Seite geladen ist.
Die startup() Funktion
Die startup()
Funktion wird ausgeführt, wenn die Seite fertig geladen ist, dank EventTarget.addEventListener
. Die Aufgabe dieser Funktion ist es, den Zugriff auf die Webcam des Benutzers anzufordern, die Ausgabe im <img>
-Element auf einen Standardzustand zu initialisieren und die Event-Listener einzurichten, die benötigt werden, um jeden Videoframe von der Kamera zu empfangen und darauf zu reagieren, wenn der Knopf zum Erfassen eines Bildes geklickt wird.
Elementreferenzen abrufen
Zuerst holen wir Referenzen auf die wichtigen Elemente, die wir zugänglich machen müssen.
function startup() {
video = document.getElementById('video');
canvas = document.getElementById('canvas');
photo = document.getElementById('photo');
startButton = document.getElementById('start-button');
Den Medienstream abrufen
Der nächste Schritt ist, den Medienstream zu bekommen:
navigator.mediaDevices
.getUserMedia({ video: true, audio: false })
.then((stream) => {
video.srcObject = stream;
video.play();
})
.catch((err) => {
console.error(`An error occurred: ${err}`);
});
Hier rufen wir MediaDevices.getUserMedia()
auf und fordern einen Videostream (ohne Audio) an. Es gibt ein Versprechen zurück, an das wir Erfolgs- und Fehler-Callbacks anhängen.
Der Erfolgs-Callback erhält ein stream
Objekt als Eingabe. Es ist die Quelle unseres neuen Streams für das <video>
-Element.
Sobald der Stream mit dem <video>
-Element verbunden ist, starten wir ihn, indem wir HTMLMediaElement.play()
aufrufen.
Der Fehler-Callback wird aufgerufen, wenn das Öffnen des Streams nicht funktioniert. Dies geschieht zum Beispiel, wenn keine kompatible Kamera angeschlossen ist oder der Benutzer den Zugriff verweigert hat.
Auf das Starten des Videos warten
Nach dem Aufruf von HTMLMediaElement.play()
auf dem <video>
gibt es eine (hoffentlich kurze) Zeitspanne, in der sich der Videostream noch nicht fließend bewegt. Um ein Blockieren zu vermeiden, bis das passiert, fügen wir einen Event-Listener zu video
hinzu, um auf das canplay
Ereignis zu hören, das ausgegeben wird, wenn die Videowiedergabe tatsächlich beginnt. Zu diesem Zeitpunkt sind alle Eigenschaften im video
-Objekt basierend auf dem Format des Streams 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 überprüft, indem man den Wert unserer streaming
-Variablen betrachtet, die beim ersten Ausführen dieser Methode false
ist.
Wenn dies tatsächlich das erste Mal ist, berechnen wir die Höhe des Videos basierend auf dem Größenunterschied zwischen der tatsächlichen Größe des Videos, video.videoWidth
, und der Breite, in der wir es rendern möchten, width
.
Schließlich werden die width
und height
sowohl des Videos als auch der Leinwand einander angepasst, indem Element.setAttribute()
aufgerufen wird, um die Breiten und Höhen auf die jeweiligen Attribute jedes Elements zu setzen. Schließlich setzen wir die streaming
-Variable auf true
, um zu verhindern, dass wir diesen Setup-Code versehentlich erneut ausführen.
Klicks auf den Button verarbeiten
Um jedes Mal, wenn der Benutzer auf den startButton
klickt, ein Standbild aufzunehmen, müssen wir einen Event-Listener zum Button hinzufügen, der aufgerufen wird, wenn das click
Ereignis ausgelöst wird:
startButton.addEventListener(
"click",
(ev) => {
takePicture();
ev.preventDefault();
},
false,
);
Diese Methode ist einfach genug: Sie ruft einfach unsere takePicture()
Funktion auf, die unten im Abschnitt Einen Frame aus dem Stream erfassen definiert ist, und dann ruft sie Event.preventDefault()
auf, um zu verhindern, dass der Klick mehr als einmal verarbeitet wird.
Die startup()-Methode abschließen
Es gibt nur noch zwei Zeilen Code in der startup()
-Methode:
clearPhoto();
}
Hier rufen wir die clearPhoto()
-Methode auf, die wir unten im Abschnitt Das Fotofeld leeren beschreiben.
Das Fotofeld leeren
Das Leeren des Fotofeldes umfasst das Erstellen eines Bildes, das dann in ein Format umgewandelt wird, das vom <img>
-Element verwendet werden kann, welches den zuletzt aufgenommenen Frame anzeigt. Dieser Code sieht so 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);
}
Wir beginnen damit, eine Referenz auf das versteckte <canvas>
-Element zu bekommen, das wir für das Offscreen-Rendering verwenden. Als nächstes setzen wir das fillStyle
auf #AAA
(ein recht 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 unser Fotoaufnahmefeld das Bild anzeigen zu lassen.
Einen Frame aus dem Stream erfassen
Es gibt noch eine letzte Funktion zu definieren, und sie ist der Kern der gesamten Übung: die takePicture()
Funktion, deren Aufgabe es ist, den aktuell angezeigten Videoframe zu erfassen, ihn in eine PNG-Datei zu konvertieren und ihn im aufgenommenen Rahmenfeld anzuzeigen. Der Code sieht so 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 (was bedeutet, dass es zumindest potenziell gültige Bilddaten gibt), setzen wir die Breite und Höhe der Leinwand auf die des aufgenommenen Frames, rufen dann drawImage()
auf, um den aktuellen Frame des Videos in den Kontext zu zeichnen, und füllen die gesamte Leinwand mit dem Framebild.
Hinweis:
Dies nutzt aus, dass die HTMLVideoElement
Schnittstelle für jede API, die ein HTMLImageElement
als Parameter akzeptiert, wie ein HTMLImageElement
aussieht, wobei das aktuelle Frame des Videos als Inhalt des Bildes präsentiert wird.
Sobald die Leinwand das erfasste Bild enthält, konvertieren wir es durch Aufruf von HTMLCanvasElement.toDataURL()
darauf in das PNG-Format; schließlich rufen wir photo.setAttribute()
auf, um unser aufgenommenes Standbildfeld das Bild anzeigen zu lassen.
Wenn kein gültiges Bild verfügbar ist (das heißt, die width
und height
sind beide 0), löschen wir den Inhalt des aufgenommenen Rahmenfeldes, indem wir clearPhoto()
aufrufen.
Demo
HTML
<div class="content-area">
<h1>MDN - navigator.mediaDevices.getUserMedia(): Still photo capture demo</h1>
<p>
This example demonstrates how to set up a media stream using your built-in
webcam, fetch an image from that stream, and create a PNG using that image.
</p>
<div class="camera">
<video id="video">Video stream not available.</video>
<button id="start-button">Take photo</button>
</div>
<canvas id="canvas"> </canvas>
<div class="output">
<img id="photo" alt="The screen capture will appear in this box." />
</div>
<p>
Visit our article
<a
href="https://developer.mozilla.org/en-US/docs/Web/API/Media_Capture_and_Streams_API/Taking_still_photos">
Taking still photos with WebRTC
</a>
to learn more about the technologies used here.
</p>
</div>
JavaScript
(() => {
// The width and height of the captured photo. We will set the
// width to the value defined here, but the height will be
// calculated based on the aspect ratio of the input stream.
const width = 320; // We will scale the photo width to this
let height = 0; // This will be computed based on the input stream
// |streaming| indicates whether or not we're currently streaming
// video from the camera. Obviously, we start at false.
let streaming = false;
// The various HTML elements we need to configure or control. These
// will be set by the startup() function.
let video = null;
let canvas = null;
let photo = null;
let startButton = null;
function showViewLiveResultButton() {
if (window.self !== window.top) {
// Ensure that if our document is in a frame, we get the user
// to first open it in its own tab or window. Otherwise, it
// won't be able to request permission for camera access.
document.querySelector(".content-area").remove();
const button = document.createElement("button");
button.textContent = "Open example in new window";
document.body.append(button);
button.addEventListener("click", () =>
window.open(
location.href,
"MDN",
"width=850,height=700,left=150,top=150",
),
);
return true;
}
return false;
}
function startup() {
if (showViewLiveResultButton()) {
return;
}
video = document.getElementById("video");
canvas = document.getElementById("canvas");
photo = document.getElementById("photo");
startButton = document.getElementById("start-button");
navigator.mediaDevices
.getUserMedia({ video: true, audio: false })
.then((stream) => {
video.srcObject = stream;
video.play();
})
.catch((err) => {
console.error(`An error occurred: ${err}`);
});
video.addEventListener(
"canplay",
(ev) => {
if (!streaming) {
height = video.videoHeight / (video.videoWidth / width);
// Firefox currently has a bug where the height can't be read from
// the video, so we will make assumptions if this happens.
if (isNaN(height)) {
height = width / (4 / 3);
}
video.setAttribute("width", width);
video.setAttribute("height", height);
canvas.setAttribute("width", width);
canvas.setAttribute("height", height);
streaming = true;
}
},
false,
);
startButton.addEventListener(
"click",
(ev) => {
takePicture();
ev.preventDefault();
},
false,
);
clearPhoto();
}
// Fill the photo with an indication that none has been captured.
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);
}
// Capture a photo by fetching the current contents of the video
// and drawing it into a canvas, then converting that to a PNG
// format data URL. By drawing it on an offscreen canvas and then
// drawing that to the screen, we can change its size and/or apply
// other changes before drawing it.
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();
}
}
// Set up our event listener to run the startup process
// once loading is complete.
window.addEventListener("load", startup, false);
})();
Spaß mit Filtern
Da wir Bilder von der Kamera des Benutzers erfassen, indem wir Frames aus einem <video>
-Element aufnehmen, können wir sehr einfach Filter und lustige Effekte auf das Video anwenden. Es stellt sich heraus, dass alle CSS-Filter, die Sie auf das Element mit der filter
-Eigenschaft anwenden, das aufgenommene Foto beeinträchtigen. Diese Filter können vom Einfachen (Schwarz-Weiß-Darstellung) bis zum Extremen (Gaußsche Weichzeichner und Farbtonrotation) reichen.
Sie können diesen Effekt mit den Entwicklerwerkzeugen von Firefox im Stil-Editor ausprobieren; siehe CSS-Filter bearbeiten für Details, wie dies zu tun ist.
Verwendung bestimmter Geräte
Bei Bedarf können Sie die erlaubten Videoquellen auf ein bestimmtes Gerät oder eine Gruppe von Geräten beschränken. Dazu rufen Sie MediaDevices.enumerateDevices
auf. Wenn das Versprechen mit einem Array von MediaDeviceInfo
Objekten erfüllt wird, die die verfügbaren Geräte beschreiben, suchen Sie die Geräte, die Sie zulassen möchten, und geben Sie die entsprechenden deviceId
oder deviceId
s im MediaTrackConstraints
Objekt an, das an getUserMedia()
übergeben wird.