Aufzeichnen eines Media-Elements
Während der Artikel zur Verwendung der MediaStream Recording API die Verwendung der MediaRecorder
-Schnittstelle zur Erfassung eines durch ein Hardwaregerät erzeugten MediaStream
demonstriert, wie er von navigator.mediaDevices.getUserMedia()
zurückgegeben wird, können Sie auch ein HTML-Media-Element (nämlich <audio>
oder <video>
) als Quelle des aufzuzeichnenden MediaStream
verwenden. In diesem Artikel betrachten wir ein Beispiel, das genau das tut.
Beispiel für die Aufzeichnung eines Media-Elements
HTML
Lassen Sie uns mit den wichtigsten Teilen des HTML beginnen. Es gibt noch ein wenig mehr als das, aber das ist nur informativ und nicht Teil des Hauptbetriebs 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 ein Startknopf und ein <video>
-Element, das die Videovorschau anzeigt; dies ist das Video, das die Kamera des Benutzers sieht. Beachten Sie, dass das autoplay
-Attribut verwendet wird, sodass das Video sofort angezeigt wird, sobald der Stream von der Kamera eintrifft, und das muted
-Attribut angegeben ist, um sicherzustellen, dass der Ton des Mikrofons des Benutzers nicht über die Lautsprecher ausgegeben wird, was zu einer unschönen Rückkopplungsschleife führen würde.
<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 einen Stop-Knopf 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 beginnt, sobald das Medium eintrifft) und es hat controls
eingestellt, was bedeutet, dass dem Benutzer Steuerelemente zur Verfügung gestellt werden, um Play, Pause und so weiter zu bedienen.
Unter dem Wiedergabeelement befindet sich ein Knopf zum Herunterladen des aufgezeichneten Videos.
Nun schauen wir uns den JavaScript-Code an; hier passiert schließlich das meiste!
Festlegen globaler Variablen
Wir beginnen damit, einige globale Variablen zu erstellen, die wir benötigen.
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) gesetzt; dies 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 an ein <div>
auszugeben, damit wir Informationen mit dem Benutzer teilen können. Nicht sehr hübsch, aber es erledigt den Job für unsere Zwecke.
function wait(delayInMS) {
return new Promise((resolve) => setTimeout(resolve, delayInMS));
}
Die wait()
-Funktion gibt ein neues Promise
zurück, das aufgelöst wird, sobald die angegebene Anzahl von Millisekunden verstrichen ist. Es funktioniert, indem eine Pfeilfunktion verwendet wird, die setTimeout()
aufruft und den Auflösungs-Handler des Promise als Timeout-Handler-Funktion angibt. Dadurch können wir die Promise-Syntax beim Verwenden von Zeitüberschreitungen nutzen, was sehr nützlich sein kann, wenn wir Promises verketten, wie wir später sehen werden.
Start 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 an: ein MediaStream
, von dem aufgenommen werden soll, und die Länge in Millisekunden der zu erstellenden Aufnahme. Wir nehmen nie mehr als die angegebene Anzahl von Millisekunden auf, obwohl, wenn die Medien vorher stoppen, auch der MediaRecorder
die Aufnahme automatisch stoppt.
- Zuerst erstellen wir den
MediaRecorder
, der die Aufnahme des Eingabe-stream
behandelt. data
ist ein Array, das zunächst leer ist und dieBlob
s der Mediendaten enthält, die unseremondataavailable
-Ereignishandler zur Verfügung gestellt werden.- Die
ondataavailable
-Zuweisung richtet den Handler für dasdataavailable
-Ereignis ein. Diedata
-Eigenschaft des empfangenen Ereignisses ist einBlob
, das die Mediendaten enthält. Der Ereignishandler schiebt denBlob
auf dasdata
-Array. - Wir starten den Aufnahmevorgang durch Aufruf von
recorder.start()
und geben eine Nachricht im Log aus, die den aktualisierten Zustand des Recorders und die Anzahl der Sekunden, die er aufnehmen wird, enthält. - Wir erstellen ein neues
Promise
, genanntstopped
, das aufgelöst wird, wenn deronstop
-Ereignishandler desMediaRecorder
aufgerufen wird und das abgelehnt wird, wenn seinonerror
-Ereignishandler aufgerufen wird. Der Ablehnungshandler erhält als Eingabe den Namen des aufgetretenen Fehlers. - Wir erstellen ein weiteres neues
Promise
, genanntrecorded
, das aufgelöst wird, wenn die angegebene Anzahl von Millisekunden verstrichen ist. Nach der Auflösung stoppt es denMediaRecorder
, wenn er 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 stop()
-Funktion stoppt die Eingabemedien:
function stop(stream) {
stream.getTracks().forEach((track) => track.stop());
}
Dies funktioniert, indem MediaStream.getTracks()
aufgerufen wird, um mit forEach()
auf jede Spur im Stream MediaStreamTrack.stop()
aufzurufen.
Abrufen eines Eingabestreams und Einrichten des Recorders
Nun sehen wir uns den kompliziertesten Codeabschnitt in diesem Beispiel an: unseren Ereignishandler für Klicks auf den Startknopf:
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 eintritt, passiert Folgendes:
-
MediaDevices.getUserMedia
wird aufgerufen, um einen neuenMediaStream
anzufordern, der sowohl Video- als auch Audiotracks enthält. Dies 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 aufgenommene Video im Vorschaukasten angezeigt wird. Da das<video>
-Element stummgeschaltet ist, wird der Ton nicht abgespielt. Der Link des "Download"-Knopfes wird dann ebenfalls auf den Stream gesetzt. Dann arrangieren wir, dasspreview.captureStream()
preview.mozCaptureStream()
aufruft, damit unser Code in Firefox funktioniert, bei dem die MethodeHTMLMediaElement.captureStream()
mit einem Präfix versehen ist. Dann wird ein neuesPromise
erstellt, das aufgelöst wird, wenn das Vorschauvideo zu spielen beginnt, und dieses wird zurückgegeben. -
Wenn das Vorschauvideo zu spielen beginnt, wissen wir, dass es Medien zum Aufzeichnen gibt, also reagieren wir, indem wir die zuvor erstellte
startRecording()
-Funktion aufrufen und den Vorschau-Video-Stream (als aufzuzeichnende Medienquelle) undrecordingTimeMS
als Anzahl der Millisekunden, die aufgezeichnet werden sollen, übergeben. Wie zuvor erwähnt, gibtstartRecording()
einPromise
zurück, dessen Auflösungs-Handler (der ein Array vonBlob
-Objekten erhält, das die Teile der aufgezeichneten Mediendaten enthält) aufgerufen wird, sobald die Aufnahme abgeschlossen ist. -
Der Auflösungs-Handler des Aufnahmeprozesses erhält als Eingabe ein Array von Mediendaten-
Blob
s, lokal bekannt alsrecordedChunks
. Das erste, was wir tun, ist, die Teile zu einem einzigenBlob
zusammenzuführen, dessen MIME-Typ"video/webm"
ist, indem wir den Vorteil der Tatsache nutzen, dass derBlob()
-Konstruktor Arrays von Objekten zu einem Objekt zusammenfügt. Dann wirdURL.createObjectURL()
verwendet, um eine URL zu erstellen, die auf den Blob verweist; dies wird dann der Wert dessrc
-Attributs des Wiedergabe-Elements des aufgezeichneten Videos, damit Sie das Video aus dem Blob abspielen können, sowie das Ziel des Download-Buttons.Dann wird das
download
-Attribut des Download-Knopfes gesetzt. Während dasdownload
-Attribut ein Boolean sein kann, können Sie es auch auf eine Zeichenkette setzen, um es als Namen für die heruntergeladene Datei zu verwenden. Indem wir dasdownload
-Attribut des Download-Links auf "RecordedVideo.webm" setzen, sagen wir dem Browser, dass ein Klick auf die Schaltfläche eine Datei namens"RecordedVideo.webm"
herunterlädt, deren Inhalt das aufgezeichnete Video ist. -
Die Größe und der Typ der aufgezeichneten Medien werden im Logbereich unterhalb der beiden Videos und des Download-Knopfes ausgegeben.
-
Das
catch()
für allePromise
s gibt den Fehler im Protokollbereich aus, indem es unserelog()
-Funktion aufruft.
Umgang mit dem Stop-Knopf
Der letzte Codeabschnitt fügt einen Handler für das click
-Ereignis auf dem Stop-Knopf mit addEventListener()
hinzu:
stopButton.addEventListener(
"click",
() => {
stop(preview.srcObject);
},
false,
);
Dies ruft die zuvor behandelte stop()
-Funktion auf.
Ergebnis
Wenn alles mit dem Rest des nicht gezeigten HTML und CSS zusammengefügt wird, sieht es so aus und funktioniert so:
Sie können das vollständige Demo hier ansehen und die Entwicklertools Ihres Browsers verwenden, um die Seite zu inspizieren und sich den gesamten Code anzusehen, einschließlich der Teile, die oben verborgen sind, da sie nicht entscheidend für die Erklärung sind, wie die APIs verwendet werden.
Siehe auch
- Media Capture and Streams API Übersichtsseite
MediaDevices.getUserMedia()