Start · Sprachen · JavaScript · Referenz · RTCTransformEvent

RTCTransformEvent

Schnittstelle

Ereignis vom Typ <code>rtctransform</code>, das in einem dedizierten Worker ausgelöst wird, sobald ein <code>RTCRtpScriptTransform</code> dort einen Transformer zum Bearbeiten kodierter Frames bereitstellt.

seit JavaScript Firefox 117+, Safari 15.4+; in Chromium erst in ne Kategorie: webapi

Signatur

interface RTCTransformEvent extends Event

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): ein RTCRtpScriptTransformer. Er stellt readable (ein ReadableStream mit eingehenden kodierten Frames), writable (ein WritableStream für die bearbeiteten Frames) und options bereit (die Optionen, die beim Konstruktor im Hauptthread übergeben wurden). Außerdem bietet er die Methoden generateKeyFrame() und sendKeyFrameRequest().

Ü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

Typ
RTCTransformEvent
Beschreibung
Instanzen erzeugt ausschließlich der Browser. Es gibt keinen öffentlichen Konstruktor. Das Wichtigste am Ereignis ist 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));
});
Transformer bereit für: send Transformer bereit für: receive

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;
300 Frames, Typ: delta

// Wichtig · Fallstricke

  • Nur im dedizierten Worker: Das Ereignis wird ausschließlich im DedicatedWorkerGlobalScope ausgelö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 API createEncodedStreams() (Insertable Streams) angeboten und RTCRtpScriptTransform erst später eingeführt. Prüfe die Unterstützung mit 'RTCRtpScriptTransform' in window und 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 per postMessage an den Worker.
  • Für mehrere Sender oder Empfänger kann ein einzelner Worker verwendet werden. Pro RTCRtpScriptTransform wird ein eigenes Ereignis ausgelöst. Welches zu welchem Stream gehört, erkennst du an options.