Start · Sprachen · JavaScript · Referenz · MediaSource.endOfStream()

MediaSource.endOfStream()

Methode

Signalisiert der <code>MediaSource</code>, dass keine weiteren Mediendaten folgen, und setzt <code>readyState</code> auf <code>"ended"</code>, optional mit Fehlerangabe.

seit JavaScript Baseline: alle modernen Browser (MSE); iOS Safari Kategorie: webapi

Signatur

MediaSource.prototype.endOfStream(endOfStreamError)

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

Typ
undefined
Beschreibung
Kein Rückgabewert. Die Wirkung zeigt sich in 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 });
ended

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);
});
Segment fehlgeschlagen: HTTP 404 MediaError-Code: 2

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);
endOfStream() jetzt nicht möglich

// Wichtig · Fallstricke

  • InvalidStateError: wird geworfen, wenn readyState nicht "open" ist oder ein SourceBuffer gerade updating === true hat. Deshalb vorher immer updateend abwarten.
  • Dauer wird gekürzt: Beim regulären Ende setzt der Browser duration auf das Ende der gepufferten Daten. Eine vorher von Hand gesetzte, größere Dauer geht dabei verloren.
  • Wieder öffnen: Ein späteres appendBuffer() oder remove() setzt readyState zurück auf "open" (Ereignis sourceopen). Danach muss man endOfStream() 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 window bzw. MediaSource.isTypeSupported() prüfen.