Start · Sprachen · JavaScript · Referenz · RTCEncodedVideoFrame.type

RTCEncodedVideoFrame.type

Eigenschaft

Schreibgeschützter String, der angibt, ob ein kodierter Video-Frame ein Schlüsselbild (<code>"key"</code>), ein Delta-Bild (<code>"delta"</code>) oder leer (<code>"empty"</code>) ist.

seit JavaScript WebRTC Encoded Transform – Safari 15.4+, Firefox 1 Kategorie: webapi

Signatur

RTCEncodedVideoFrame.type

Beschreibung

Die Eigenschaft type eines RTCEncodedVideoFrame sagt dir, welche Art von kodiertem Bild gerade durch die WebRTC-Pipeline läuft. Ein Schlüsselbild ("key", auch I-Frame genannt) lässt sich ohne andere Frames dekodieren. Ein Delta-Bild ("delta", P- oder B-Frame) enthält nur die Änderungen zum vorherigen Bild und braucht deshalb die vorangehenden Frames. Laut Spezifikation gibt es auch "empty" für Frames ohne Nutzdaten. In der Praxis kommt dieser Wert kaum vor.

Du liest die Eigenschaft innerhalb einer Encoded Transform, also in einem TransformStream, das zwischen Encoder und Paketierer (beim Sender) oder zwischen Depaketierer und Decoder (beim Empfänger) hängt. Üblich sind folgende Anwendungsfälle:

  • Ende-zu-Ende-Verschlüsselung (E2EE): Bei Schlüsselbildern bleiben oft die ersten Bytes (z. B. VP8-Header) unverschlüsselt, oder ein neuer Schlüssel wird ab einem Schlüsselbild aktiv.
  • Statistik und Diagnose: Du zählst Schlüsselbilder und misst so, wie oft der Encoder neu synchronisiert.
  • Robuster Empfang: Delta-Frames werden verworfen, bis ein dekodierbares Schlüsselbild eintrifft.

Die Eigenschaft ist schreibgeschützt. Der Frame-Typ wird vom Encoder festgelegt und lässt sich in der Transformation nicht ändern. Brauchst du ein neues Schlüsselbild, forderst du es an: beim Sender mit RTCRtpScriptTransformer.generateKeyFrame(), beim Empfänger mit sendKeyFrameRequest().

Rückgabewert

Typ
"key" | "delta" | "empty"
Beschreibung
Ein String aus dem Aufzählungstyp RTCEncodedVideoFrameType: "key" für ein Schlüsselbild, "delta" für ein Delta-Bild, "empty" für einen Frame ohne Daten.

Beispiele

Schlüsselbilder im Worker zählen (RTCRtpScriptTransform)

// main.js – Transformation an den Sender hängen
const worker = new Worker('transform-worker.js');
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const pc = new RTCPeerConnection();
const sender = pc.addTrack(stream.getVideoTracks()[0], stream);
sender.transform = new RTCRtpScriptTransform(worker, { side: 'send' });

// transform-worker.js
let keyFrames = 0;
let deltaFrames = 0;

onrtctransform = (event) => {
  const { readable, writable } = event.transformer;
  readable
    .pipeThrough(new TransformStream({
      transform(frame, controller) {
        if (frame.type === 'key') {
          keyFrames++;
          console.log(`Schlüsselbild #${keyFrames}, ${frame.data.byteLength} Bytes`);
        } else if (frame.type === 'delta') {
          deltaFrames++;
        }
        controller.enqueue(frame); // Frame unverändert weiterreichen
      }
    }))
    .pipeTo(writable);
};
Schlüsselbild #1, 18342 Bytes Schlüsselbild #2, 17990 Bytes

Empfänger: Delta-Frames bis zum ersten Schlüsselbild verwerfen

// receive-worker.js
onrtctransform = (event) => {
  const transformer = event.transformer;
  let synchronisiert = false;

  // Beim Sender ein neues Schlüsselbild anfordern
  transformer.sendKeyFrameRequest().catch(() => {});

  transformer.readable
    .pipeThrough(new TransformStream({
      transform(frame, controller) {
        if (!synchronisiert) {
          if (frame.type !== 'key') return; // nicht dekodierbar, verwerfen
          synchronisiert = true;
          console.log('Erstes Schlüsselbild empfangen, Dekodierung startet');
        }
        controller.enqueue(frame);
      }
    }))
    .pipeTo(transformer.writable);
};
Erstes Schlüsselbild empfangen, Dekodierung startet

E2EE-Muster: Header von Schlüsselbildern unverschlüsselt lassen (Chromium, createEncodedStreams)

// Ältere Chromium-API (Insertable Streams)
const pc = new RTCPeerConnection({ encodedInsertableStreams: true });
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const sender = pc.addTrack(stream.getVideoTracks()[0], stream);
const { readable, writable } = sender.createEncodedStreams();

// VP8: 10 Bytes Header bei Schlüsselbildern, 3 Bytes bei Delta-Bildern
const headerBytes = (frame) => (frame.type === 'key' ? 10 : 3);
const KEY = 0x5a; // nur Demo – keine echte Kryptografie!

readable
  .pipeThrough(new TransformStream({
    transform(frame, controller) {
      const data = new Uint8Array(frame.data);
      for (let i = headerBytes(frame); i < data.length; i++) {
        data[i] ^= KEY;
      }
      frame.data = data.buffer;
      controller.enqueue(frame);
    }
  }))
  .pipeTo(writable);

// Wichtig · Fallstricke

  • Schreibgeschützt: Eine Zuweisung an frame.type wird ignoriert (im Strict Mode gibt es einen TypeError). Nur frame.data kannst du verändern.
  • Nur für Video: RTCEncodedAudioFrame hat keine type-Eigenschaft. Prüfe bei gemischten Transformationen vorher mit frame instanceof RTCEncodedVideoFrame.
  • "empty": Dieser Wert ist in der Spezifikation definiert, taucht aber in aktuellen Browsern praktisch nie auf. Dein Code sollte trotzdem damit umgehen können und darf nicht blind !== 'key' als Delta werten.
  • Header-Größen hängen vom Codec ab: Die 10/3 Bytes aus dem Beispiel gelten nur für VP8. H.264, VP9 und AV1 brauchen jeweils eine eigene Behandlung. Welcher Codec verwendet wird, steht in frame.getMetadata().mimeType (sofern der Browser das unterstützt).
  • Browser-Kompatibilität: Der Standardweg über RTCRtpScriptTransform funktioniert in Safari und Firefox. Chromium hat lange nur das nicht standardisierte createEncodedStreams() angeboten, das ein encodedInsertableStreams: true in der Konfiguration voraussetzt. Prüfe die Unterstützung zur Laufzeit mit 'RTCRtpScriptTransform' in window.
  • Leistung: Die Transformation läuft für jeden Frame. Verarbeite die Frames deshalb in einem Worker und vermeide teure Operationen im Hauptthread.