Aufzeichnen eines Medienelements
Während der Artikel "Using the MediaStream Recording API" die Verwendung der MediaRecorder
-Schnittstelle zum Erfassen eines von einem Hardwaregerät erzeugten MediaStream
demonstriert, wie er von navigator.mediaDevices.getUserMedia()
bereitgestellt wird, können Sie auch ein HTML-Medienelement (nämlich <audio>
oder <video>
) als Quelle des aufzuzeichnenden MediaStream
verwenden. In diesem Artikel sehen wir uns ein Beispiel an, das genau das tut.
Beispiel zur Aufzeichnung eines Medienelements
HTML
Beginnen wir mit den wesentlichen Teilen des HTML. Es gibt noch etwas mehr, aber das dient nur informativen Zwecken und ist nicht Teil der Kernfunktionalität der Anwendung.
<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>
Unsere Hauptschnittstelle präsentieren wir in zwei Spalten. Auf der linken Seite befindet sich eine Starttaste und ein <video>
-Element, das die Video-Vorschau anzeigt; dies ist das Video, das die Kamera des Benutzers sieht. Beachten Sie, dass das Attribut autoplay
verwendet wird, sodass das Video sofort angezeigt wird, sobald der Stream von der Kamera eintrifft, und dass das Attribut muted
angegeben ist, um sicherzustellen, dass der Ton des Mikrofons des Benutzers nicht auf deren Lautsprecher ausgegeben wird, was zu einer unschönen Rückkopplungsschleife führen 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 Stopptaste und das <video>
-Element, das zur Wiedergabe des aufgezeichneten Videos verwendet wird. Beachten Sie, dass das Wiedergabefeld kein Autoplay eingestellt hat (damit die Wiedergabe nicht sofort gestartet wird, sobald Medien eintreffen) und dass es controls
gesetzt hat, was anzeigt, dass es die Benutzersteuerungen zum Abspielen, Anhalten und dergleichen anzeigt.
Unter dem Wiedergabeelement befindet sich eine Schaltfläche zum Herunterladen des aufgezeichneten Videos.
Nun sehen wir uns den JavaScript-Code an; hier geschieht schließlich der Großteil der Aktionen!
Einrichtung 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 zu Elementen, mit denen wir arbeiten müssen. Die letzte, recordingTimeMS
, ist auf 5000 Millisekunden (5 Sekunden) eingestellt; das gibt die Länge der Videos an, die wir aufzeichnen werden.
Hilfsfunktionen
Als Nächstes erstellen wir einige Hilfsfunktionen, die später verwendet werden.
function log(msg) {
logElement.innerText += `${msg}\n`;
}
Die log()
-Funktion wird verwendet, um Textzeichenfolgen in ein <div>
auszugeben, damit wir Informationen mit dem Benutzer teilen können. Nicht sehr hübsch, aber für unsere Zwecke ausreichend.
function wait(delayInMS) {
return new Promise((resolve) => setTimeout(resolve, delayInMS));
}
Die wait()
-Funktion gibt ein neues Promise
zurück, das aufgelöst wird, nachdem die angegebene Anzahl von Millisekunden vergangen ist. Sie funktioniert, indem sie eine Pfeilfunktion verwendet, die setTimeout()
aufruft und den Auflösungs-Handler des Promise als die Timeout-Handler-Funktion angibt. Das lässt uns die Promise-Syntax bei der Verwendung von Timeouts nutzen, was sehr nützlich sein kann, wenn Promises verkettet werden sollen, wie wir später sehen werden.
Start der Medienaufzeichnung
Die Funktion startRecording()
übernimmt 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: ein MediaStream
, von dem aus aufgenommen werden soll, und die Länge der Aufnahme in Millisekunden. Wir zeichnen immer nur die angegebene Anzahl von Millisekunden auf, obwohl MediaRecorder
das Aufnehmen automatisch stoppt, wenn die Medien vor Ablauf der Zeit stoppen.
- Wir erstellen zuerst den
MediaRecorder
, der die Aufnahme des Eingabestreams verwaltet. data
ist ein Array, das zu Beginn leer ist und dieBlob
s der Mediendaten enthält, die unseremondataavailable
Event-Handler zur Verfügung gestellt werden.- Die
ondataavailable
-Zuweisung richtet den Handler für dasdataavailable
-Ereignis ein. Die empfangenendata
-Eigenschaft des Ereignisses ist einBlob
, das die Mediendaten enthält. Der Ereignishandler fügt dasBlob
demdata
-Array hinzu. - Wir starten den Aufnahmeprozess, indem wir
recorder.start()
aufrufen, und geben eine Nachricht im Protokoll aus, die den aktualisierten Zustand des Recorders und die Anzahl der Sekunden anzeigt, die er aufzeichnen wird. - Wir erstellen ein neues
Promise
mit dem Namenstopped
, das aufgelöst wird, wenn deronstop
-Event-Handler desMediaRecorder
aufgerufen wird, und abgelehnt wird, wenn seinonerror
-Event-Handler aufgerufen wird. Der Ablehnungshandler erhält als Eingabe den Namen des aufgetretenen Fehlers. - Wir erstellen ein weiteres neues
Promise
mit dem Namenrecorded
, das aufgelöst wird, wenn die angegebene Anzahl von Millisekunden vergangen ist. Bei der Auflösung stoppt es denMediaRecorder
, falls dieser noch aufnimmt. - Schließlich verwenden wir
Promise.all
, um ein neuesPromise
zu erstellen, das erfüllt wird, wenn beidePromise
s (stopped
undrecorded
) aufgelöst wurden. Sobald dies aufgelöst ist, wird das Arraydata
vonstartRecording()
an seinen Aufrufer zurückgegeben.
Stoppen des Eingabestreams
Die Funktion stop()
stoppt die Eingabemedien:
function stop(stream) {
stream.getTracks().forEach((track) => track.stop());
}
Dies geschieht, indem MediaStream.getTracks()
aufgerufen wird, und forEach()
verwendet wird, um MediaStreamTrack.stop()
auf jedem Track im Stream aufzurufen.
Abrufen eines Eingabestreams und Einrichten des Recorders
Sehen wir uns nun das komplizierteste Stück Code in diesem Beispiel an: 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, passiert Folgendes:
-
MediaDevices.getUserMedia
wird aufgerufen, um einen neuenMediaStream
anzufordern, der sowohl Video- als auch Audiotracks enthält. Das ist der Stream, den wir aufzeichnen werden. -
Wenn das von
getUserMedia()
zurückgegebene Promise aufgelöst wird, wird diesrcObject
-Eigenschaft des Vorschau-<video>
-Elements auf den Eingabestream gesetzt, wodurch das von der Kamera des Benutzers erfasste Video im Vorschaufeld angezeigt wird. Da das<video>
-Element stummgeschaltet ist, wird der Ton nicht abgespielt. Der Link der "Herunterladen"-Schaltfläche wird dann ebenfalls auf den Stream gesetzt. Dann arrangieren wir, dasspreview.captureStream()
preview.mozCaptureStream()
aufruft, damit unser Code in Firefox funktioniert, auf dem die MethodeHTMLMediaElement.captureStream()
ein Präfix hat. Anschließend wird ein neuesPromise
erstellt und zurückgegeben, das aufgelöst wird, wenn das Vorschauvideo zu spielen beginnt. -
Wenn das Vorschauvideo zu spielen beginnt, wissen wir, dass es Medien gibt, die aufgezeichnet werden können. Daher antworten wir, indem wir die zuvor erstellte Funktion
startRecording()
aufrufen und den Vorschaustream (als Quellmedien, die aufgenommen werden sollen) undrecordingTimeMS
als die Anzahl der Millisekunden, die aufgenommen werden sollen, übergeben. Wie bereits erwähnt, gibtstartRecording()
einPromise
zurück, dessen Auflösungshandler aufgerufen wird (und als Eingabe ein Array vonBlob
-Objekten mit den aufgezeichneten Mediendatenstücken erhält), wenn die Aufnahme abgeschlossen ist. -
Der Auflösungshandler des Aufnahmeprozesses erhält als Eingabe ein Array von Mediendaten-
Blob
s, lokal bekannt alsrecordedChunks
. Das Erste, was wir tun, ist, die Stücke zu einem einzigenBlob
zusammenzuführen, dessen MIME-Typ"video/webm"
ist, indem wir die Tatsache nutzen, dass derBlob()
-Konstruktor Arrays von Objekten zu einem Objekt verkettet. Dann wirdURL.createObjectURL()
verwendet, um eine URL zu erstellen, die auf das Blob verweist; diese wird dann zum Wert dessrc
-Attributs (damit Sie das Video aus dem Blob abspielen können) des Wiedergabefeldes des aufgezeichneten Videos sowie zum Ziel des Links der Download-Schaltfläche gemacht.Dann wird das
download
-Attribut der Download-Schaltfläche gesetzt. Während dasdownload
-Attribut ein Boolean sein kann, können Sie es auch auf eine Zeichenfolge setzen, die als Name für die heruntergeladene Datei verwendet werden soll. Durch das Setzen desdownload
-Attributs des Download-Links auf "RecordedVideo.webm" teilen wir dem Browser mit, dass beim Klicken der Schaltfläche eine Datei namens "RecordedVideo.webm" heruntergeladen werden soll, deren Inhalt das aufgezeichnete Video ist. -
Die Größe und der Typ des aufgezeichneten Mediums werden an den Protokollbereich unterhalb der beiden Videos und der Download-Schaltfläche ausgegeben.
-
Das
catch()
für allePromise
s gibt den Fehler durch einen Aufruf unsererlog()
-Funktion an den Protokollbereich aus.
Handhabung der Stopptaste
Der letzte Codeabschnitt fügt einen Handler für das click
-Ereignis auf der Stopptaste hinzu, indem addEventListener()
verwendet wird:
stopButton.addEventListener(
"click",
() => {
stop(preview.srcObject);
},
false,
);
Dies ruft die zuvor behandelte stop()
-Funktion auf.
Ergebnis
Wenn alles zusammengefügt wird, einschließlich des restlichen HTML und des oben nicht gezeigten CSS, sieht es so aus und funktioniert wie folgt:
Sie können die vollständige Demo hier ansehen und die Entwicklertools Ihres Browsers verwenden, um die Seite zu inspizieren und den gesamten Code anzusehen, einschließlich der Teile, die oben ausgeblendet sind, da sie nicht kritisch für die Erklärung der Verwendung der APIs sind.
Siehe auch
- Media Capture and Streams API Landing Page
MediaDevices.getUserMedia()