Signatur
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: einArrayBuffermit 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. StattdessengetMetadata().rtpTimestampverwenden.getMetadata(): gibt ein Objekt zurück, unter anderem mitframeId,dependencies,width,height,spatialIndex,temporalIndex,synchronizationSource,contributingSources,payloadType,mimeTypeundrtpTimestamp.
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
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);
});
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);
});
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);
}