diff --git a/package.json b/package.json index 9168eea..a7d8c53 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "test": "npx vitest --run", "test-node": "npm run test node/", "test-browser": "npm run test browser/", - "check": "rm -rf dist/modules && tsc -p src && tsc -p packages/mp3-encoder/src --noEmit && tsc -p tsconfig.vitest.json --noEmit && tsc -p scripts --noEmit && tsc -p tsconfig.vite.json --noEmit", + "check": "rm -rf dist/modules && tsc -p src --stripInternal false && tsc -p packages/mp3-encoder/src --noEmit && tsc -p tsconfig.vitest.json --noEmit && tsc -p scripts --noEmit && tsc -p tsconfig.vite.json --noEmit", "check-docblocks": "tsx scripts/check-docblocks.ts dist/mediabunny.d.ts", "docs:dev": "vitepress dev docs", "docs:build": "npm run build && npm run docs:generate && vitepress build docs && npm run examples:build && cp dist/mediabunny.d.ts dist-docs/", diff --git a/test/browser/flac.test.ts b/test/browser/flac.test.ts index ddc065b..dc7e8d5 100644 --- a/test/browser/flac.test.ts +++ b/test/browser/flac.test.ts @@ -2,12 +2,12 @@ import { expect, test } from 'vitest'; import { Input } from '../../src/input.js'; import { BufferSource, UrlSource } from '../../src/source.js'; import { FLAC, WAVE } from '../../src/input-format.js'; -import { AudioSampleSink } from '../../src/media-sink.js'; import { assert } from '../../src/misc.js'; import { Output } from '../../src/output.js'; import { WavOutputFormat } from '../../src/output-format.js'; import { BufferTarget } from '../../src/target.js'; import { Conversion } from '../../src/conversion.js'; +import { AudioSampleCursor } from '../../src/cursors.js'; test('can decode samples from a FLAC file', async () => { using input = new Input({ @@ -17,9 +17,9 @@ test('can decode samples from a FLAC file', async () => { const track = await input.getPrimaryAudioTrack(); assert(track); - const sink = new AudioSampleSink(track); + await using cursor = new AudioSampleCursor(track); - using sample = await sink.getSample(1); + using sample = await cursor.seekTo(1); assert(sample); expect(sample.timestamp).toBe(0.9287981859410431); }); diff --git a/test/browser/transparency.test.ts b/test/browser/transparency.test.ts index 394c827..ab53561 100644 --- a/test/browser/transparency.test.ts +++ b/test/browser/transparency.test.ts @@ -2,7 +2,6 @@ import { expect, test } from 'vitest'; import { Input } from '../../src/input.js'; import { BufferSource, UrlSource } from '../../src/source.js'; import { ALL_FORMATS } from '../../src/input-format.js'; -import { CanvasSink, EncodedPacketSink, VideoSampleSink } from '../../src/media-sink.js'; import { Output } from '../../src/output.js'; import { WebMOutputFormat } from '../../src/output-format.js'; import { BufferTarget } from '../../src/target.js'; @@ -10,6 +9,7 @@ import { CanvasSource, VideoSampleSource } from '../../src/media-source.js'; import { canEncodeVideo, QUALITY_HIGH } from '../../src/encode.js'; import { VideoSample } from '../../src/sample.js'; import { Conversion } from '../../src/conversion.js'; +import { canvasTransformer, PacketCursor, PacketReader, VideoSampleCursor } from '../../src/cursors.js'; test('Can decode transparent video', async () => { using input = new Input({ @@ -20,8 +20,8 @@ test('Can decode transparent video', async () => { const videoTrack = (await input.getPrimaryVideoTrack())!; expect(await videoTrack.canBeTransparent()).toBe(true); - const sink = new VideoSampleSink(videoTrack); - using sample = (await sink.getSample(0.5))!; + await using cursor = new VideoSampleCursor(videoTrack); + using sample = (await cursor.seekTo(0.5))!; expect(sample.format).toContain('A'); // Probably RGBA expect(sample.hasAlpha).toBe(true); @@ -42,15 +42,15 @@ test('Can decode faulty transparent video and behaves gracefully', async () => { }); const videoTrack = (await input.getPrimaryVideoTrack())!; - const packetSink = new EncodedPacketSink(videoTrack); - const secondKeyPacket = (await packetSink.getNextKeyPacket((await packetSink.getFirstPacket())!))!; + const reader = new PacketReader(videoTrack); + const secondKeyPacket = (await reader.readNextKey((await reader.readFirst())!))!; - const sink = new VideoSampleSink(videoTrack); + await using cursor = new VideoSampleCursor(videoTrack); - using startSample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!; + using startSample = (await cursor.seekTo(await videoTrack.getFirstTimestamp()))!; expect(startSample.format).toContain('A'); - using secondSample = (await sink.getSample(secondKeyPacket.timestamp))!; + using secondSample = (await cursor.seekTo(secondKeyPacket.timestamp))!; expect(secondSample.format).not.toContain('A'); // There was no alpha key frame for this one expect(secondSample.hasAlpha).toBe(false); }); @@ -62,8 +62,11 @@ test('Can extract transparent frames via CanvasSink', async () => { }); const videoTrack = (await input.getPrimaryVideoTrack())!; - const sink = new CanvasSink(videoTrack, { alpha: true }); - const wrappedCanvas = (await sink.getCanvas(await videoTrack.getFirstTimestamp()))!; + const cursor = new VideoSampleCursor(videoTrack, { + transform: canvasTransformer({ alpha: true }), + }); + const wrappedCanvas = (await cursor.seekToFirst())!; + await cursor.close(); const canvas = new OffscreenCanvas(wrappedCanvas.canvas.width, wrappedCanvas.canvas.height); const context = canvas.getContext('2d')!; @@ -72,8 +75,11 @@ test('Can extract transparent frames via CanvasSink', async () => { let imageData = context.getImageData(0, 0, canvas.width, canvas.height); expect(imageData.data[3]).toBeLessThan(255); // Check that there's actually transparent pixels - const opaqueSink = new CanvasSink(videoTrack); // Default is alpha: false - const opaqueWrappedCanvas = (await opaqueSink.getCanvas(await videoTrack.getFirstTimestamp()))!; + const opaqueCursor = new VideoSampleCursor(videoTrack, { + transform: canvasTransformer(), // Default is alpha: false + }); + const opaqueWrappedCanvas = (await opaqueCursor.seekToFirst())!; + await opaqueCursor.close(); context.drawImage(opaqueWrappedCanvas.canvas, 0, 0); @@ -162,9 +168,9 @@ test('Can encode transparent video', async () => { const videoTrack = (await input.getPrimaryVideoTrack())!; expect(await videoTrack.canBeTransparent()).toBe(true); - const sink = new VideoSampleSink(videoTrack); + await using cursor = new VideoSampleCursor(videoTrack); - using firstSample = (await sink.getSample(0))!; + using firstSample = (await cursor.seekToFirst())!; expect(firstSample.format).toContain('A'); probeContext.clearRect(0, 0, probeCanvas.width, probeCanvas.height); @@ -219,10 +225,10 @@ test('Can encode video with alternating transparency', async () => { }); const videoTrack = (await input.getPrimaryVideoTrack())!; - const packetSink = new EncodedPacketSink(videoTrack); + const packetCursor = new PacketCursor(videoTrack); let i = 0; - for await (const packet of packetSink.packets()) { + for await (const packet of packetCursor) { if (i % 2) { expect(packet.sideData.alpha).toBeUndefined(); } else { @@ -232,10 +238,10 @@ test('Can encode video with alternating transparency', async () => { i++; } - const sampleSink = new VideoSampleSink(videoTrack); + await using sampleCursor = new VideoSampleCursor(videoTrack); i = 0; - for await (using sample of sampleSink.samples()) { + for await (using sample of sampleCursor) { if (i % 2) { expect(sample.format).not.toContain('A'); } else { @@ -298,8 +304,8 @@ test('Can transmux transparent video, discards alpha by default', async () => { const videoTrack = (await outputInput.getPrimaryVideoTrack())!; expect(await videoTrack.canBeTransparent()).toBe(false); - const sink = new VideoSampleSink(videoTrack); - using sample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!; + await using cursor = new VideoSampleCursor(videoTrack); + using sample = (await cursor.seekToFirst())!; expect(sample.hasAlpha).toBe(false); }); @@ -330,8 +336,8 @@ test('Can transmux transparent video, can keep alpha', async () => { const videoTrack = (await outputInput.getPrimaryVideoTrack())!; expect(await videoTrack.canBeTransparent()).toBe(true); - const sink = new VideoSampleSink(videoTrack); - using sample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!; + await using cursor = new VideoSampleCursor(videoTrack); + using sample = (await cursor.seekToFirst())!; expect(sample.format).toContain('A'); expect(sample.hasAlpha).toBe(true); }); @@ -369,8 +375,8 @@ test('Can reencode transparent video, keeping alpha', async () => { expect(await videoTrack.canBeTransparent()).toBe(true); expect(videoTrack.displayWidth).toBe(320); - const sink = new VideoSampleSink(videoTrack); - using sample = (await sink.getSample(await videoTrack.getFirstTimestamp()))!; + const cursor = new VideoSampleCursor(videoTrack); + using sample = (await cursor.seekToFirst())!; expect(sample.format).toContain('A'); expect(sample.hasAlpha).toBe(true); }); diff --git a/test/node/flac.test.ts b/test/node/flac.test.ts index a7efd4e..1d3145a 100644 --- a/test/node/flac.test.ts +++ b/test/node/flac.test.ts @@ -4,11 +4,11 @@ import { assert, toUint8Array } from '../../src/misc.js'; import { Input } from '../../src/input.js'; import { BufferSource, FilePathSource } from '../../src/source.js'; import { ALL_FORMATS, FLAC } from '../../src/input-format.js'; -import { EncodedPacketSink } from '../../src/media-sink.js'; import { Output } from '../../src/output.js'; import { BufferTarget } from '../../src/target.js'; import { FlacOutputFormat } from '../../src/output-format.js'; import { Conversion } from '../../src/conversion.js'; +import { PacketCursor, PacketReader } from '../../src/cursors.js'; const __dirname = new URL('.', import.meta.url).pathname; @@ -36,10 +36,11 @@ test('can loop over all samples', async () => { expect(track.timeResolution).toEqual(44100); expect(await input.getMimeType()).toEqual('audio/flac'); - const sink = new EncodedPacketSink(track); + const cursor = new PacketCursor(track); + let samples = 0; let lastSampleTimestamp = 0; - for await (const sample of sink.packets()) { + for await (const sample of cursor) { samples++; lastSampleTimestamp = sample.timestamp; if (sample.sequenceNumber === 212) { @@ -63,23 +64,24 @@ test('can do random access', async () => { const track = await input.getPrimaryAudioTrack(); assert(track); - const packetSink = new EncodedPacketSink(track); - const packet = await packetSink.getPacket(10); + const reader = new PacketReader(track); + + const packet = await reader.readAt(10); assert(packet); expect(packet.timestamp).toBe(9.93814058956916); expect(packet.data.byteLength).toBe(8345); expect(packet.sequenceNumber).toBe(107); expect(packet.duration).toBe(0.09287981859410431); - const nextPacket = await packetSink.getNextPacket(packet); + const nextPacket = await reader.readNext(packet); assert(nextPacket); expect(nextPacket.timestamp).toBe(10.031020408163265); expect(nextPacket.data.byteLength).toBe(8988); expect(nextPacket.sequenceNumber).toBe(108); expect(nextPacket.duration).toBe(0.09287981859410431); - const priorPacket = await packetSink.getPacket(3); + const priorPacket = await reader.readAt(3); assert(priorPacket); expect(priorPacket.timestamp).toBe(2.972154195011338); expect(priorPacket.data.byteLength).toBe(6877); @@ -96,9 +98,10 @@ test('can get metadata-only packets', async () => { const track = await input.getPrimaryAudioTrack(); assert(track); - const packetSink = new EncodedPacketSink(track); - const packet = await packetSink.getPacket(10, { metadataOnly: true }); + const reader = new PacketReader(track); + + const packet = await reader.readAt(10, { metadataOnly: true }); assert(packet); expect(packet.timestamp).toBe(9.93814058956916); expect(packet.isMetadataOnly).toBe(true); @@ -209,11 +212,13 @@ test('can re-mux a .flac', async () => { images: inputImages, }); - const inputPacketSink = new EncodedPacketSink(inputTrack); - const outputPacketSink = new EncodedPacketSink(outputTrack); + const inputReader = new PacketReader(inputTrack); + const outputReader = new PacketReader(outputTrack); + const outputCursor = new PacketCursor(outputTrack); + let packets = 0; let timestamp = 0; - for await (const packet of outputPacketSink.packets()) { + for await (const packet of outputCursor) { packets++; timestamp = packet.timestamp; } @@ -222,8 +227,8 @@ test('can re-mux a .flac', async () => { expect(timestamp).toBe(19.690521541950112); // Test that packets are byte-identical - const inputPacket = await inputPacketSink.getPacket(10); - const outputPacket = await outputPacketSink.getPacket(10); + const inputPacket = await inputReader.readAt(10); + const outputPacket = await outputReader.readAt(10); assert(inputPacket); assert(outputPacket); diff --git a/test/node/read-mp4.test.ts b/test/node/read-mp4.test.ts index d986ed3..5a2dec7 100644 --- a/test/node/read-mp4.test.ts +++ b/test/node/read-mp4.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'vitest'; import path from 'node:path'; -import { ALL_FORMATS, MP4, EncodedPacketSink, Input, FilePathSource } from '../../src/index.js'; +import { ALL_FORMATS, MP4, Input, FilePathSource, PacketCursor } from '../../src/index.js'; const __dirname = new URL('.', import.meta.url).pathname; @@ -18,12 +18,12 @@ test('Should be able to get packets from a .MP4 file', async () => { const track = await input.getPrimaryVideoTrack(); if (!track) throw new Error('No video track found'); - const sink = new EncodedPacketSink(track); + const cursor = new PacketCursor(track); let samples = 0; const timestamps: number[] = []; - for await (const packet of sink.packets()) { + for await (const packet of cursor) { timestamps.push(packet.timestamp); samples++; }