Aufnehmen eines Media-Elements
Während der Artikel zur Verwendung der MediaStream Recording API die Nutzung des MediaRecorder
-Interfaces zur Aufnahme eines MediaStream
, der von einem Hardwaregerät generiert und von navigator.mediaDevices.getUserMedia()
zurückgegeben wird, demonstriert, können Sie auch ein HTML-Media-Element (nämlich <audio>
oder <video>
) als Quelle für den aufzuzeichnenden MediaStream
verwenden. In diesem Artikel betrachten wir ein Beispiel, das genau das tut.
Beispiel für die Aufnahme eines Media-Elements
HTML
Beginnen wir mit den wichtigsten Teilen des HTML-Codes. Es gibt noch ein wenig mehr, aber das ist eher informativ als Teil des Kernbetriebs der App.
<div class="left">
<div id="startButton" class="button">Start Recording</div>
<h2>Preview</h2>
<video id="preview" width="160" height="120" autoplay muted></video>
</div>
Wir präsentieren unsere Hauptschnittstelle in zwei Spalten. Links befindet sich eine Start-Taste und ein <video>
-Element, das die Video-Vorschau zeigt; dies ist das Video, das die Kamera des Benutzers sieht. Beachten Sie, dass das autoplay
-Attribut verwendet wird, damit das Video sofort angezeigt wird, wenn der Stream von der Kamera zu laufen beginnt, und das muted
-Attribut spezifiziert ist, um sicherzustellen, dass der Ton vom Mikrofon des Benutzers nicht auf seine Lautsprecher ausgegeben wird, was eine unangenehme Rückkopplungsschleife verursachen könnte.
<div class="right">
<div id="stopButton" class="button">Stop Recording</div>
<h2>Recording</h2>
<video id="recording" width="160" height="120" controls></video>
<a id="downloadButton" class="button">Download</a>
</div>
Rechts sehen wir eine Stop-Taste und das <video>
-Element, das zur Wiedergabe des aufgezeichneten Videos verwendet wird. Beachten Sie, dass das Wiedergabefeld nicht auf Autoplay gesetzt ist (sodass die Wiedergabe nicht startet, sobald Medien ankommen) und dass es mit controls
ausgestattet ist, was dem Benutzer ermöglicht, Steuerungen zum Abspielen, Pausieren usw. anzuzeigen.
Unter dem Wiedergabeelement befindet sich eine Schaltfläche zum Herunterladen des aufgezeichneten Videos.
Schauen wir uns nun den JavaScript-Code an; hier passiert schließlich der Großteil der Aktionen!
Einrichten globaler Variablen
Wir beginnen mit der Festlegung einiger globaler Variablen, die wir benötigen werden.
let preview = document.getElementById("preview");
let recording = document.getElementById("recording");
let startButton = document.getElementById("startButton");
let stopButton = document.getElementById("stopButton");
let downloadButton = document.getElementById("downloadButton");
let logElement = document.getElementById("log");
let recordingTimeMS = 5000;
Die meisten davon sind Referenzen auf Elemente, mit denen wir arbeiten müssen. Die letzte, recordingTimeMS
, ist auf 5000 Millisekunden (5 Sekunden) eingestellt; sie gibt die Länge der Videos an, die wir aufnehmen werden.
Dienstfunktionen
Als nächstes erstellen wir einige Dienstfunktionen, die später verwendet werden.
function log(msg) {
logElement.innerText += `${msg}\n`;
}
Die log()
-Funktion wird verwendet, um Textzeichenfolgen an ein <div>
auszugeben, damit wir Informationen mit dem Benutzer teilen können. Nicht sehr hübsch, aber es erfüllt für unsere Zwecke seinen Zweck.
function wait(delayInMS) {
return new Promise((resolve) => setTimeout(resolve, delayInMS));
}
Die wait()
-Funktion gibt ein neues Promise
zurück, das sich auflöst, wenn die angegebene Anzahl von Millisekunden verstrichen ist. Sie funktioniert, indem sie eine Pfeilfunktion verwendet, die setTimeout()
aufruft, wobei der Auflösungs-Handler des Promise als Timeout-Handler-Funktion angegeben wird. Dadurch können wir die Promise-Syntax verwenden, wenn wir mit Timeouts arbeiten, was sehr nützlich beim Verketten von Promises sein kann, wie wir später sehen werden.
Starten der Medienaufnahme
Die startRecording()
-Funktion behandelt den Start des Aufnahmevorgangs:
function startRecording(stream, lengthInMS) {
let recorder = new MediaRecorder(stream);
let data = [];
recorder.ondataavailable = (event) => data.push(event.data);
recorder.start();
log(`${recorder.state} for ${lengthInMS / 1000} seconds…`);
let stopped = new Promise((resolve, reject) => {
recorder.onstop = resolve;
recorder.onerror = (event) => reject(event.name);
});
let recorded = wait(lengthInMS).then(() => {
if (recorder.state === "recording") {
recorder.stop();
}
});
return Promise.all([stopped, recorded]).then(() => data);
}
startRecording()
nimmt zwei Eingabeparameter entgegen: einen MediaStream
, von dem aufgenommen werden soll, und die Länge in Millisekunden der Aufnahme. Wir zeichnen immer nicht mehr als die angegebene Anzahl von Millisekunden auf, auch wenn die Medien vorher stoppen, beendet MediaRecorder
die Aufnahme automatisch ebenfalls.
- Zuerst erstellen wir den
MediaRecorder
, der die Aufnahme des Eingabe-streams
verarbeitet. data
ist ein Array, das anfänglich leer ist und dieBlob
s der Mediendaten enthält, die unseremondataavailable
-Ereignishandler übergeben werden.- Die
ondataavailable
-Zuweisung richtet den Handler für dasdataavailable
-Ereignis ein. Die empfangene Veranstaltung enthält in ihrerdata
-Eigenschaft einBlob
, das die Mediendaten enthält. Der Ereignishandler fügt dasBlob
demdata
-Array hinzu. - Wir starten den Aufnahmeprozess, indem wir
recorder.start()
aufrufen und eine Nachricht an das Log ausgeben, die den aktualisierten Zustand des Recorders und die Anzahl der Sekunden angibt, für die aufgenommen wird. - Wir erstellen ein neues
Promise
, benanntstopped
, das aufgelöst wird, wenn deronstop
-Ereignishandler vonMediaRecorder
aufgerufen wird und abgewiesen wird, wenn seinonerror
-Ereignishandler aufgerufen wird. Der Ablehnungshandler erhält als Eingabe den Namen des aufgetretenen Fehlers. - Wir erstellen ein weiteres neues
Promise
, benanntrecorded
, das aufgelöst wird, wenn die angegebene Anzahl von Millisekunden verstrichen ist. Bei Auflösung wirdMediaRecorder
gestoppt, falls es noch aufnimmt. - Schließlich verwenden wir
Promise.all
, um ein neuesPromise
zu erstellen, das erfüllt wird, wenn beidePromises
(stopped
undrecorded
) aufgelöst wurden. Sobald dies aufgelöst ist, wird das Arraydata
vonstartRecording()
an seinen Aufrufer zurückgegeben.
Beenden des Eingabestreams
Die stop()
-Funktion beendet den Eingabemedienstream:
function stop(stream) {
stream.getTracks().forEach((track) => track.stop());
}
Dies wird erreicht, indem MediaStream.getTracks()
aufgerufen und forEach()
verwendet wird, um MediaStreamTrack.stop()
für jeden Track im Stream aufzurufen.
Abrufen eines Eingabestreams und Einrichten des Recorders
Nun schauen wir auf das komplizierteste Stück Code in diesem Beispiel: unseren Ereignishandler für Klicks auf die Starttaste:
startButton.addEventListener(
"click",
() => {
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
})
.then((stream) => {
preview.srcObject = stream;
downloadButton.href = stream;
preview.captureStream =
preview.captureStream || preview.mozCaptureStream;
return new Promise((resolve) => {
preview.onplaying = resolve;
});
})
.then(() => startRecording(preview.captureStream(), recordingTimeMS))
.then((recordedChunks) => {
let recordedBlob = new Blob(recordedChunks, { type: "video/webm" });
recording.src = URL.createObjectURL(recordedBlob);
downloadButton.href = recording.src;
downloadButton.download = "RecordedVideo.webm";
log(
`Successfully recorded ${recordedBlob.size} bytes of ${recordedBlob.type} media.`,
);
})
.catch((error) => {
if (error.name === "NotFoundError") {
log("Camera or microphone not found. Can't record.");
} else {
log(error);
}
});
},
false,
);
Wenn ein click
-Ereignis auftritt, geschieht Folgendes:
-
MediaDevices.getUserMedia
wird aufgerufen, um einen neuenMediaStream
anzufordern, der sowohl Video- als auch Audiotracks enthält. Dies ist der Stream, den wir aufnehmen werden. -
Wenn das von
getUserMedia()
zurückgegebene Promise aufgelöst wird, wird die EigenschaftsrcObject
des Vorschau-<video>
-Elements auf den Eingabestream gesetzt, was dazu führt, dass das von der Kamera des Benutzers aufgenommene Video im Vorschaufenster angezeigt wird. Da das<video>
-Element stummgeschaltet ist, wird der Ton nicht abgespielt. Der Link der "Download"-Schaltfläche wird dann ebenfalls auf den Stream gesetzt. Danach arrangieren wir, dasspreview.captureStream()
preview.mozCaptureStream()
aufruft, sodass unser Code in Firefox funktioniert, in dem dieHTMLMediaElement.captureStream()
-Methode ein Präfix hat. Dann wird ein neuesPromise
erstellt und zurückgegeben, das aufgelöst wird, wenn die Vorschau des Videos zu spielen beginnt. -
Wenn die Vorschau des Videos zu spielen beginnt, wissen wir, dass es Medien gibt, die aufgezeichnet werden können. Daher reagieren wir, indem wir die zuvor erstellte
startRecording()
-Funktion aufrufen und den Vorschau-Video-Stream (als Quelle der aufzunehmenden Medien) undrecordingTimeMS
als die Anzahl von Millisekunden der aufzuzeichnenden Medien übergeben. Wie zuvor erwähnt, gibtstartRecording()
einPromise
zurück, dessen Auflösungshandler (empfängt ein Array vonBlob
-Objekten, die die aufgezeichneten Medien-Daten enthalten) beim Abschluss der Aufnahme aufgerufen wird. -
Der Auflösungshandler des Aufnahmeprozesses erhält ein Array von Medien-
Blob
s, lokal bekannt alsrecordedChunks
. Das Erste, was wir tun, ist die Chunks in ein einzigesBlob
mit dem MIME-Typ"video/webm"
zu verschmelzen, indem wir die Tatsache nutzen, dass derBlob()
-Konstruktor Arrays von Objekten zu einem Objekt zusammenfügt. Dann wirdURL.createObjectURL()
verwendet, um eine URL zu erstellen, die auf das Blob verweist; dies wird dann zum Wert dessrc
-Attributs des aufgezeichneten Videowiedergabeelements (damit Sie das Video aus dem Blob abspielen können) sowie zum Ziel des Download-Link der Schaltfläche gemacht.Dann wird das
download
-Attribut des Download-Buttons gesetzt. Während dasdownload
-Attribut ein Boolean sein kann, können Sie es auch auf eine Zeichenkette setzen, um einen Namen für die heruntergeladene Datei zu verwenden. Indem wir dasdownload
-Attribut des Download-Links auf "RecordedVideo.webm" setzen, teilen wir dem Browser mit, dass beim Klicken auf die Schaltfläche eine Datei namens"RecordedVideo.webm"
heruntergeladen werden soll, deren Inhalt das aufgezeichnete Video ist. -
Die Größe und der Typ der aufgezeichneten Medien werden im Logbereich unter den beiden Videos und der Download-Schaltfläche ausgegeben.
-
Der
catch()
-Block für alle Promises gibt den Fehler durch Aufrufen unsererlog()
-Funktion im Logbereich aus.
Behandeln der Stop-Taste
Das letzte Code-Fragment fügt einen Handler für das click
-Ereignis auf der Stop-Taste mit addEventListener()
hinzu:
stopButton.addEventListener(
"click",
() => {
stop(preview.srcObject);
},
false,
);
Dies ruft die zuvor behandelte stop()
-Funktion auf.
Ergebnis
Wenn alles zusammen mit dem Rest des HTML und dem oben nicht gezeigten CSS kombiniert wird, sieht es so aus und funktioniert so:
Sie können dieses Beispiel auch im Playground mit der "Play"-Schaltfläche öffnen, die es Ihnen ermöglicht, den kombinierten Code zu betrachten, einschließlich der oben verborgenen Teile, da diese nicht entscheidend für die Erklärung sind, wie die APIs verwendet werden.