Start · Sprachen · JavaScript · Referenz · DedicatedWorkerGlobalScope: rtctransform

DedicatedWorkerGlobalScope: rtctransform

Ereignis

Wird im globalen Bereich eines Dedicated Workers ausgelöst, wenn ein <code>RTCRtpScriptTransform</code> an ihn gebunden wird. Über dieses Ereignis kommt der Worker an die codierten WebRTC-Frames.

seit JavaScript Firefox 117, Safari 15.4; Chromium: bisher nur tei Kategorie: webapi

Signatur

self.addEventListener("rtctransform", (event) => { … }) / self.onrtctransform = (event) => { … }

Beschreibung

Das Ereignis rtctransform ist der Einstiegspunkt für WebRTC Encoded Transforms. Das sind codierte Audio- und Video-Frames, die zwischen Encoder und Paketierer (beim Senden) bzw. zwischen Depaketierer und Decoder (beim Empfangen) in einem Worker bearbeitet werden. Im Haupt-Thread erzeugen Sie ein new RTCRtpScriptTransform(worker, options) und weisen es der Eigenschaft transform eines RTCRtpSender oder RTCRtpReceiver zu. Daraufhin feuert im Worker ein rtctransform-Ereignis.

Das Ereignisobjekt ist vom Typ RTCTransformEvent. Seine Eigenschaft transformer enthält ein RTCRtpScriptTransformer mit folgenden Bestandteilen:

  • readable: ein ReadableStream mit den eingehenden codierten Frames (RTCEncodedVideoFrame bzw. RTCEncodedAudioFrame),
  • writable: ein WritableStream, in den Sie die bearbeiteten Frames zurückschreiben,
  • options: das zweite Argument des RTCRtpScriptTransform-Konstruktors, also etwa ein Name oder ein Schlüssel, mit dem sich Sender- und Empfänger-Transforms unterscheiden lassen.

Typische Anwendungen sind Ende-zu-Ende-Verschlüsselung (E2EE, etwa bei Videokonferenzen über SFUs), das Einfügen von Metadaten und die Analyse von Frames. Das Ereignis wird einmal pro gebundenem Transform ausgelöst, nicht einmal pro Frame. Die Frames selbst verarbeiten Sie danach über die Streams, meist mit readable.pipeThrough(new TransformStream(...)).pipeTo(writable).

Sie können das Ereignis mit addEventListener("rtctransform", handler) abonnieren oder die Eigenschaft onrtctransform setzen. Es bubbelt nicht und kann nicht abgebrochen werden.

Parameter

Name Typ Default Beschreibung
$type Pflicht string Der Ereignisname "rtctransform".
$listener Pflicht (event: RTCTransformEvent) => void Callback, der das RTCTransformEvent erhält. Über event.transformer erreichen Sie readable, writable und options.
$options boolean | AddEventListenerOptions false Übliche Listener-Optionen wie once oder signal.

Rückgabewert

Typ
void
Beschreibung
addEventListener gibt nichts zurück. Der Handler bekommt ein RTCTransformEvent.

Beispiele

Haupt-Thread: Transform an Sender und Empfänger binden

// main.js
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);
  // Löst im Worker ein rtctransform-Ereignis aus
  sender.transform = new RTCRtpScriptTransform(worker, { role: 'sender', key: 42 });
}

pc.addEventListener('track', ({ receiver }) => {
  receiver.transform = new RTCRtpScriptTransform(worker, { role: 'receiver', key: 42 });
});

Worker: Frames per addEventListener verarbeiten (einfache XOR-„Verschlüsselung“)

// e2ee-worker.js
const xorFrame = (key) => new TransformStream({
  transform(frame, controller) {
    const data = new Uint8Array(frame.data);
    for (let i = 0; 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('Transform gebunden:', options.role);
  readable
    .pipeThrough(xorFrame(options.key))
    .pipeTo(writable)
    .catch((err) => console.error('Pipeline-Fehler:', err));
});
Transform gebunden: sender Transform gebunden: receiver

Worker: onrtctransform-Eigenschaft und Frame-Statistik

// stats-worker.js
self.onrtctransform = async ({ transformer }) => {
  let count = 0;
  const reader = transformer.readable.getReader();
  const writer = transformer.writable.getWriter();
  while (true) {
    const { value: frame, done } = await reader.read();
    if (done) break;
    if (++count % 100 === 0) {
      console.log(`${count} Frames, Typ: ${frame.type ?? 'audio'}, Bytes: ${frame.data.byteLength}`);
    }
    await writer.write(frame); // unverändert weiterreichen
  }
};

// Handler wieder entfernen:
// self.onrtctransform = null;
100 Frames, Typ: delta, Bytes: 1834

Feature-Erkennung mit Fallback

// main.js
const supportsScriptTransform = 'RTCRtpScriptTransform' in window;
const supportsLegacyStreams = 'createEncodedStreams' in RTCRtpSender.prototype;

console.log({ supportsScriptTransform, supportsLegacyStreams });
// Ohne RTCRtpScriptTransform: Streams über sender.createEncodedStreams()
// holen und per postMessage (transferierbar) an den Worker schicken.
{ supportsScriptTransform: true, supportsLegacyStreams: false }

// Wichtig · Fallstricke

  • Browser-Unterstützung: Safari ab 15.4 und Firefox ab 117 unterstützen RTCRtpScriptTransform. Chromium-Browser setzen dagegen lange auf die nicht standardisierte API createEncodedStreams() (setzt encodedInsertableStreams: true in der RTCPeerConnection-Konfiguration voraus). Prüfen Sie beide Wege per Feature-Erkennung.
  • Das Ereignis wird nur in Dedicated Workern ausgelöst, nicht in Shared Workern, Service Workern oder im Haupt-Thread.
  • Das Ereignis feuert einmal pro Transform. Wer im Handler auf einzelne Frames wartet, hat das Modell falsch verstanden: Die Frames kommen über transformer.readable.
  • Wenn ein Worker mehrere Transforms bedient (Audio/Video, Senden/Empfangen), unterscheiden Sie diese über transformer.options. Frames, die Sie nicht zurück in writable schreiben, werden verworfen. Das zeigt sich als eingefrorenes Video oder als Tonaussetzer.
  • Die Beispiel-XOR-Verschlüsselung ist keine echte Sicherheit. Für E2EE nutzen Sie crypto.subtle (z. B. AES-GCM). Lassen Sie bei Video die Codec-Header der Keyframes unverschlüsselt, damit die SFU weiterhin richtig weiterleiten kann. Alternativ kommt SFrame infrage.
  • Bleibt die Verarbeitung pro Frame zu lange hängen, steigt die Latenz. Blockierende Arbeit im Worker sollten Sie daher vermeiden.