Signatur
Beschreibung
RTCRtpScriptTransformer gehört zur WebRTC Encoded Transform-API. Damit kannst du die bereits kodierten Medien-Frames einer RTCPeerConnection in einem Worker lesen, verändern und weitergeben. Das passiert zwischen Encoder und Paketierer (beim Senden) bzw. zwischen Depaketierer und Decoder (beim Empfangen). Typische Einsatzfälle sind Ende-zu-Ende-Verschlüsselung (E2EE) in SFU-Konferenzen, das Einbetten von Metadaten oder Analyse und Statistik auf Frame-Ebene.
Du erzeugst die Schnittstelle nicht selbst. Im Hauptthread erstellst du ein RTCRtpScriptTransform mit einem Worker und weist es RTCRtpSender.transform oder RTCRtpReceiver.transform zu. Der Browser löst daraufhin im Worker das Ereignis rtctransform aus. Dessen Eigenschaft event.transformer ist die RTCRtpScriptTransformer-Instanz.
Wichtige Mitglieder:
readable(ReadableStream): liefert die kodierten Frames alsRTCEncodedVideoFrameoderRTCEncodedAudioFrame.writable(WritableStream): nimmt die (veränderten) Frames zurück und leitet sie weiter in die Pipeline.options(any): das Objekt, das beim Konstruktornew RTCRtpScriptTransform(worker, options)übergeben wurde. Damit unterscheidest du z. B. zwischen Sender und Empfänger.generateKeyFrame(rid?): fordert beim Encoder einen neuen Schlüsselbild-Frame an (nur auf der Senderseite). Gibt einPromisezurück.sendKeyFrameRequest(): bittet die Gegenstelle, ein Schlüsselbild zu senden (nur auf der Empfängerseite). Gibt einPromisezurück.
Am einfachsten verarbeitest du die Frames, indem du readable über einen TransformStream mit writable verbindest: transformer.readable.pipeThrough(new TransformStream({...})).pipeTo(transformer.writable).
Rückgabewert
RTCTransformEvent.transformer zur Verfügung.Beispiele
Hauptthread: Transform an Sender und Empfänger hängen
const worker = new Worker('e2ee-worker.js');
const pc = new RTCPeerConnection({ encodedInsertableStreams: true }); // nur für ältere Chromium-Versionen nötig
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
for (const track of stream.getTracks()) {
const sender = pc.addTrack(track, stream);
// options landen im Worker unter transformer.options
sender.transform = new RTCRtpScriptTransform(worker, { side: 'send', kind: track.kind });
}
pc.addEventListener('track', ({ receiver }) => {
receiver.transform = new RTCRtpScriptTransform(worker, { side: 'receive', kind: receiver.track.kind });
});
Worker: Frames per XOR „verschlüsseln“ (Demo-E2EE)
// e2ee-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, damit der Decoder sie noch lesen kann
const skip = frame.type === 'key' ? 10 : 3;
for (let i = skip; i < data.length; i++) data[i] ^= KEY;
frame.data = data.buffer;
controller.enqueue(frame);
};
self.addEventListener('rtctransform', (event) => {
const transformer = event.transformer; // RTCRtpScriptTransformer
console.log('Transform gestartet:', transformer.options);
transformer.readable
.pipeThrough(new TransformStream({ transform: xorFrame }))
.pipeTo(transformer.writable)
.catch((err) => console.error('Pipeline abgebrochen:', err));
});
Schlüsselbild nach Schlüsselwechsel anfordern
// im Worker
let transformers = [];
self.addEventListener('rtctransform', ({ transformer }) => {
transformers.push(transformer);
transformer.readable.pipeTo(transformer.writable);
});
self.addEventListener('message', async ({ data }) => {
if (data.type !== 'rotate-key') return;
for (const t of transformers) {
if (t.options.kind !== 'video') continue;
try {
if (t.options.side === 'send') {
const timestamp = await t.generateKeyFrame();
console.log('Schlüsselbild erzeugt, Zeitstempel:', timestamp);
} else {
await t.sendKeyFrameRequest();
console.log('Schlüsselbild bei der Gegenstelle angefordert');
}
} catch (err) {
console.warn('Schlüsselbild nicht möglich:', err.name);
}
}
});
// Wichtig · Fallstricke
- Kompatibilität: Die Schnittstelle gibt es in Firefox ab 117 und in Safari ab 15.4. Chromium hat lange nur die ältere, nicht standardisierte API
RTCRtpSender.createEncodedStreams()angeboten (mitencodedInsertableStreams: true). Prüfe vor der Nutzung, ob sie vorhanden ist:'RTCRtpScriptTransform' in window. - Die Schnittstelle gibt es nur in dedizierten Workern. Im Hauptthread existiert sie nicht, und du kannst sie nicht selbst konstruieren.
- Du musst jeden Frame per
enqueue()anwritableweiterreichen. Verworfene oder zu langsam verarbeitete Frames führen zu Rucklern, Bildfehlern oder einem eingefrorenen Video. Arbeite deshalb schnell und vermeide blockierende Aufrufe. - Verändere Header-Bytes des Codecs nicht (z. B. VP8-Payload-Header, H.264-NAL-Header). Sonst können SFUs und Decoder die Frames nicht mehr verarbeiten. Bei echter E2EE nutzt du am besten
crypto.subtlemit AES-GCM und pro Frame einem eigenen IV, z. B. abgeleitet ausframe.getMetadata(). generateKeyFrame()wirft eineInvalidStateError, wenn du es auf der Empfängerseite oder bei Audio aufrufst.sendKeyFrameRequest()funktioniert nur auf der Empfängerseite.- Sicherheit: Die Schlüssel für E2EE übergibst du dem Worker über
postMessageoder dieoptions. Speichere sie nicht an einer Stelle, die für Skripte im Hauptthread zugänglich ist.