Dieser Artikel zeigt, wie Sie navigator.mediaDevices.getUserMedia()
verwenden, um auf die Kamera eines Computers oder Mobiltelefons mit Unterstützung für getUserMedia()
zuzugreifen und ein Foto damit aufzunehmen.
Sie können auch direkt zum Demo springen, wenn Sie möchten.
Das HTML-MarkupUnsere HTML-Oberfläche hat zwei Hauptfunktionsbereiche: das Stream- und Erfassungsfeld sowie das Präsentationsfeld. Jeder dieser Bereiche wird nebeneinander in seinem eigenen <div>
präsentiert, um das Styling und die Steuerung zu erleichtern. Es gibt ein <button>
-Element (permissions-button
), das wir später in JavaScript verwenden können, um dem Benutzer zu ermöglichen, Kameraberechtigungen 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()
empfangen wird, und ein <button>
, um die Videokapitur zu starten. Das ist einfach, und wir werden sehen, wie es zusammenpasst, wenn wir den JavaScript-Code betrachten.
body {
font:
1rem "Lucida Grande",
"Arial",
sans-serif;
padding: 0.8rem;
}
button {
display: block;
margin-block: 1rem;
}
#start-button {
position: relative;
margin: auto;
bottom: 32px;
background-color: rgb(0 150 0 / 50%);
border: 1px solid rgb(255 255 255 / 70%);
box-shadow: 0px 0px 1px 2px rgb(0 0 0 / 20%);
font-size: 14px;
color: white;
}
#video,
#photo {
border: 1px solid black;
box-shadow: 2px 2px 3px black;
width: 100%;
height: auto;
}
#canvas {
display: none;
}
.camera,
.output {
display: inline-block;
width: 49%;
height: auto;
}
.output {
vertical-align: top;
}
code {
background-color: lightgrey;
}
<h1>Still photo capture demo</h1>
<p>
This example demonstrates how to use
<code>navigator.mediaDevices.getUserMedia()</code> to set up a media stream
using your webcam or other video device, fetch an image from that stream, and
create a PNG using that image.
</p>
<button id="permissions-button">Allow camera</button>
<p>
ⓘ This example uses the same code as before, but this time, we're adding
a filter effect to the <code><video></code> element using a CSS
<code>filter: grayscale(100%)</code> declaration. We can then check if the
video element has any CSS <code>filter</code> applied, and use the same filter
when drawing to the canvas:
</p>
<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 das die aufgenommenen Frames gespeichert, möglicherweise auf irgendeine Weise manipuliert und dann in eine Ausgabe-Bilddatei konvertiert werden. Dieses Canvas wird verborgen gehalten, indem es mit display: none
gestylt wird, um zu vermeiden, dass der Bildschirm überladen wird â der Benutzer muss diese Zwischenschritte nicht sehen.
Wir haben auch ein <img>
-Element, in das wir das Bild zeichnen werden â dies ist die finale Anzeige, die dem Benutzer angezeigt wird.
<canvas id="canvas"></canvas>
<div class="output">
<img id="photo" alt="The screen capture will appear in this box." />
</div>
Der JavaScript-Code
Lassen Sie uns nun den JavaScript-Code betrachten. Wir werden ihn in ein paar kleinere Abschnitte unterteilen, um ihn leichter zu erklären.
InitialisierungWir beginnen damit, verschiedene Variablen einzurichten, 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 davon, wie groà das eingehende Video ist, skalieren wir das resultierende Bild auf eine Breite von 320 Pixeln.
height
Die Ausgabehöhe des Bildes wird basierend auf der width
und dem Seitenverhältnis des Streams berechnet.
streaming
Gibt an, ob derzeit ein aktiver Videostream läuft.
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 verwendet wird, um die Erfassung auszulösen.
allowButton
Eine Referenz auf das <button>
-Element, das verwendet wird, um zu steuern, ob die Seite auf Geräte zugreifen kann oder nicht.
Die nächste Aufgabe besteht darin, den Medienstream zu erhalten: Wir definieren einen Ereignis-Listener, der MediaDevices.getUserMedia()
aufruft und einen Videostream (ohne Audio) anfordert, wenn der Benutzer auf den "Kamera erlauben"-Button klickt. Es gibt ein Versprechen zurück, an das wir Erfolgs- und Fehler-Rückrufe 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-Rückruf erhält ein stream
-Objekt als Eingabe, das als Quelle für unser <video>
-Element eingestellt wird. Sobald der Stream mit dem <video>
-Element verknüpft ist, starten wir ihn durch einen Aufruf von HTMLMediaElement.play()
.
Der Fehler-Rückruf wird aufgerufen, wenn das Ãffnen des Streams nicht funktioniert. Dies tritt beispielsweise auf, wenn keine kompatible Kamera angeschlossen ist oder der Benutzer den Zugriff verweigert hat.
Hören Sie darauf, dass das Video zu spielen beginntNach dem Aufruf von HTMLMediaElement.play()
auf dem <video>
gibt es eine (hoffentlich kurze) Zeitspanne, die abläuft, bevor der Videostream zu flieÃen beginnt. Um zu vermeiden, dass wir blockieren, bis das passiert, fügen wir video
einen Ereignis-Listener für das canplay
-Ereignis 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.
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 Rückruf tut nichts, es sei denn, es ist das erste Mal, dass er aufgerufen wurde; dies wird getestet, indem der Wert unserer streaming
-Variablen betrachtet wird, die false
ist, das erste Mal, dass diese Methode ausgeführt wird.
Wenn dies tatsächlich das erste Mal 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, bei der wir es rendern werden, width
.
SchlieÃlich werden die width
und height
sowohl des Videos als auch des Canvas so eingestellt, dass sie sich gegenseitig entsprechen, indem Element.setAttribute()
für jede der beiden Eigenschaften auf jedem Element aufgerufen wird und Breiten und Höhen entsprechend eingestellt werden. SchlieÃlich setzen wir die streaming
-Variable auf true
, um zu verhindern, dass wir diesen Initialisierungscode versehentlich erneut ausführen.
Um jedes Mal, wenn der Benutzer auf den startButton
klickt, ein Standbild zu erfassen, müssen wir dem Button einen Ereignis-Listener hinzufügen, der aufgerufen wird, wenn das click
-Ereignis ausgegeben wird:
startButton.addEventListener(
"click",
(ev) => {
takePicture();
ev.preventDefault();
},
false,
);
Diese Methode ist einfach: Sie ruft die takePicture()
-Funktion auf, die im Abschnitt Erfassen eines Frames vom Stream weiter unten definiert ist, und ruft dann Event.preventDefault()
für das empfangene Ereignis auf, um zu verhindern, dass der Klick mehr als einmal verarbeitet wird.
Das Löschen des Fotofeldes besteht darin, ein Bild zu erstellen und es dann in ein Format zu konvertieren, das vom <img>
-Element verwendet werden kann, das das zuletzt erfasste Bild anzeigt. Der Code sieht so aus:
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 unser erfasstes Standbild-Box anzuzeigen.
Es gibt eine letzte Funktion zu definieren, und sie ist der Punkt 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 erfassten Frame-Feld 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 jedes Mal, wenn wir mit dem Inhalt eines Canvas arbeiten müssen, beginnen wir damit, den 2D-Zeichnungskontext für das versteckte Canvas zu erhalten.
Dann, wenn die Breite und die Höhe beide ungleich null sind (was bedeutet, dass potenziell gültige Bilddaten vorliegen), stellen wir die Breite und Höhe des Canvas so ein, dass sie mit denen des erfassten Frames übereinstimmen, und rufen dann drawImage()
auf, um den aktuellen Frame des Videos in den Kontext zu zeichnen und das gesamte Canvas mit dem Frame-Bild zu füllen.
Hinweis: Dies nutzt die Tatsache, 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 dargestellt wird.
Sobald das Canvas das erfasste Bild enthält, konvertieren wir es durch einen Aufruf von HTMLCanvasElement.toDataURL()
in das PNG-Format; schlieÃlich rufen wir photo.setAttribute()
auf, um unser erfasstes Standbild-Box anzuzeigen.
Wenn kein gültiges Bild verfügbar ist (d.h. die width
und height
sind beide 0), löschen wir den Inhalt des erfassten Frame-Feldes, indem wir clearPhoto()
aufrufen.
Klicken Sie auf "Kamera erlauben", um ein Eingabegerät auszuwählen und der Seite zu erlauben, auf die Kamera zuzugreifen. Sobald das Video gestartet ist, können Sie auf "Foto aufnehmen" klicken, um ein Standbild aus dem Stream als Bild aufzunehmen und auf das rechte Canvas zu zeichnen:
Spaà mit FilternDa wir Bilder von der Webcam des Benutzers durch Erfassen von Frames aus einem <video>
-Element aufnehmen, können wir mit Filtern lustige CSS filter
-Effekte auf das Video anwenden. Diese Filter reichen von einfach (Schwarz-WeiÃ-Bild) bis komplex (Gaussian-Weichzeichner 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 zum Beispiel die Firefox-Entwicklungstools' Style-Editor verwenden; sehen Sie CSS-Filter bearbeiten für Einzelheiten, wie das geht.
Verwendung spezifischer GeräteSie können, falls erforderlich, die Menge der zulässigen Videoquellen auf ein bestimmtes Gerät oder eine bestimmte Gruppe von Geräten beschränken. Dazu rufen Sie MediaDevices.enumerateDevices
auf. Wenn das Versprechen mit einem Array von MediaDeviceInfo
-Objekten, die die verfügbaren Geräte beschreiben, erfüllt wird, finden Sie die Geräte, die Sie zulassen möchten, und geben die entsprechenden deviceId
oder deviceId
s im MediaTrackConstraints
-Objekt an, das an getUserMedia()
übergeben wird.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4