Signatur
Beschreibung
RTCTransformEvent gehört zur WebRTC Encoded Transform-API. Mit ihr lassen sich kodierte Audio- und Video-Frames einer RTCPeerConnection bearbeiten, nachdem sie kodiert und bevor sie paketiert wurden (beim Senden), bzw. nach dem Entpacken und vor dem Dekodieren (beim Empfangen). Typische Anwendungen sind Ende-zu-Ende-Verschlüsselung (E2EE) über einen SFU-Server hinweg, das Anhängen von Metadaten oder das Analysieren von Frames.
Im Hauptthread erzeugst du ein RTCRtpScriptTransform mit einem Worker und weist es RTCRtpSender.transform oder RTCRtpReceiver.transform zu. Der Browser löst daraufhin im zugehörigen DedicatedWorkerGlobalScope ein rtctransform-Ereignis aus. Du fängst es mit addEventListener("rtctransform", handler) oder über die Eigenschaft onrtctransform ab.
Das Ereignis hat genau eine eigene Eigenschaft:
transformer(nur lesbar): einRTCRtpScriptTransformer. Er stelltreadable(einReadableStreammit eingehenden kodierten Frames),writable(einWritableStreamfür die bearbeiteten Frames) undoptionsbereit (die Optionen, die beim Konstruktor im Hauptthread übergeben wurden). Außerdem bietet er die MethodengenerateKeyFrame()undsendKeyFrameRequest().
Üblicherweise verbindest du readable über einen TransformStream mit writable. Mit options unterscheidest du, ob der Transformer zu einem Sender oder einem Empfänger gehört.
Rückgabewert
event.transformer vom Typ RTCRtpScriptTransformer.Beispiele
Hauptthread: Transform an Sender und Empfänger anhängen
// main.js
const worker = new Worker('transform-worker.js');
const pc = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();
const sender = pc.addTrack(track, stream);
if ('RTCRtpScriptTransform' in window) {
// Löst im Worker ein rtctransform-Ereignis aus
sender.transform = new RTCRtpScriptTransform(worker, { side: 'send' });
pc.addEventListener('track', ({ receiver }) => {
receiver.transform = new RTCRtpScriptTransform(worker, { side: 'receive' });
});
} else {
console.warn('WebRTC Encoded Transform wird nicht unterstützt');
}
Worker: Frames per XOR verschlüsseln und entschlüsseln (addEventListener)
// transform-worker.js
const KEY = 0x5a;
const xorFrame = (frame, controller) => {
const data = new Uint8Array(frame.data);
// Die ersten Bytes (z. B. VP8-Header) unverändert lassen
for (let i = 10; i < data.length; i++) {
data[i] ^= KEY;
}
frame.data = data.buffer;
controller.enqueue(frame);
};
self.addEventListener('rtctransform', (event) => {
const { readable, writable, options } = event.transformer;
console.log('Transformer bereit für:', options.side);
readable
.pipeThrough(new TransformStream({ transform: xorFrame }))
.pipeTo(writable)
.catch((err) => console.error('Pipeline-Fehler:', err));
});
onrtctransform-Eigenschaft, Frame-Zähler und Keyframe anfordern
// stats-worker.js
let frames = 0;
self.onrtctransform = async (event) => {
const transformer = event.transformer;
// Beim Sender sofort einen Keyframe erzeugen lassen
if (transformer.options.side === 'send') {
try {
await transformer.generateKeyFrame();
} catch (e) {
console.warn('Keyframe nicht möglich:', e.name);
}
}
await transformer.readable
.pipeThrough(new TransformStream({
transform(frame, controller) {
frames++;
if (frames % 300 === 0) {
console.log(`${frames} Frames, Typ: ${frame.type ?? 'audio'}`);
}
controller.enqueue(frame);
}
}))
.pipeTo(transformer.writable);
};
// Handler später entfernen:
// self.onrtctransform = null;
// Wichtig · Fallstricke
- Nur im dedizierten Worker: Das Ereignis wird ausschließlich im
DedicatedWorkerGlobalScopeausgelöst, nie im Hauptthread und nicht in Shared- oder Service-Workern. - Browser-Kompatibilität: Firefox und Safari unterstützen
RTCRtpScriptTransform. Chromium hat lange nur die ältere, nicht standardisierte APIcreateEncodedStreams()(Insertable Streams) angeboten undRTCRtpScriptTransformerst später eingeführt. Prüfe die Unterstützung mit'RTCRtpScriptTransform' in windowund sieh bei Bedarf einen Fallback vor. - Frames immer weiterreichen: Wenn du
controller.enqueue(frame)nicht aufrufst, wird der Frame verworfen. Bei Video führt das zu Artefakten oder eingefrorenem Bild, bis der nächste Keyframe ankommt. - Codec-Header schützen: Wer bei der Verschlüsselung den gesamten Frame verändert, macht Header-Bytes (z. B. von VP8 oder H.264) unlesbar. Manche SFUs oder Dekoder kommen dann nicht mehr zurecht.
- Sicherheit: XOR ist nur ein Beispiel und keine echte Verschlüsselung. Für E2EE solltest du
crypto.subtle(z. B. AES-GCM) verwenden und die Schlüssel sicher austauschen, etwa perpostMessagean den Worker. - Für mehrere Sender oder Empfänger kann ein einzelner Worker verwendet werden. Pro
RTCRtpScriptTransformwird ein eigenes Ereignis ausgelöst. Welches zu welchem Stream gehört, erkennst du anoptions.