Signatur
Beschreibung
RTCRtpScriptTransform gehört zur WebRTC-Erweiterung Encoded Transform. Damit greifen Sie auf bereits kodierte Audio- und Videoframes zu, also nachdem der Encoder gearbeitet hat und bevor das Paketieren beginnt (Sender), bzw. nach dem Entpaketieren und vor dem Decoder (Empfänger). Typische Anwendungen sind Ende-zu-Ende-Verschlüsselung (E2EE) über SFU-Server hinweg, das Anhängen von Metadaten an Frames oder Analyse und Statistik.
Das Objekt hat selbst keine öffentlichen Eigenschaften oder Methoden. Sie erzeugen es im Haupt-Thread mit new RTCRtpScriptTransform(worker, options, transfer) und weisen es der Eigenschaft transform eines RTCRtpSender oder RTCRtpReceiver zu. Im angegebenen Worker wird daraufhin das Ereignis rtctransform ausgelöst. Dessen Eigenschaft event.transformer ist ein RTCRtpScriptTransformer mit readable, writable und options.
Im Worker leiten Sie die Frames über readable.pipeThrough(new TransformStream(...)).pipeTo(writable) weiter und können sie dabei verändern. Weil die Verarbeitung im Worker stattfindet, bleibt der Haupt-Thread frei. Kodierte Frames sind RTCEncodedVideoFrame- bzw. RTCEncodedAudioFrame-Objekte, deren Nutzdaten als ArrayBuffer in frame.data liegen.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $worker Pflicht | Worker | Dedizierter Worker, in dem das Ereignis rtctransform ausgelöst wird und die Transformation läuft. |
|
| $options | any | undefined | Beliebiger strukturiert klonbarer Wert, z. B. { operation: 'encrypt' }. Im Worker steht er als event.transformer.options zur Verfügung. |
| $transfer | Array<Transferable> | [] | Übertragbare Objekte, etwa ein MessagePort für den Schlüsselaustausch. Sie werden an den Worker übergeben und sind danach im Haupt-Thread nicht mehr nutzbar. |
Beispiele
Transform im Haupt-Thread erzeugen und an Sender/Empfänger hängen
const worker = new Worker('e2ee-worker.js');
const pc = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
for (const track of stream.getTracks()) {
const sender = pc.addTrack(track, stream);
sender.transform = new RTCRtpScriptTransform(worker, { operation: 'encrypt' });
}
pc.addEventListener('track', (event) => {
event.receiver.transform = new RTCRtpScriptTransform(worker, { operation: 'decrypt' });
});
Worker: Frames per XOR „verschlüsseln“ (Demo, keine echte Kryptografie)
// e2ee-worker.js
const KEY = 0x5a;
const xorFrame = (frame, controller) => {
const view = new Uint8Array(frame.data);
for (let i = 0; i < view.length; i++) view[i] ^= KEY;
frame.data = view.buffer;
controller.enqueue(frame);
};
self.addEventListener('rtctransform', (event) => {
const { readable, writable, options } = event.transformer;
console.log('Transform gestartet:', options.operation);
readable
.pipeThrough(new TransformStream({ transform: xorFrame }))
.pipeTo(writable)
.catch((err) => console.error('Pipeline abgebrochen:', err));
});
Schlüssel per MessagePort übertragen
// Haupt-Thread
const worker = new Worker('e2ee-worker.js');
const { port1, port2 } = new MessageChannel();
sender.transform = new RTCRtpScriptTransform(
worker,
{ operation: 'encrypt', port: port2 },
[port2]
);
// Schlüssel später rotieren
const key = crypto.getRandomValues(new Uint8Array(16));
port1.postMessage({ type: 'setKey', key });
// Im Worker:
// event.transformer.options.port.onmessage = ({ data }) => { currentKey = data.key; };
Feature-Erkennung mit Fallback für Chromium
const attachTransform = (sender, worker) => {
if ('RTCRtpScriptTransform' in window) {
sender.transform = new RTCRtpScriptTransform(worker, { operation: 'encrypt' });
return 'script-transform';
}
if (typeof sender.createEncodedStreams === 'function') {
// Ältere Chromium-API (PeerConnection mit encodedInsertableStreams: true nötig)
const { readable, writable } = sender.createEncodedStreams();
worker.postMessage({ operation: 'encrypt', readable, writable }, [readable, writable]);
return 'insertable-streams';
}
return 'nicht unterstützt';
};
console.log(attachTransform(sender, worker));
// Wichtig · Fallstricke
- Browser-Unterstützung: Safari (ab 15.4) und Firefox (ab 117) unterstützen die API. Chromium-Browser bieten sie teilweise noch nicht an und nutzen stattdessen das nicht standardisierte
createEncodedStreams(); dafür muss dieRTCPeerConnectionmit{ encodedInsertableStreams: true }erzeugt werden. Prüfen Sie die Unterstützung immer per Feature-Erkennung. - Die Transformation läuft nur im Worker. Im Haupt-Thread gibt es kein
RTCRtpScriptTransformer, und das Ereignisrtctransformwird ausschließlich im Worker-Scope (self) ausgelöst. - Jede Instanz darf nur einem Sender oder Empfänger zugewiesen werden. Eine zweite Zuweisung wirft einen
InvalidStateError. Legen Sie deshalb pro Track ein eigenes Objekt an; der Worker selbst kann mehrfach verwendet werden. - Rufen Sie für jeden Frame
controller.enqueue(frame)auf. Fehlende oder verworfene Frames führen zu Bildfehlern, bis der nächste Keyframe eintrifft. Mittransformer.generateKeyFrame()(Sender) bzw.transformer.sendKeyFrameRequest()(Empfänger) lässt sich ein Keyframe anfordern, etwa nach einem Schlüsselwechsel. - Sicherheit: Für echte E2EE verwenden Sie die Web Crypto API (z. B. AES-GCM) oder SFrame, nicht das XOR aus dem Demo-Beispiel. Bei Video sollten die Codec-Header (die ersten Bytes) oft unverschlüsselt bleiben, damit SFU-Server weiterhin funktionieren.
- Die Zuweisung sollte möglichst vor dem Verbindungsaufbau erfolgen. Mit
sender.transform = nullentfernen Sie die Transformation wieder.