Automatically close new VideoFrames created by toCanvasImageSource, reduce clones in mediaSamplesAtTimestamps

This commit is contained in:
Vanilagy
2025-05-09 15:31:18 +02:00
parent 8bca9b0217
commit 8de25036b8
5 changed files with 37 additions and 18 deletions
+15 -5
View File
@@ -60,7 +60,16 @@
6.933333333333334, 6.933333333333334,
7.039999999999999, 7.039999999999999,
7.1466666666666665, 7.1466666666666665,
7.253333333333333 7.253333333333333,
7.253333333333333,
7.253333333333333,
7.253333333333333,
7.253333333333333,
7.253333333333333,
7.253333333333333,
7.253333333333333,
7.253333333333333,
7.253333333333333,
]; ];
console.log(timestamps); console.log(timestamps);
@@ -88,21 +97,22 @@
*/ */
/*
for (let i = 0; i < 1000; i++) { for (let i = 0; i < 40; i++) {
const count = Math.floor(50 * Math.random()); const count = Math.floor(50 * Math.random());
const timestamps = Array.from({ length: count }, () => Math.random() * 20).sort((a, b) => a-b); const timestamps = Array.from({ length: count }, () => Math.random() * 20).sort((a, b) => a-b);
for await (const wrappedCanvas of sink.canvasesAtTimestamps(timestamps)) { for await (const wrappedCanvas of sink.samplesAtTimestamps(timestamps)) {
//console.log(wrappedCanvas); //console.log(wrappedCanvas);
wrappedCanvas.close()
} }
console.log("don") console.log("don")
} }
console.log("DONE!") console.log("DONE!")
*/
+4
View File
@@ -328,6 +328,10 @@ videoSample.toCanvasImageSource(); // => VideoFrame | OffscreenCanvas;
This method returns a valid `CanvasImageSource` you can use with [drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage). This method returns a valid `CanvasImageSource` you can use with [drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage).
::: warning
If this method returns a `VideoFrame`, you should use that frame immediately. This is because any internally-created video frames will automatically be closed in the next microtask.
:::
--- ---
Sometimes you may want direct access to the underlying pixel data. To do this, `VideoSample` allows you to copy this data into an `ArrayBuffer`. Sometimes you may want direct access to the underlying pixel data. To do this, `VideoSample` allows you to copy this data into an `ArrayBuffer`.
+7 -9
View File
@@ -461,7 +461,6 @@ export abstract class BaseMediaSampleSink<
// the consumer. // the consumer.
let outOfBandError = null as Error | null; let outOfBandError = null as Error | null;
let lastUsedSample = null as MediaSample | null;
const pushToQueue = (sample: MediaSample | null) => { const pushToQueue = (sample: MediaSample | null) => {
sampleQueue.push(sample); sampleQueue.push(sample);
onQueueNotEmpty(); onQueueNotEmpty();
@@ -479,19 +478,20 @@ export abstract class BaseMediaSampleSink<
return; return;
} }
let sampleUsed = false; let sampleUses = 0;
while ( while (
timestampsOfInterest.length > 0 timestampsOfInterest.length > 0
&& sample.timestamp - timestampsOfInterest[0]! > -1e-10 // Give it a little epsilon && sample.timestamp - timestampsOfInterest[0]! > -1e-10 // Give it a little epsilon
) { ) {
pushToQueue(sample.clone() as MediaSample); sampleUses++;
timestampsOfInterest.shift(); timestampsOfInterest.shift();
sampleUsed = true;
} }
if (sampleUsed) { if (sampleUses > 0) {
lastUsedSample?.close(); for (let i = 0; i < sampleUses; i++) {
lastUsedSample = sample; // Clone the sample if we need to emit it multiple times
pushToQueue((i < sampleUses - 1 ? sample.clone() : sample) as MediaSample);
}
} else { } else {
sample.close(); sample.close();
} }
@@ -601,7 +601,6 @@ export abstract class BaseMediaSampleSink<
} }
await flushDecoder(); await flushDecoder();
lastUsedSample?.close();
} }
decoder.close(); decoder.close();
@@ -641,7 +640,6 @@ export abstract class BaseMediaSampleSink<
for (const sample of sampleQueue) { for (const sample of sampleQueue) {
sample?.close(); sample?.close();
} }
lastUsedSample?.close();
return { value: undefined, done: true }; return { value: undefined, done: true };
}, },
+10 -2
View File
@@ -414,7 +414,12 @@ export class VideoSample {
context.restore(); context.restore();
} }
/** Converts this video sample to a CanvasImageSource for drawing to a canvas. */ /**
* Converts this video sample to a CanvasImageSource for drawing to a canvas.
*
* You must use the value returned by this method immediately, as any VideoFrame created internally will
* automatically be closed in the next microtask.
*/
toCanvasImageSource() { toCanvasImageSource() {
if (this._closed) { if (this._closed) {
throw new Error('VideoSample is closed.'); throw new Error('VideoSample is closed.');
@@ -424,7 +429,10 @@ export class VideoSample {
if (this._data instanceof Uint8Array) { if (this._data instanceof Uint8Array) {
// Requires VideoFrame to be defined // Requires VideoFrame to be defined
return this.toVideoFrame(); const videoFrame = this.toVideoFrame();
queueMicrotask(() => videoFrame.close()); // Let's automatically close the frame in the next microtask
return videoFrame;
} else { } else {
return this._data; return this._data;
} }
+1 -2
View File
@@ -1,4 +1,3 @@
- https://github.com/Vanilagy/mp4-muxer/issues/83 tell him it's possible now - https://github.com/Vanilagy/mp4-muxer/issues/83 tell him it's possible now
- textsubtitlesource, chunked piping - textsubtitlesource, chunked piping
- More efficient MP3 loading when reading sequentially - More efficient MP3 loading when reading sequentially
- toCanvasImageSource might return a new videoframe; how to handle this?