Signatur
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
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);
};
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);
};
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.typewird ignoriert (im Strict Mode gibt es einenTypeError). Nurframe.datakannst du verändern. - Nur für Video:
RTCEncodedAudioFramehat keinetype-Eigenschaft. Prüfe bei gemischten Transformationen vorher mitframe 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
RTCRtpScriptTransformfunktioniert in Safari und Firefox. Chromium hat lange nur das nicht standardisiertecreateEncodedStreams()angeboten, das einencodedInsertableStreams: truein 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.