Update thumbnail generation example to use cursors

This commit is contained in:
Vanilagy
2025-12-21 10:50:32 +01:00
parent 3fa95cb449
commit ece27cca79
3 changed files with 20 additions and 9 deletions
@@ -1,6 +1,7 @@
import { Input, ALL_FORMATS, BlobSource, UrlSource, CanvasSink } from 'mediabunny'; import { Input, ALL_FORMATS, BlobSource, UrlSource, VideoSampleCursor } from 'mediabunny';
import SampleFileUrl from '../../docs/assets/big-buck-bunny-trimmed.mp4'; import SampleFileUrl from '../../docs/assets/big-buck-bunny-trimmed.mp4';
import { canvasTransformer } from '../../src/cursors.js';
(document.querySelector('#sample-file-download') as HTMLAnchorElement).href = SampleFileUrl; (document.querySelector('#sample-file-download') as HTMLAnchorElement).href = SampleFileUrl;
const selectMediaButton = document.querySelector('#select-file') as HTMLButtonElement; const selectMediaButton = document.querySelector('#select-file') as HTMLButtonElement;
@@ -69,16 +70,23 @@ const generateThumbnails = async (resource: File | string) => {
(_, i) => firstTimestamp + i * (lastTimestamp - firstTimestamp) / THUMBNAIL_COUNT, (_, i) => firstTimestamp + i * (lastTimestamp - firstTimestamp) / THUMBNAIL_COUNT,
); );
// Create a CanvasSink for extracting resized frames from the video track // Create a cursor to extract frames from the video
const sink = new CanvasSink(videoTrack, { const cursor = new VideoSampleCursor(videoTrack, {
width: Math.floor(width * window.devicePixelRatio), transform: canvasTransformer({
height: Math.floor(height * window.devicePixelRatio), width: Math.floor(width * window.devicePixelRatio),
fit: 'fill', height: Math.floor(height * window.devicePixelRatio),
fit: 'fill',
}),
}); });
// Queue all cursor commands at once, including closing the cursor; this allows it to optimize access patterns
const promises = timestamps.map(x => cursor.seekTo(x));
void cursor.close();
// Iterate over all thumbnail canvases // Iterate over all thumbnail canvases
let i = 0; let i = 0;
for await (const wrappedCanvas of sink.canvasesAtTimestamps(timestamps)) { for (const promise of promises) {
const wrappedCanvas = await promise;
const container = thumbnailElements[i]!; const container = thumbnailElements[i]!;
if (wrappedCanvas) { if (wrappedCanvas) {
+1 -1
View File
@@ -930,7 +930,6 @@ export abstract class SampleCursor<
this._lastPendingRequest = pendingRequest; this._lastPendingRequest = pendingRequest;
this._pumpGate.open(); this._pumpGate.open();
deferred.execute(); // Waiting for the return would be too long deferred.execute(); // Waiting for the return would be too long
return res.set(await request.promise); return res.set(await request.promise);
@@ -987,6 +986,7 @@ export abstract class SampleCursor<
} }
this._lastPendingRequest = pendingRequest; this._lastPendingRequest = pendingRequest;
this._pumpGate.open();
lock.release(); // Waiting for the return would be too long lock.release(); // Waiting for the return would be too long
return res.set(await request.promise); return res.set(await request.promise);
+4 -1
View File
@@ -2368,7 +2368,10 @@ abstract class IsobmffTrackBacking implements InputTrackBacking {
} }
async getFirstTimestamp() { async getFirstTimestamp() {
const firstPacket = await this.getFirstPacket({ metadataOnly: true }); const result = new ResultValue<EncodedPacket | null>();
await this.getFirstPacket(result, { metadataOnly: true });
const firstPacket = result.value;
return firstPacket?.timestamp ?? 0; return firstPacket?.timestamp ?? 0;
} }