Media Source Extensions (MSE) ist eine JavaScript API, mit der du Streams für die Wiedergabe aus Audio- oder Videosegmenten erstellen kannst. Auch wenn es in diesem Artikel nicht behandelt wird, ist es wichtig, MSE zu verstehen, wenn Sie Videos auf Ihrer Website einbetten möchten, die Folgendes ermöglichen:
- Adaptives Streaming, d. h. Anpassung an die Gerätefunktionen und Netzwerkbedingungen
- Adaptives Splicing, z. B. Anzeigeneinblendung
- Zeitverschiebung
- Leistung und Downloadgröße steuern
Sie können sich MSE fast als Kette vorstellen. Wie in der Abbildung dargestellt, befinden sich zwischen der heruntergeladenen Datei und den Medienelementen mehrere Ebenen.
- Ein
<audio>
- oder<video>
-Element zum Abspielen der Medien. - Eine
MediaSource
-Instanz mit einemSourceBuffer
für das Medienelement. - Ein
fetch()
- oder XHR-Aufruf zum Abrufen von Mediendaten in einemResponse
-Objekt. - Ein Aufruf an
Response.arrayBuffer()
,MediaSource.SourceBuffer
zu füttern.
In der Praxis sieht die Kette so aus:
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function (response) {
return response.arrayBuffer();
})
.then(function (arrayBuffer) {
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}
Wenn Sie die bisherigen Erklärungen verstanden haben, können Sie mit dem Lesen aufhören. Eine ausführlichere Erklärung finden Sie weiter unten. Ich werde diese Kette durchgehen, indem ich ein einfaches MSE-Beispiel erstelle. Mit jedem Build-Schritt wird Code zum vorherigen Schritt hinzugefügt.
Hinweis zur Klarheit
Wird in diesem Artikel alles Wissenswerte zum Abspielen von Medien auf einer Webseite beschrieben? Nein, es soll Ihnen nur dabei helfen, komplizierteren Code zu verstehen, den Sie möglicherweise an anderer Stelle finden. Zur Verdeutlichung werden in diesem Dokument viele Dinge vereinfacht und ausgeschlossen. Wir gehen davon aus, dass dies in Ordnung ist, da wir auch die Verwendung einer Bibliothek wie dem Shaka Player von Google empfehlen. Ich werde immer darauf hinweisen, wenn ich absichtlich vereinfache.
Nicht abgedeckte Fälle
Hier sind einige Dinge, die ich nicht behandeln werde, in keiner bestimmten Reihenfolge:
- Wiedergabesteuerung. Diese erhalten wir kostenlos, da wir die HTML5-Elemente
<audio>
und<video>
verwenden. - Fehlerbehandlung –
Für die Verwendung in Produktionsumgebungen
Im Folgenden findest du einige Empfehlungen für die Produktionsnutzung von MSE-bezogenen APIs:
- Bevor Sie Aufrufe an diese APIs senden, prüfen Sie alle Fehlerereignisse oder API-Ausnahmen und prüfen Sie
HTMLMediaElement.readyState
undMediaSource.readyState
. Diese Werte können sich ändern, bevor die zugehörigen Ereignisse gesendet werden. - Prüfe den booleschen Wert
SourceBuffer.updating
, bevor dumode
,timestampOffset
,appendWindowStart
oderappendWindowEnd
vonSourceBuffer
aktualisierst oderappendBuffer()
oderremove()
aufSourceBuffer
aufrufst, um sicherzustellen, dass keine vorherigenappendBuffer()
- undremove()
-Aufrufe noch laufen. - Achten Sie darauf, dass für alle
SourceBuffer
-Instanzen, die IhrerMediaSource
hinzugefügt wurden, keiner derupdating
-Werte „wahr“ ist, bevor SieMediaSource.endOfStream()
aufrufen oder dieMediaSource.duration
aktualisieren. - Wenn der Wert von
MediaSource.readyState
ended
ist, wird durch Aufrufe wieappendBuffer()
undremove()
oder durch Festlegen vonSourceBuffer.mode
oderSourceBuffer.timestampOffset
der Wert inopen
geändert. Sie sollten also darauf vorbereitet sein, mehreresourceopen
-Ereignisse zu verarbeiten. - Beim Umgang mit
HTMLMediaElement error
-Ereignissen kann der Inhalt vonMediaError.message
hilfreich sein, um die Ursache des Fehlers zu ermitteln, insbesondere bei Fehlern, die in Testumgebungen schwer zu reproduzieren sind.
MediaSource-Instanz an ein Medienelement anhängen
Wie bei vielen anderen Dingen in der Webentwicklung, die heutzutage auch in der Webentwicklung tätig sind, beginnen Sie mit der Funktionserkennung. Rufe als Nächstes ein Medienelement ab, entweder ein <audio>
- oder ein <video>
-Element.
Erstellen Sie abschließend eine Instanz von MediaSource
. Es wird in eine URL umgewandelt und an das Attribut „source“ des Medienelements übergeben.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
// Is the MediaSource instance ready?
} else {
console.log('The Media Source Extensions API is not supported.');
}
Dass ein MediaSource
-Objekt an ein src
-Attribut übergeben werden kann, mag etwas seltsam erscheinen. In der Regel sind es Strings, aber es können auch Blobs sein.
Wenn Sie eine Seite mit eingebetteten Medien und deren Medienelement untersuchen, sehen Sie, was ich meine.
Ist die MediaSource-Instanz bereit?
URL.createObjectURL()
ist selbst synchron, verarbeitet den Anhang jedoch asynchron. Dadurch kann es etwas dauern, bis Sie etwas mit der MediaSource
-Instanz tun können. Glücklicherweise gibt es Möglichkeiten, dies zu testen.
Die einfachste Möglichkeit ist die Verwendung einer MediaSource
-Eigenschaft namens readyState
. Das Attribut readyState
beschreibt die Beziehung zwischen einer MediaSource
-Instanz und einem Medienelement. Es kann einen der folgenden Werte haben:
closed
: DieMediaSource
-Instanz ist an kein Medienelement angehängt.open
: DieMediaSource
-Instanz ist mit einem Medienelement verknüpft und empfängt Daten oder ist bereit, Daten zu empfangen.ended
: DieMediaSource
-Instanz ist an ein Medienelement angehängt und alle zugehörigen Daten wurden an dieses Element übergeben.
Wenn Sie diese Optionen direkt abfragen, kann sich das negativ auf die Leistung auswirken. Glücklicherweise löst MediaSource
auch Ereignisse aus, wenn sich readyState
ändert, insbesondere sourceopen
, sourceclosed
und sourceended
. In meinem Beispiel verwende ich das Ereignis sourceopen
, um anzugeben, wann das Video abgerufen und zwischengespeichert werden soll.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
<strong>mediaSource.addEventListener('sourceopen', sourceOpen);</strong>
} else {
console.log("The Media Source Extensions API is not supported.")
}
<strong>function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
// Create a SourceBuffer and get the media file.
}</strong>
Beachte, dass ich auch revokeObjectURL()
genannt habe. Ich weiß, dass das voreilig erscheint, aber ich kann das jederzeit tun, nachdem das src
-Attribut des Medienelements mit einer MediaSource
-Instanz verbunden ist. Durch den Aufruf dieser Methode werden keine Objekte zerstört. Es ermöglicht der Plattform jedoch, die Garbage Collection zum richtigen Zeitpunkt auszuführen. Deshalb rufe ich sie sofort auf.
SourceBuffer erstellen
Jetzt ist es an der Zeit, die SourceBuffer
zu erstellen. Dieses Objekt ist für die Übertragung von Daten zwischen Medienquellen und Medienelementen verantwortlich. Ein SourceBuffer
muss für den Typ der geladenen Mediendatei spezifisch sein.
In der Praxis können Sie dazu addSourceBuffer()
mit dem entsprechenden Wert aufrufen. Beachte, dass der MIME-Typ-String im folgenden Beispiel einen MIME-Typ und zwei Codecs enthält. Dies ist ein MIME-String für eine Videodatei, verwendet jedoch separate Codecs für die Video- und Audioteile der Datei.
Version 1 der MSE-Spezifikation ermöglicht es, dass User-Agents sich darin unterscheiden, ob sowohl ein MIME-Typ als auch ein Codec erforderlich sind. Einige User-Agents erfordern den MIME-Typ nicht, erlauben ihn aber. Einige User-Agents, z. B. Chrome, benötigen einen Codec für MIME-Typen, die ihre Codecs nicht selbst beschreiben. Anstatt zu versuchen, das alles zu sortieren, ist es besser, beides zu berücksichtigen.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
<strong>
var mime = 'video/webm; codecs="opus, vp09.00.10.08"'; // e.target refers to
the mediaSource instance. // Store it in a variable so it can be used in a
closure. var mediaSource = e.target; var sourceBuffer =
mediaSource.addSourceBuffer(mime); // Fetch and process the video.
</strong>;
}
Mediendatei abrufen
Wenn Sie im Internet nach MSE-Beispielen suchen, finden Sie viele, die Mediendateien mit XHR abrufen. Für ein besseres Ergebnis verwende ich die Fetch API und das Promise, das sie zurückgibt. Wenn Sie dies in Safari versuchen, funktioniert es ohne eine fetch()
-Polyfill nicht.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
<strong>
fetch(videoUrl) .then(function(response){' '}
{
// Process the response object.
}
);
</strong>;
}
Ein Player in Produktionsqualität sollte dieselbe Datei in mehreren Versionen haben, um verschiedene Browser zu unterstützen. Es könnten separate Dateien für Audio und Video verwendet werden, damit Audio basierend auf den Spracheinstellungen ausgewählt werden kann.
Der tatsächliche Code hätte außerdem mehrere Kopien von Mediendateien mit unterschiedlichen Auflösungen, sodass er sich an unterschiedliche Gerätefunktionen und Netzwerkbedingungen anpassen könnte. Eine solche Anwendung kann Videos entweder mithilfe von Bereichsanfragen oder Segmenten in Teilen laden und abspielen. So können sich die Netzwerkbedingungen während der Medienwiedergabe anpassen. Möglicherweise haben Sie die Begriffe DASH oder HLS gehört, zwei Methoden, um dies zu erreichen. Eine vollständige Erläuterung dieses Themas würde den Rahmen dieser Einführung sprengen.
Antwortobjekt verarbeiten
Der Code sieht fast fertig aus, aber die Medien werden nicht abgespielt. Wir müssen Mediendaten vom Response
-Objekt an das SourceBuffer
-Objekt weitergeben.
Normalerweise werden Daten vom Antwortobjekt an die MediaSource
-Instanz übergeben, indem ein ArrayBuffer
aus dem Antwortobjekt abgerufen und an die SourceBuffer
übergeben wird. Rufen Sie zuerst response.arrayBuffer()
auf, um ein Versprechen für den Puffer zurückzugeben. In meinem Code habe ich dieses Promise an eine zweite then()
-Klausel übergeben, wo ich es an SourceBuffer
anfüge.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
<strong>return response.arrayBuffer();</strong>
})
<strong>.then(function(arrayBuffer) {
sourceBuffer.appendBuffer(arrayBuffer);
});</strong>
}
endOfStream() aufrufen
Nachdem alle ArrayBuffers
angehängt wurden und keine weiteren Mediendaten erwartet werden, rufe MediaSource.endOfStream()
auf. Dadurch wird MediaSource.readyState
in ended
geändert und das sourceended
-Ereignis ausgelöst.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
return response.arrayBuffer();
})
.then(function(arrayBuffer) {
<strong>sourceBuffer.addEventListener('updateend', function(e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});</strong>
sourceBuffer.appendBuffer(arrayBuffer);
});
}
Die endgültige Version
Hier ist das vollständige Codebeispiel. Ich hoffe, dass Sie etwas über Erweiterungen für Medienquellen gelernt haben.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function (response) {
return response.arrayBuffer();
})
.then(function (arrayBuffer) {
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}