Media Source Extensions (MSE) sind eine JavaScript API, mit der Sie Streams für die Wiedergabe von Audio- oder Videosegmenten erstellen können. Auch wenn es in diesem Artikel nicht behandelt wird, ist es erforderlich, dass du mit MSE vertraut bist, wenn du Videos in deine Website einbetten möchtest, die Folgendes tun:
- Adaptives Streaming, also die Anpassung an Gerätefunktionen und Netzwerkbedingungen,
- Adaptive Teilung, z. B. Anzeigenbereitstellung
- Zeitverschiebung
- Kontrolle über Leistung und Downloadgröße
Sie können sich MSE quasi als eine 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
zum Einspeisen des Medienelements. - Ein
fetch()
- oder XHR-Aufruf zum Abrufen von Mediendaten in einemResponse
-Objekt. - Ein Aufruf von
Response.arrayBuffer()
, umMediaSource.SourceBuffer
zu erfassen.
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 Dinge aus den bisherigen Erläuterungen verstehen können, können Sie jetzt aufhören zu lesen. Eine ausführlichere Erläuterung erhaltet ihr im Folgenden. Ich werde diese Kette anhand eines einfachen MSE-Beispiels durchgehen. Mit jedem Build-Schritt wird Code zum vorherigen Schritt hinzugefügt.
Hinweis zur Übersichtlichkeit
Enthält dieser Artikel alle wichtigen Informationen zum Abspielen von Medien auf einer Webseite? Nein, sie soll Ihnen lediglich helfen, komplizierteren Code zu verstehen, den Sie an anderer Stelle finden könnten. Zum besseren Verständnis wird in diesem Dokument vieles vereinfacht und ausgeschlossen. Wir denken, dass wir damit durchkommen können, da wir auch die Verwendung einer Bibliothek wie dem Shaka-Player von Google empfehlen. Ich werde immer wieder darauf hinweisen, wo ich das Ganze absichtlich vereinfachen werde.
Einige Dinge, die nicht behandelt werden
Ich möchte hier ein paar Dinge, die ich nicht behandeln werde, - und zwar in keiner bestimmten Reihenfolge -.
- Wiedergabesteuerung. Sie erhalten diese kostenlos, da wir die HTML5-Elemente
<audio>
und<video>
verwenden. - Fehlerbehandlung –
Zur Verwendung in Produktionsumgebungen
Bei der Verwendung von MSE-bezogenen APIs in der Produktionsumgebung würde ich Folgendes empfehlen:
- Bevor Sie diese APIs aufrufen, sollten Sie alle Fehlerereignisse oder API-Ausnahmen bearbeiten und
HTMLMediaElement.readyState
undMediaSource.readyState
prüfen. Diese Werte können sich ändern, bevor verknüpfte Ereignisse gesendet werden. - Achten Sie darauf, dass die vorherigen
appendBuffer()
- undremove()
-Aufrufe nicht noch ausgeführt werden. Prüfen Sie dazu den booleschen WertSourceBuffer.updating
, bevor Siemode
,timestampOffset
,appendWindowStart
oderappendWindowEnd
vonSourceBuffer
aktualisieren oderappendBuffer()
oderremove()
inSourceBuffer
aufrufen. - Achten Sie bei allen
SourceBuffer
-Instanzen, die zuMediaSource
hinzugefügt wurden, darauf, dass keiner derupdating
-Werte wahr ist, bevor SieMediaSource.endOfStream()
aufrufen oderMediaSource.duration
aktualisieren. - Wenn der Wert von
MediaSource.readyState
ended
ist, führen Aufrufe wieappendBuffer()
undremove()
oder das Festlegen vonSourceBuffer.mode
oderSourceBuffer.timestampOffset
dazu, dass dieser Wert inopen
übergeht. Sie sollten also auf die Verarbeitung mehrerersourceopen
-Ereignisse vorbereitet sein. - Bei der Verarbeitung von
HTMLMediaElement error
-Ereignissen kann der Inhalt vonMediaError.message
nützlich sein, um die Ursache des Fehlers zu ermitteln. Dies gilt insbesondere für Fehler, die in Testumgebungen schwer reproduziert werden können.
MediaSource-Instanz an Medienelement anhängen
Wie bei vielen Dingen in der Webentwicklung heutzutage beginnen Sie mit der Funktionserkennung. Rufen Sie als Nächstes ein Medienelement ab, entweder ein <audio>
- oder <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.');
}
Es mag etwas seltsam erscheinen, dass ein MediaSource
-Objekt an ein src
-Attribut übergeben werden kann. Normalerweise sind es Strings, können aber auch Blobs sein.
Wenn Sie sich eine Seite mit eingebetteten Medien ansehen und deren Medienelement untersuchen, werden Sie verstehen, was ich meine.
Ist die MediaSource-Instanz bereit?
URL.createObjectURL()
ist selbst synchron, verarbeitet den Anhang jedoch asynchron. Dies führt zu einer geringfügigen Verzögerung, bevor Sie mit der MediaSource
-Instanz etwas unternehmen 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. Sie kann einen der folgenden Werte haben:
closed
: Die InstanzMediaSource
ist mit keinem Medienelement verknüpft.open
: Die InstanzMediaSource
ist an ein Medienelement angehängt und kann Daten empfangen oder empfangen.ended
: Die InstanzMediaSource
ist an ein Medienelement angehängt und alle Daten wurden an dieses Element übergeben.
Das direkte Abfragen dieser Optionen kann sich negativ auf die Leistung auswirken. Glücklicherweise löst MediaSource
auch Ereignisse aus, wenn sich readyState
ändert, insbesondere sourceopen
, sourceclosed
, sourceended
. In meinem Beispiel verwende ich das sourceopen
-Ereignis, um mir mitzuteilen, wann das Video abgerufen und gepuffert 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>
Wie Sie sehen, habe ich auch revokeObjectURL()
genannt. Ich weiß, dass dies zu früh erscheint, aber ich kann dies jederzeit tun, nachdem das Attribut src
des Medienelements mit einer MediaSource
-Instanz verbunden ist. Durch den Aufruf dieser Methode werden keine Objekte gelöscht. Er ermöglicht der Plattform, die automatische Speicherbereinigung zu einem geeigneten Zeitpunkt auszuführen, weswegen ich sie sofort aufrufe.
SourceBuffer erstellen
Jetzt ist es an der Zeit, das SourceBuffer
zu erstellen. Dies ist das Objekt, das die Daten zwischen Medienquellen und Medienelementen verbindet. Ein SourceBuffer
muss spezifisch für den Typ der Mediendatei sein, die Sie laden.
In der Praxis erreichen Sie dies, indem Sie addSourceBuffer()
mit dem entsprechenden Wert aufrufen. Beachten Sie, dass der String des MIME-Typs im Beispiel unten einen MIME-Typ und zwei Codecs enthält. Dies ist ein MIME-String für eine Videodatei, der jedoch separate Codecs für die Video- und Audioteile der Datei verwendet.
In Version 1 der MSE-Spezifikation können User-Agents unterscheiden, ob sowohl ein MIME-Typ als auch ein Codec erforderlich sind. Einige User-Agents benötigen zwar keine Voraussetzung, lassen aber nur den MIME-Typ zu. Einige User-Agents, beispielsweise Chrome, benötigen einen Codec für MIME-Typen, die ihre Codecs nicht selbst beschreiben. Anstatt zu versuchen, alles zu klären, ist es besser, einfach beide 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, werden Sie viele finden, die Mediendateien mit XHR abrufen. Ich verwende die Fetch API und das zurückgegebene Promise-Objekt, um noch innovativer zu werden. In Safari funktioniert das aber nicht ohne fetch()
-Polyfill.
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 mit Produktionsqualität verfügt über dieselbe Datei in mehreren Versionen, um verschiedene Browser zu unterstützen. Es könnten separate Dateien für Audio und Video verwendet werden, damit Audio je nach Spracheinstellungen ausgewählt werden kann.
Realer Code hätte außerdem mehrere Kopien von Mediendateien mit unterschiedlichen Auflösungen, sodass er an unterschiedliche Gerätefunktionen und Netzwerkbedingungen angepasst werden könnte. Eine solche Anwendung ist in der Lage, Videos mithilfe von Bereichsanfragen oder Segmenten in Blöcken zu laden und abzuspielen. Dies ermöglicht die Anpassung an die Netzwerkbedingungen, während Medien wiedergegeben werden. Vielleicht haben Sie schon die Begriffe DASH oder HLS gehört. Eine ausführliche Diskussion dieses Themas wird in dieser Einführung nicht behandelt.
Antwortobjekt verarbeiten
Der Code sieht fast fertig aus, aber die Medien werden nicht abgespielt. Wir müssen Mediendaten aus dem Response
-Objekt in das SourceBuffer
-Objekt abrufen.
In der Regel werden Daten vom Antwortobjekt an die MediaSource
-Instanz übergeben, indem ein ArrayBuffer
-Objekt aus dem Antwortobjekt abgerufen und an das SourceBuffer
-Objekt übergeben wird. Rufen Sie zuerst response.arrayBuffer()
auf, das ein Promise an den Puffer zurückgibt. In meinem Code habe ich dieses Promise an eine zweite then()
-Klausel übergeben und an die SourceBuffer
angehängt.
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, rufen Sie MediaSource.endOfStream()
auf. Dadurch wird MediaSource.readyState
in ended
geändert und das Ereignis sourceended
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, Sie haben etwas über Media Source Extensions erfahren.
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);
});
}