Start · Sprachen · JavaScript · Referenz · RTCRtpScriptTransformer

RTCRtpScriptTransformer

Schnittstelle

Worker-seitiges Gegenstück zu <code>RTCRtpScriptTransform</code>: stellt die kodierten Audio-/Video-Frames einer WebRTC-Verbindung als <code>readable</code>/<code>writable</code>-Streams bereit.

seit JavaScript Firefox 117+, Safari 15.4+; in Chromium lange nich Kategorie: webapi

Signatur

interface RTCRtpScriptTransformer

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 als RTCEncodedVideoFrame oder RTCEncodedAudioFrame.
  • writable (WritableStream): nimmt die (veränderten) Frames zurück und leitet sie weiter in die Pipeline.
  • options (any): das Objekt, das beim Konstruktor new 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 ein Promise zurück.
  • sendKeyFrameRequest(): bittet die Gegenstelle, ein Schlüsselbild zu senden (nur auf der Empfängerseite). Gibt ein Promise zurü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

Typ
RTCRtpScriptTransformer
Beschreibung
Keine Rückgabe im engeren Sinn. Die Instanz steht im Worker über 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));
});
Transform gestartet: { side: "send", kind: "video" }

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);
    }
  }
});
Schlüsselbild erzeugt, Zeitstempel: 1234567890

// 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 (mit encodedInsertableStreams: 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() an writable weiterreichen. 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.subtle mit AES-GCM und pro Frame einem eigenen IV, z. B. abgeleitet aus frame.getMetadata().
  • generateKeyFrame() wirft eine InvalidStateError, 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 postMessage oder die options. Speichere sie nicht an einer Stelle, die für Skripte im Hauptthread zugänglich ist.