Signatur
Beschreibung
Mit endOfStream() teilt man einer MediaSource mit, dass alle Segmente per SourceBuffer.appendBuffer() angehängt wurden. Der Browser setzt dann readyState auf "ended", löst das Ereignis sourceended aus und berechnet die duration anhand der tatsächlich gepufferten Daten neu. Erst danach kann das <video>- oder <audio>-Element das Ende der Wiedergabe richtig erkennen und ended auslösen.
Gebraucht wird die Methode vor allem bei eigenen Streaming-Umsetzungen (Adaptive Streaming, segmentweises Laden per fetch()). Ohne den Aufruf bleibt der Player am Ende des Puffers einfach im Wartezustand hängen, weil er noch mit weiteren Daten rechnet.
Über den optionalen Parameter kann man auch einen Fehler melden: "network" steht für einen Abbruch beim Laden, "decode" für fehlerhafte Mediendaten. In beiden Fällen bekommt das Medienelement einen passenden MediaError und löst error aus.
Wichtig: Der Aufruf ist nur erlaubt, wenn readyState === "open" ist und kein SourceBuffer gerade aktualisiert wird (updating === false). Deshalb wartet man vorher immer auf das Ereignis updateend.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $endOfStreamError | "network" | "decode" | Optionaler Fehlergrund. Ohne Angabe endet der Stream regulär. "network" meldet einen Netzwerkfehler, "decode" einen Dekodierfehler an das Medienelement. |
Rückgabewert
readyState === "ended" und im Ereignis sourceended.Beispiele
Segmente laden und Stream sauber beenden
const video = document.querySelector('video');
const mime = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
const segmente = ['/media/init.mp4', '/media/seg1.m4s', '/media/seg2.m4s'];
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
// Wartet, bis der SourceBuffer mit dem Anhängen fertig ist
const warteAufUpdate = (sb) =>
new Promise((resolve) => sb.addEventListener('updateend', resolve, { once: true }));
mediaSource.addEventListener('sourceopen', async () => {
const sb = mediaSource.addSourceBuffer(mime);
for (const url of segmente) {
const daten = await (await fetch(url)).arrayBuffer();
sb.appendBuffer(daten);
await warteAufUpdate(sb);
}
// Alle Daten angehängt – jetzt ist der Aufruf zulässig
mediaSource.endOfStream();
console.log(mediaSource.readyState);
}, { once: true });
Netzwerkfehler an den Player melden
const ladeSegment = async (mediaSource, sb, url) => {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
sb.appendBuffer(await res.arrayBuffer());
} catch (err) {
console.warn('Segment fehlgeschlagen:', err.message);
if (mediaSource.readyState === 'open' && !sb.updating) {
// Das Medienelement erhält einen MediaError und löst "error" aus
mediaSource.endOfStream('network');
}
}
};
const video = document.querySelector('video');
video.addEventListener('error', () => {
console.log('MediaError-Code:', video.error.code);
});
Sicherer Aufruf mit Zustandsprüfung und sourceended
const beendeStream = (mediaSource) => {
const beschaeftigt = [...mediaSource.sourceBuffers].some((sb) => sb.updating);
if (mediaSource.readyState !== 'open' || beschaeftigt) {
console.log('endOfStream() jetzt nicht möglich');
return false;
}
mediaSource.endOfStream();
return true;
};
const ms = new MediaSource();
ms.addEventListener('sourceended', () => {
console.log('Stream beendet, Dauer:', ms.duration);
});
// Vor "sourceopen" ist readyState noch "closed"
beendeStream(ms);
// Wichtig · Fallstricke
- InvalidStateError: wird geworfen, wenn
readyStatenicht"open"ist oder einSourceBuffergeradeupdating === truehat. Deshalb vorher immerupdateendabwarten. - Dauer wird gekürzt: Beim regulären Ende setzt der Browser
durationauf das Ende der gepufferten Daten. Eine vorher von Hand gesetzte, größere Dauer geht dabei verloren. - Wieder öffnen: Ein späteres
appendBuffer()oderremove()setztreadyStatezurück auf"open"(Ereignissourceopen). Danach muss manendOfStream()erneut aufrufen. - Fehler-Varianten: Mit
"network"oder"decode"kann die Wiedergabe endgültig abbrechen. Das nur bei echten, nicht behebbaren Fehlern verwenden und nicht für Wiederholungsversuche. - Kompatibilität: MSE funktioniert in allen aktuellen Desktop-Browsern und in Chrome für Android. Auf dem iPhone gibt es MSE erst ab iOS 17.1, und zwar über
ManagedMediaSource. Vorher mit'MediaSource' in windowbzw.MediaSource.isTypeSupported()prüfen.