Start · Sprachen · JavaScript · Referenz · SourceBuffer.appendBuffer()

SourceBuffer.appendBuffer()

Methode

Hängt Mediasegmentdaten aus einem <code>ArrayBuffer</code>, <code>TypedArray</code> oder <code>DataView</code> asynchron an einen <code>SourceBuffer</code> an.

seit JavaScript Baseline: alle modernen Browser (iOS Safari erst a Kategorie: webapi

Signatur

SourceBuffer.prototype.appendBuffer(source)

Beschreibung

appendBuffer() ist die zentrale Methode der Media Source Extensions (MSE). Damit übergibt man dem Browser rohe Mediadaten, etwa ein Initialisierungssegment oder fragmentierte MP4- bzw. WebM-Mediensegmente. Diese Daten wurden vorher zum Beispiel per fetch() oder über einen WebSocket geladen. So bauen Player wie hls.js, dash.js oder Shaka Player adaptives Streaming (HLS/DASH), Live-Streams oder eigene Pufferstrategien.

Die Methode arbeitet asynchron: Nach dem Aufruf steht SourceBuffer.updating auf true. Nacheinander werden die Ereignisse updatestart, update und updateend ausgelöst. Tritt ein Fehler auf, kommen stattdessen error und dann updateend. Solange updating auf true steht, darf man kein weiteres appendBuffer() oder remove() aufrufen. Deshalb verwaltet man die Segmente in der Praxis in einer Warteschlange und hängt das nächste erst im updateend-Handler an.

Zuerst muss ein Initialisierungssegment angehängt werden (bei fMP4 die Boxen ftyp und moov), danach folgen die Mediensegmente. Die Daten müssen zu dem MIME-Typ und Codec passen, die bei MediaSource.addSourceBuffer() angegeben wurden. Normale, nicht fragmentierte MP4-Dateien werden nicht unterstützt.

Parameter

Name Typ Default Beschreibung
$source Pflicht ArrayBuffer | TypedArray | DataView Die anzuhängenden Mediasegmentdaten, z. B. ein Uint8Array mit einem fMP4- oder WebM-Segment. Bei TypedArray und DataView wird nur der sichtbare Ausschnitt (byteOffset/byteLength) verwendet.

Rückgabewert

Typ
void
Beschreibung
Gibt undefined zurück. Ob das Anhängen fertig ist, erfährt man über das Ereignis updateend, Fehler über das Ereignis error.

Beispiele

Initialisierungs- und Mediensegment nacheinander anhängen

const video = document.querySelector('video');
const mime = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';

if (MediaSource.isTypeSupported(mime)) {
  const mediaSource = new MediaSource();
  video.src = URL.createObjectURL(mediaSource);

  mediaSource.addEventListener('sourceopen', async () => {
    URL.revokeObjectURL(video.src);
    const sb = mediaSource.addSourceBuffer(mime);

    // Hilfsfunktion: appendBuffer als Promise
    const append = (data) => new Promise((resolve, reject) => {
      sb.addEventListener('updateend', resolve, { once: true });
      sb.addEventListener('error', reject, { once: true });
      sb.appendBuffer(data);
    });

    const init = await (await fetch('/media/init.mp4')).arrayBuffer();
    await append(init);

    const seg1 = await (await fetch('/media/seg-1.m4s')).arrayBuffer();
    await append(seg1);

    mediaSource.endOfStream();
    console.log('Gepuffert bis', sb.buffered.end(0), 's');
  });
}
Gepuffert bis 4.004 s

Warteschlange für Segmente aus einem WebSocket

const mediaSource = new MediaSource();
document.querySelector('video').src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', () => {
  const sb = mediaSource.addSourceBuffer('video/webm; codecs="vp9, opus"');
  const queue = [];

  const pump = () => {
    if (!sb.updating && queue.length > 0) {
      sb.appendBuffer(queue.shift());
    }
  };
  sb.addEventListener('updateend', pump);

  const ws = new WebSocket('wss://example.com/live');
  ws.binaryType = 'arraybuffer';
  ws.addEventListener('message', (event) => {
    queue.push(new Uint8Array(event.data));
    pump();
  });
});

QuotaExceededError abfangen und alten Puffer freigeben

const safeAppend = async (sb, video, data) => {
  try {
    sb.appendBuffer(data);
  } catch (err) {
    if (err.name === 'QuotaExceededError') {
      // Alles freigeben, was mehr als 30 s hinter der Abspielposition liegt
      const removeEnd = Math.max(0, video.currentTime - 30);
      if (removeEnd > 0) {
        sb.remove(0, removeEnd);
        await new Promise((r) => sb.addEventListener('updateend', r, { once: true }));
        sb.appendBuffer(data); // erneut versuchen
      }
    } else {
      throw err;
    }
  }
};

// Wichtig · Fallstricke

  • InvalidStateError wird geworfen, wenn sb.updating === true ist, wenn der SourceBuffer schon aus der MediaSource entfernt wurde oder wenn beim Media-Element ein Fehler vorliegt. Deshalb Aufrufe immer serialisieren. Steht readyState auf "ended", setzt ein Aufruf die MediaSource wieder auf "open".
  • QuotaExceededError wird geworfen, wenn der Puffer voll ist. Browser begrenzen ihn oft auf etwa 100–150 MB für Video. Mit remove() abgespielte Bereiche freigeben und den Aufruf erneut versuchen.
  • Sind die Daten ungültig oder passen sie nicht zum Codec, kommt es zu einem Parse-Fehler. Dann wird das Ereignis error ausgelöst, und die MediaSource wird in der Regel mit einem Decode-Fehler beendet (video.error).
  • Der Browser kopiert die übergebenen Daten. Sie können danach also wiederverwendet werden. Beim Übertragen aus einem Worker ist ein ArrayBuffer aber möglicherweise schon detached.
  • endOfStream() erst aufrufen, wenn bei keinem SourceBuffer mehr updating auf true steht.
  • Kompatibilität: MSE wird in Chrome, Edge, Firefox und Safari auf dem Desktop unterstützt. Auf dem iPhone gibt es erst ab iOS 17.1 ManagedMediaSource. In Dedicated Workers ist MSE erst ab Chrome 108 verfügbar (MediaSource.canConstructInDedicatedWorker). Vorher mit MediaSource.isTypeSupported() prüfen. Die ältere Methode appendStream() ist veraltet.