Echtzeit-Videoverarbeitung mit der WebCodecs API
WebCodecs-Videoverarbeitung mit MediaStreamTrackProcessor, TransformStream und VideoTrackGenerator, plus frame.close, Backpressure, Worker und Browser-Support.
Eine WebCodecs-Videopipeline besteht aus drei Teilen: einem MediaStreamTrackProcessor, der einen MediaStreamTrack in einen ReadableStream<VideoFrame> umwandelt, einem TransformStream, in dem jedes Frame manipuliert wird, und einem VideoTrackGenerator, der verarbeitete Frames wieder in einen MediaStreamTrack umwandelt, der einem <video>-Element zugewiesen werden kann. VideoTrackGenerator ist der aktuelle Name in der Spezifikation; Chromium-Beispiele verwenden noch den älteren, nicht standardisierten MediaStreamTrackGenerator. Hier ist die vollständige Pipeline:
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const track = stream.getVideoTracks()[0];
const processor = new MediaStreamTrackProcessor({ track });
const generator = new VideoTrackGenerator();
const grayscale = new TransformStream({
async transform(frame, controller) {
try {
const canvas = new OffscreenCanvas(frame.displayWidth, frame.displayHeight);
const ctx = canvas.getContext('2d');
ctx.filter = 'grayscale(1)';
ctx.drawImage(frame, 0, 0);
controller.enqueue(new VideoFrame(canvas, {
timestamp: frame.timestamp,
duration: frame.duration,
}));
} finally {
frame.close();
}
},
});
processor.readable.pipeThrough(grayscale).pipeTo(generator.writable);
videoEl.srcObject = new MediaStream([generator.track]);
Dieser Artikel behandelt die Aspekte, die in allen bisherigen Tutorials übergangen werden: die Fehlerfälle. Die oben gezeigte Happy-Path-Variante funktioniert – bis sie es nicht mehr tut: bis Frames verloren gehen, die Transformation in Rückstand gerät, der Encoder in einen geschlossenen Zustand übergeht oder man Code ausliefert, der davon ausgeht, dass Safari ihn nicht ausführen kann. Jeder dieser Fälle ist ein reales Produktionsproblem mit einer konkreten Ursache und einer konkreten Lösung – genau darum geht es im Rest dieses Artikels.
Wichtigste Erkenntnisse
- Eine WebCodecs-Pipeline folgt dem Schema
MediaStreamTrackProcessor→TransformStream→VideoTrackGenerator; verwenden Sie den Spezifikations-Konstruktornew MediaStreamTrackProcessor({ track })und nicht die veraltete Positionsform. - Das Vergessen von
frame.close()erschöpft die begrenzten Medienressourcen, auf die die Pipeline angewiesen ist; sind diese erschöpft, kommt die Frame-Ausgabe zum Stillstand, was zu einem Video führt, das zunächst ruckelt und dann einfriert, während der Rest der Seite weiterhin reagiert. MediaStreamTrackProcessorgibt keinen Gegendruck (Backpressure) an die Quelle weiter — wenn Ihre Transformation in Rückstand gerät, verwirft der Processor stillschweigend die ältesten Frames, anstatt einen Fehler auszulösen.- Führen Sie alle
VideoFrame-Operationen in einem einzigen Worker durch: Ein Frame, der über Worker-Grenzen hinweg übertragen wird, wird auf der sendenden Seite automatisch geschlossen, und ein erneuter Zugriff darauf löst eine Ausnahme aus. - Die WebCodecs-Unterstützung ist nach Schnittstelle aufgeteilt — die Kern-Interfaces
VideoEncoder/VideoFramesind ab Chrome 94+, Firefox 130+ und Safari 16.4+ verfügbar, währendMediaStreamTrackProcessor/VideoTrackGeneratorhinterherhinken (Safari 18+, in Firefox nicht unterstützt).
Was WebCodecs ist und warum die Pipeline so aufgebaut ist
WebCodecs gibt JavaScript direkten Zugriff auf die im Browser integrierten, häufig hardwarebeschleunigten Medien-Codecs sowie auf rohe Video-Frames. Zuvor war ein MediaStream undurchsichtig: Man leitete ihn in ein <video>-Element, und der Browser übernahm alles zwischen Aufnahme und Anzeige. WebCodecs öffnet diese Pipeline. Das VideoFrame-Interface legt die rohen Pixel zwischen Aufnahme und Kodierung offen – genau dort, wo ein Filter, ein virtueller Hintergrund oder ein benutzerdefinierter Encoder ansetzen muss.
Der Grund für die Verwendung von Streams in der Pipeline liegt darin, dass rohe dekodierte Frames groß sind (mehrere Megabyte pro Frame) und schnell ankommen (25+ pro Sekunde). Daher benötigt man Flusskontrolle und inkrementelle Verarbeitung, anstatt alles im Speicher zu puffern. Die WHATWG Streams API wurde genau für diese Art der atomaren Chunk-Verarbeitung durch Pipe-Ketten entwickelt. MediaStreamTrackProcessor überführt einen Live-Track in einen Stream; der TransformStream ist der Ort, an dem die framebezogene Verarbeitungslogik stattfindet; VideoTrackGenerator überführt das Ergebnis zurück in einen Track, den der Rest der Plattform — <video>, RTCPeerConnection — versteht.
WebCodecs arbeitet ausschließlich mit nicht-containerisierten Streams. Wenn Sie MP4/ISOBMFF lesen oder schreiben müssen, müssen Sie die Container-Logik selbst bereitstellen. Audio verfügt über eine parallele Schnittstelle (AudioData, AudioEncoder), die in diesem Artikel nicht behandelt wird; die folgenden Muster sind videospezifisch.
Eine funktionierende Kamera → Filter → Anzeige-Pipeline
Discover how at OpenReplay.com.
Eine funktionierende WebCodecs-Filter-Pipeline nimmt mit MediaStreamTrackProcessor auf, filtert innerhalb eines TransformStream mithilfe von Canvas2D direkt auf dem VideoFrame und zeigt das Ergebnis über VideoTrackGenerator an — wie im einleitenden Codeblock dargestellt. Der entscheidende Effizienzgewinn liegt in ctx.drawImage(frame, 0, 0) — drawImage akzeptiert einen VideoFrame direkt als Quelle, sodass Frames auf ein Canvas gezeichnet werden können, ohne sie manuell in ein PNG umzuwandeln oder ein zwischengeschaltetes ImageBitmap zu erstellen.
Für einen Canvas2D-Farbfilter ist der ctx.filter-String der günstigste Weg. Für pixeladressierbare Operationen — Chroma-Key, benutzerdefinierte Faltung — verwenden Sie getImageData/putImageData:
const filter = new TransformStream({
async transform(frame, controller) {
try {
const w = frame.displayWidth, h = frame.displayHeight;
const canvas = new OffscreenCanvas(w, h);
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(frame, 0, 0);
const imageData = ctx.getImageData(0, 0, w, h);
const px = imageData.data;
for (let i = 0; i < px.length; i += 4) {
const lum = 0.299 * px[i] + 0.587 * px[i + 1] + 0.114 * px[i + 2];
px[i] = px[i + 1] = px[i + 2] = lum;
}
ctx.putImageData(imageData, 0, 0);
controller.enqueue(new VideoFrame(canvas, {
timestamp: frame.timestamp,
duration: frame.duration,
}));
} finally {
frame.close();
}
},
});
Zwei Eigenschaften werden vom ursprünglichen Frame in den neuen übernommen: timestamp und duration. Der timestamp ist die Identität des Frames innerhalb der gesamten Pipeline — er übersteht Kodier-/Dekodierzyklen und wird später zur Latenzmessung verwendet. Lässt man ihn weg, verlieren nachgelagerte Verbraucher die Frame-Reihenfolge.
Für rechenintensivere pixelbezogene Operationen in voller Auflösung ist das getImageData-Readback der Flaschenhals; WebGL oder WebGPU (über importExternalTexture) halten den Frame auf der GPU und vermeiden das CPU-Readback vollständig. Verwenden Sie Canvas2D für Farbtransformationen und einfaches Compositing; greifen Sie auf einen GPU-Pfad zurück, wenn die pixelbezogenen Kosten Ihr Frame-Budget dominieren.
Der VideoFrame-Lebenszyklus: Warum frame.close() zwingend erforderlich ist
Das Vergessen von frame.close() verursacht nicht nur einen gewöhnlichen Speicherleck — es erschöpft die begrenzten Medienressourcen, auf die die Pipeline angewiesen ist. Sind diese erschöpft, kommt die Dekodierung oder Frame-Ausgabe zum Stillstand, weil kein neuer Frame mehr allokiert oder ausgegeben werden kann. Das charakteristische Symptom ist ein Video, das zunehmend ruckelt und dann einfriert, während der Rest der Seite weiterhin reagiert. VideoFrame.close() gibt die zugrundeliegende Medienressource frei, die der Frame belegt, und die WebCodecs-Spezifikation stellt ausdrücklich klar, dass diese Ressourcen begrenzt sind — Frames, die durch Hardware-Buffer unterstützt werden, stammen aus einem begrenzten Pool, und eine Quelle kann keinen neuen Frame ausgeben, wenn der Pool voll ist.
Deshalb ist close() keine optionale Bereinigung, die man der Garbage Collection überlassen kann. Der Garbage Collector kennt die zugrundeliegende Medienressource nicht nach eigenem Zeitplan, und wenn er schließlich ausgeführt wird, ist der Pool bereits erschöpft. Jeder VideoFrame, den Sie aus dem Processor lesen, und jeder, den Sie konstruieren, muss genau einmal geschlossen werden, wenn Sie ihn nicht mehr benötigen.
Der nicht offensichtliche Fehlerfall ist der Fehlerpfad. Wenn Ihre Transformation nach dem Lesen eines Frames, aber vor dessen Schließen eine Ausnahme auslöst, geht dieser Frame verloren — und eine Transformation, die bei einem Frame eine Ausnahme auslöst, tut dies in der Regel auch beim nächsten, sodass der Verlust sich schnell summiert. Die Lösung ist try/finally:
async transform(frame, controller) {
try {
// ...Filterarbeit, die eine Ausnahme auslösen könnte...
controller.enqueue(newFrame);
} finally {
frame.close(); // wird ausgeführt, unabhängig davon, ob der Body eine Ausnahme ausgelöst hat
}
}
finally stellt sicher, dass frame.close() sowohl im Erfolgs- als auch im Fehlerfall ausgeführt wird. Dies ist das wichtigste Muster in einer WebCodecs-Pipeline.
Backpressure: Warum langsame Transformationen stillschweigend Frames verwerfen
MediaStreamTrackProcessor gibt keinen Gegendruck (Backpressure) an die Quelle weiter. Wenn Ihr TransformStream in Rückstand gerät, verwirft der Processor stillschweigend die ältesten Frames, anstatt die Kamera zu verlangsamen — Sie werden niemals einen Fehler sehen, sondern nur fehlende Frames. Die praktische Konsequenz: Eine Transformation, die bei einer 30-fps-Quelle (33ms Budget) 50ms pro Frame benötigt, löst weder einen Fehler aus noch reiht sie Frames unbegrenzt in eine Warteschlange ein. Sie läuft stillschweigend mit ungefähr 20fps, wobei die Differenz verworfen wird. Sie können dies erkennen, indem Sie die Warteschlange der lesbaren Seite innerhalb der Transformation beobachten. TransformStreamDefaultController.desiredSize spiegelt den Backpressure-Zustand der lesbaren Seite wider — wenn dieser Wert negativ wird, hat die lesbare Seite ihren High-Water-Mark überschritten und der Verbraucher ist in Rückstand:
const filter = new TransformStream({
async transform(frame, controller) {
try {
if (controller.desiredSize !== null && controller.desiredSize < 0) {
// Der Verbraucher ist in Rückstand. Diesen Frame absichtlich verwerfen,
// anstatt weiter zurückzufallen.
return;
}
// ...Filterarbeit...
controller.enqueue(newFrame);
} finally {
frame.close();
}
},
});
Wenn Sie Backpressure erkennen, stehen Ihnen zwei Stellhebel zur Verfügung. Erstens: Frames absichtlich verwerfen — überspringen Sie den aktuellen Frame wie oben gezeigt, sodass ein bewusstes Muster den stillen, zufälligen Verlust ersetzt. Zweitens: Die Eingabe reduzieren — fordern Sie über getUserMedia mittels MediaTrackConstraints eine niedrigere Auflösung oder Framerate an, oder rufen Sie zur Laufzeit track.applyConstraints() auf, um diese zu verringern. Eine niedrigere Auflösung reduziert die pixelbezogene Arbeit pro Frame direkt und ist in der Regel die wirksamste Maßnahme bei einem CPU-gebundenen Filter.
Workers: Warum sollte man alle VideoFrame-Operationen in einem einzigen Worker durchführen?
Führen Sie alle VideoFrame-Operationen in einem einzigen Worker durch. Wenn ein VideoFrame über Worker-Grenzen hinweg via postMessage übertragen wird, wird die Referenz auf der sendenden Seite automatisch geschlossen, und jeder weitere Versuch, ihn zu lesen oder zu schließen, löst eine Ausnahme aus — eine stille Race Condition, die über Worker-Message-Queues hinweg nahezu unmöglich zu debuggen ist. Frames innerhalb übertragener Streams werden serialisiert, was sie klont und ein explizites Schließen auf beiden Seiten erfordert. Vermischt man beides, erhält man den Frühschließ-Fehler:
controller.enqueue(frame);
frame.close(); // Zu früh — enqueue ist asynchron; der Frame könnte noch in Übertragung sein
Da controller.enqueue() in Bezug auf den empfangenden Worker asynchron ist, führt ein zu frühes Schließen der Sender-Referenz zu Serialisierungsfehlern, während ein Nicht-Schließen den oben beschriebenen Leak-then-Freeze-Effekt verursacht. Halten Sie die gesamte Kette MediaStreamTrackProcessor → TransformStream → VideoTrackGenerator in einem Worker, und Sie vermeiden das Ownership-Problem vollständig. (Für die Übertragung kodierter Chunks vom Gerät — WebTransport, Data Channels — siehe die webrtcHacks Pipeline-Serie; das ist ein eigenes Thema.)
Wenn Sie einen Frame tatsächlich an einen Worker übergeben — um die Pipeline zu speisen, nicht um ihn aufzuteilen — übertragen Sie ihn explizit und greifen Sie auf der sendenden Seite nicht mehr darauf zu:
// Haupt-Thread
worker.postMessage({ frame }, { transfer: [frame] });
// `frame` ist hier nun ungültig. Weder lesen noch schließen auf dem Haupt-Thread.
Nach einer Übertragung besitzt der empfangende Worker den Frame und ist für dessen Schließen verantwortlich. Der sendende Thread muss seine Referenz als ungültig betrachten.
Kodierung für Übertragung oder Aufzeichnung
Ein VideoEncoder komprimiert rohe VideoFrame-Objekte in EncodedVideoChunk-Objekte, die über einen Output-Callback für die Aufzeichnung oder Übertragung bereitgestellt werden. Konfigurieren Sie ihn mit einem Codec-String, Abmessungen, Bitrate und Framerate:
const chunks = [];
const encoder = new VideoEncoder({
output: (chunk, metadata) => {
// chunk.type ist 'key' oder 'delta'; chunk enthält timestamp, duration, byteLength
chunks.push(chunk);
},
error: (e) => console.error('encoder error', e),
});
encoder.configure({
codec: 'vp8', // oder z.B. 'avc1.42001f' für H.264 Baseline
width: 640,
height: 480,
bitrate: 1_000_000,
framerate: 30,
});
Der output-Callback liefert einen EncodedVideoChunk sowie optionale Metadaten; der Chunk enthält seinen type ('key' oder 'delta'), timestamp, duration und die kodierten Bytes. Für Codec-Strings konsultieren Sie das WebCodecs Codec-Register und den MDN-Codec-Leitfaden, anstatt AVC-Profil-Strings zu erraten.
Fordern Sie mit encoder.encode(frame, { keyFrame: true }) (beachten Sie das Großbuchstaben-F) einen Keyframe an, wenn Sie ein Intra-Frame benötigen, etwa beim Stream-Start, nach einem Seek oder an einem Wiederherstellungspunkt — jedes Frame als Keyframe zu kodieren hebt die Inter-Frame-Kompression vollständig auf und erhöht Ihre Bitrate erheblich. Die Schreibweise der Option ist im MDN-Leitfaden Using the WebCodecs API dokumentiert.
Wiederherstellung nach einem geschlossenen Encoder
Wenn der Error-Callback von VideoEncoder ausgelöst wird und der Encoder in den 'closed'-Zustand übergeht, kann er nicht wiederverwendet werden. VideoEncoder.reset() existiert für nicht-terminale Fälle, aber die Wiederherstellung nach einem geschlossenen Encoder erfordert die Konstruktion einer neuen Instanz und einen erneuten Aufruf von configure() mit denselben Parametern. Prüfen Sie den Zustand vor jedem encode()-Aufruf und bauen Sie den Encoder bei einem 'closed'-Zustand neu auf:
function encodeFrame(frame, keyFrame = false) {
if (encoder.state === 'closed') {
encoder = makeEncoder(); // neuen VideoEncoder konstruieren und konfigurieren
}
if (encoder.state === 'configured') {
encoder.encode(frame, { keyFrame });
}
}
Den encode()-Aufruf mit einer Zustandsprüfung und einem Wiederherstellungspfad abzusichern ist das, was eine lang laufende Sitzung durch einen vorübergehenden Codec-Fehler am Leben hält.
Browser-Unterstützung im Jahr 2026
Die WebCodecs-Unterstützung ist nach Schnittstelle aufgeteilt, und sie als einzelne Versionsnummer zu behandeln ist der Fehler, den jedes veraltete Tutorial macht. Die Kern-Interfaces VideoEncoder/VideoFrame sind weitgehend verfügbar; die Insertable-Streams-Komponenten — MediaStreamTrackProcessor und VideoTrackGenerator — folgen einem anderen, langsameren Zeitplan.
| Schnittstelle | Chrome / Edge | Firefox | Safari |
|---|---|---|---|
VideoEncoder / VideoFrame (WebCodecs-Kern) | 94+ | 130+ | 16.4+ |
MediaStreamTrackProcessor | 94+ | Nicht unterstützt | 18+ |
VideoTrackGenerator | Nicht unterstützt | Nicht unterstützt | 18+ |
MediaStreamTrackGenerator (nicht standardisiert) | 94+ | Nicht unterstützt | Nicht unterstützt |
Verifiziert anhand der MDN-Browser-Kompatibilitätsdaten für VideoEncoder und MediaStreamTrackProcessor. Der pauschale Hinweis „Safari unterstützt WebCodecs nicht” in den meisten Tutorials ist sowohl veraltet als auch ungenau: Safari unterstützt WebCodecs-Kern seit Version 16.4, mit erweiterter Codec-Unterstützung (einschließlich HEVC) ab Safari 17.4. Was Safari und Firefox fehlt, ist die Insertable-Streams-Aufnahme-/Ausgabeschicht — die Kamera-→-Filter-→-Anzeige-Pipeline läuft daher heute in Chromium und in Safari 18+ (wenn in einem dedizierten Worker implementiert), aber in Firefox können Sie Frames kodieren und dekodieren, während Sie diese auf anderem Wege beziehen.
Die praktische Schlussfolgerung: Erkennen Sie Features pro Schnittstelle, nicht pro Browser. Prüfen Sie window.MediaStreamTrackProcessor und window.VideoEncoder separat, und halten Sie einen Canvas-/requestVideoFrameCallback-Fallback für die Aufnahmeschicht bereit, wo die Insertable-Streams-Komponenten fehlen.
Debugging-Checkliste
Die drei Fehlermodi in einer WebCodecs-Pipeline — verworfene Frames, unkontrollierter Speicherverbrauch und Latenzspitzen — haben jeweils ein charakteristisches Symptom und einen direkten Diagnoseschritt.
| Symptom | Wahrscheinliche Ursache | Diagnoseschritt |
|---|---|---|
| Zunehmendes Ruckeln, dann Einfrieren; Rest der Seite reagiert | frame.close() fehlt auf einem Pfad → begrenzte Medienressourcen erschöpft | Jeden gelesenen oder konstruierten VideoFrame auf genau einen close()-Aufruf prüfen; try/finally-Abdeckung sicherstellen |
| Fehlende Frames, keine Fehler in der Konsole | Langsame Transformation; Processor verwirft älteste Frames stillschweigend | controller.desiredSize innerhalb der Transformation protokollieren; wenn der Wert negativ wird, ist der Verbraucher in Rückstand |
| Latenz steigt im Zeitverlauf | Langsamer Per-Frame-Filter überschreitet das Frame-Budget | Dauer pro Schritt messen; mit dem Budget Ihrer Framerate vergleichen (33ms bei 30fps) |
| Encoder produziert keine Chunks mehr | VideoEncoder ist nach einem Fehler in den 'closed'-Zustand übergegangen | encoder.state vor jedem encode()-Aufruf prüfen; bei 'closed' neu aufbauen |
Das Muster „Ruckeln dann Einfrieren” ist es wert, auf Anhieb erkannt zu werden. Session-Replays von WebCodecs-basierten Features zeigen dieses Muster zuverlässig: flüssiges Video, das normal abgespielt wird, dann sichtbar Frames verliert und schließlich vollständig einfriert, während die restliche Benutzeroberfläche interaktiv bleibt. Das ist die sichtbare Signatur einer Erschöpfung begrenzter Medienressourcen durch nicht geschlossene Frames — das Replay zeigt das Symptom deutlich, aber die Ursache ist unsichtbar, wenn man nicht weiß, nach einem nicht geschlossenen Frame irgendwo im Pipeline-Code zu suchen.
Um die echte End-to-End-Latenz — von der Kameraaufnahme bis zur Anzeige — zu messen, kodieren Sie den timestamp des Frames als Pixel-Overlay vor der Pipeline und dekodieren Sie ihn aus der gerenderten Ausgabe via requestVideoFrameCallback. Als Referenzpunkt berichtete der webrtcHacks Pipeline-Benchmark (März 2023) folgende Per-Frame-Kosten:
| Schritt | Dauer |
|---|---|
| Hintergrundentfernung | 22ms |
| Overlay-Hinzufügung | 1ms |
| Kodierung | 8ms |
| Dekodierung | 1ms |
| Anzeige | 38ms |
Ihre Werte werden je nach Hardware und Filterkomplexität variieren. Das bemerkenswerte Ergebnis ist, dass allein die Anzeige ~38ms ausmacht — der dominierende Term — was bedeutet, dass ein Filter, der bequem in ein 30-fps-Budget passt, sich dennoch verzögert anfühlen kann, wenn man den Anzeige-Anteil nicht berücksichtigt. Messen Sie den gesamten Pfad, nicht nur Ihre Transformation.
Fazit
Die Form der WebCodecs-Pipeline — MediaStreamTrackProcessor → TransformStream → VideoTrackGenerator — ist kompakt genug, um in einen einzigen Codeblock zu passen, aber die Lücke zwischen einer Demo und einem auslieferbaren Feature liegt vollständig in den Fehlerfällen: jeden Frame schließen, stillen Backpressure erkennen, die gesamte Kette in einem Worker halten, nach einem geschlossenen Encoder wiederherstellen und Features pro Schnittstelle statt pro Browser erkennen. Beginnen Sie mit dem try/finally-Beispiel am Anfang dieses Artikels, fügen Sie die desiredSize-Prüfung und den Encoder-Zustandsschutz hinzu, und Sie haben eine Pipeline, die die Fälle übersteht, die Happy-Path-Tutorials nie erreichen.
Häufig gestellte Fragen
Wann sollte ich Canvas2D gegenüber WebGL oder WebGPU für einen WebCodecs-Filter verwenden?
Verwenden Sie Canvas2D für Farbtransformationen und einfaches Compositing, bei denen ctx.filter-Strings oder moderate getImageData-Schleifen in das Frame-Budget passen. Greifen Sie auf WebGL oder WebGPU zurück, wenn pixelbezogene Kosten dominieren, da diese den Frame über importExternalTexture auf der GPU halten und das CPU-Readback vermeiden, das getImageData erzwingt. Bei voller Auflösung ist dieses Readback in der Regel der Flaschenhals, sodass ein GPU-Pfad die Lösung für rechenintensive pixelbezogene Operationen wie Chroma-Keying ist.
Warum werden meine VideoFrames unerwartet geschlossen, wenn ich sie zwischen Workers übergebe?
Das Übertragen eines VideoFrame über eine Worker-Grenze via postMessage schließt automatisch die Referenz auf der sendenden Seite, sodass jeder Versuch, ihn auf der Senderseite erneut zu lesen oder zu schließen, eine Ausnahme auslöst. Dies unterscheidet sich von Frames innerhalb übertragener Streams, die serialisiert und geklont werden und ein explizites Schließen auf beiden Seiten erfordern. Um die Race Condition zu vermeiden, halten Sie die gesamte Pipeline in einem Worker, oder behandeln Sie nach einer Übertragung die Referenz des Senders als ungültig und lassen Sie den empfangenden Worker den Frame besitzen und schließen.
Funktioniert die Kamera-zu-Filter-zu-Anzeige-Pipeline in Firefox?
Nicht vollständig. Firefox 130 und höher unterstützen die Kern-Interfaces VideoEncoder und VideoFrame, jedoch nicht die Insertable-Streams-Aufnahme- und Ausgabeschicht, was bedeutet, dass MediaStreamTrackProcessor und VideoTrackGenerator nicht verfügbar sind. Sie können Frames in Firefox kodieren und dekodieren, müssen diese jedoch auf anderem Wege beziehen, etwa über ein Canvas mit requestVideoFrameCallback. Erkennen Sie Features pro Schnittstelle, indem Sie window.MediaStreamTrackProcessor und window.VideoEncoder separat prüfen, anstatt den Browser zu testen.
Was ist der Unterschied zwischen VideoEncoder.reset() und dem Neuaufbau des Encoders?
VideoEncoder.reset() behandelt nicht-terminale Fälle und löscht ausstehende Arbeit bei einem Encoder, der noch verwendbar ist. Es kann einen Encoder nicht wiederherstellen, der nach einem Fehler in den 'closed'-Zustand übergegangen ist, da ein geschlossener Encoder weder neu konfiguriert noch wiederverwendet werden kann. Die Wiederherstellung nach 'closed' bedeutet, eine neue VideoEncoder-Instanz zu konstruieren und configure() erneut mit denselben Parametern aufzurufen. Prüfen Sie encoder.state vor jedem encode()-Aufruf und bauen Sie den Encoder neu auf, wenn er 'closed' anzeigt.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k