Start · Sprachen · JavaScript · Referenz · RTCEncodedVideoFrame

RTCEncodedVideoFrame

Schnittstelle

Stellt einen kodierten Videoframe in der WebRTC-Sende- oder Empfangs-Pipeline dar, den ein WebRTC Encoded Transform lesen und verändern kann, z. B. für Ende-zu-Ende-Verschlüsselung.

seit JavaScript Firefox 117+, Safari 15.4+ (RTCRtpScriptTransform) Kategorie: webapi

Signatur

interface RTCEncodedVideoFrame

Beschreibung

RTCEncodedVideoFrame ist ein Baustein von WebRTC Encoded Transforms (früher „Insertable Streams“). Auf der Senderseite liegt der Frame zwischen Encoder und Paketierer, auf der Empfängerseite zwischen Depaketierer und Decoder. Dadurch kommt man an die bereits komprimierten Nutzdaten, bevor sie verschickt oder dekodiert werden.

Typische Einsatzzwecke sind Ende-zu-Ende-Verschlüsselung (E2EE) über SFU-Server hinweg, das Anhängen eigener Metadaten (z. B. Zeitstempel oder Kennungen) sowie Analyse- und Debug-Aufgaben. Selbst erzeugt man die Frames normalerweise nicht. Man bekommt sie aus dem readable-Stream eines RTCRtpScriptTransformer im Worker und gibt sie nach der Bearbeitung an dessen writable-Stream weiter.

Wichtige Mitglieder:

  • data: ein ArrayBuffer mit den kodierten Nutzdaten. Man kann ihn lesen und durch einen neuen Puffer ersetzen.
  • type (nur lesbar): "key", "delta" oder "empty".
  • timestamp (nur lesbar, veraltet): der RTP-Zeitstempel. Stattdessen getMetadata().rtpTimestamp verwenden.
  • getMetadata(): gibt ein Objekt zurück, unter anderem mit frameId, dependencies, width, height, spatialIndex, temporalIndex, synchronizationSource, contributingSources, payloadType, mimeType und rtpTimestamp.

Die Bearbeitung läuft in einem TransformStream. Jeder Frame, den man annimmt, muss entweder mit controller.enqueue(frame) weitergegeben oder bewusst verworfen werden. Verworfene Frames können beim Empfänger zu Bildfehlern führen, bis der nächste Keyframe kommt.

Parameter

Name Typ Default Beschreibung
$originalFrame Pflicht RTCEncodedVideoFrame Nur beim neueren Konstruktor new RTCEncodedVideoFrame(originalFrame, options): der Frame, dessen Daten und Metadaten kopiert werden.
$options { metadata?: RTCEncodedVideoFrameMetadata } {} Optional. Metadaten, die im kopierten Frame die übernommenen Werte ersetzen (z. B. andere rtpTimestamp).

Rückgabewert

Typ
RTCEncodedVideoFrame
Beschreibung
Instanzen gibt der readable-Stream eines RTCRtpScriptTransformer (bzw. von createEncodedStreams()) aus. getMetadata() gibt ein RTCEncodedVideoFrameMetadata-Objekt zurück.

Beispiele

Hauptthread: Encoded Transform an Sender und Empfänger hängen

const worker = new Worker('e2ee-worker.js');
const pc = new RTCPeerConnection();

const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();
const sender = pc.addTrack(track, stream);

// Transform für ausgehende Frames (Verschlüsselung)
sender.transform = new RTCRtpScriptTransform(worker, { operation: 'encrypt' });

// Transform für eingehende Frames (Entschlüsselung)
pc.addEventListener('track', ({ receiver }) => {
  receiver.transform = new RTCRtpScriptTransform(worker, { operation: 'decrypt' });
});

Worker: Nutzdaten per XOR verschleiern (Demo-E2EE)

// e2ee-worker.js
const KEY = 0x5a;

const xorFrame = (frame, controller) => {
  const src = new Uint8Array(frame.data);
  const out = new Uint8Array(src.length);
  for (let i = 0; i < src.length; i++) out[i] = src[i] ^ KEY;
  frame.data = out.buffer; // neuen Puffer zuweisen
  controller.enqueue(frame);
};

addEventListener('rtctransform', (event) => {
  const { readable, writable, options } = event.transformer;
  console.log('Transform gestartet:', options.operation);
  readable
    .pipeThrough(new TransformStream({ transform: xorFrame }))
    .pipeTo(writable);
});
Transform gestartet: encrypt

Metadaten lesen und Keyframes erkennen

// im Worker
addEventListener('rtctransform', ({ transformer }) => {
  const inspect = new TransformStream({
    transform(frame, controller) {
      if (frame.type === 'key') {
        const { width, height, mimeType, rtpTimestamp } = frame.getMetadata();
        console.log(`Keyframe ${width}x${height} ${mimeType} @${rtpTimestamp}`);
      }
      controller.enqueue(frame);
    }
  });
  transformer.readable.pipeThrough(inspect).pipeTo(transformer.writable);
});
Keyframe 640x480 video/VP8 @3172839211

Chromium-Fallback: createEncodedStreams()

const pc = new RTCPeerConnection({ encodedInsertableStreams: true });
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const sender = pc.addTrack(stream.getVideoTracks()[0], stream);

if ('RTCRtpScriptTransform' in window) {
  sender.transform = new RTCRtpScriptTransform(new Worker('e2ee-worker.js'), {});
} else if ('createEncodedStreams' in sender) {
  const { readable, writable } = sender.createEncodedStreams();
  readable
    .pipeThrough(new TransformStream({
      transform(frame, controller) {
        console.log(frame.type, frame.data.byteLength, 'Bytes');
        controller.enqueue(frame);
      }
    }))
    .pipeTo(writable);
}
key 18342 Bytes delta 1204 Bytes