diff --git a/dev/convert.html b/dev/convert.html index cdcccb2..ec051d3 100644 --- a/dev/convert.html +++ b/dev/convert.html @@ -223,7 +223,7 @@ start, end: start + 5, //start: 0, - //end: 10 + end: 10 }, }); console.log(conversion); diff --git a/dev/live.html b/dev/live.html index bdd2fc2..d21faec 100644 --- a/dev/live.html +++ b/dev/live.html @@ -19,7 +19,7 @@ button.addEventListener('click', async () => { const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false }); const videoTrack = stream.getVideoTracks()[0]; - const audioTrack = null;//stream.getAudioTracks()[0]; + const audioTrack = stream.getAudioTracks()[0]; const output = new Mediabunny.Output({ target: new Mediabunny.BufferTarget(), @@ -29,7 +29,7 @@ let audioSource = null; if (videoTrack) { videoSource = new Mediabunny.MediaStreamVideoTrackSource(videoTrack, { - codec: 'avc', + codec: 'vp9', bitrate: Mediabunny.QUALITY_MEDIUM, sizeChangeBehavior: 'passThrough', }); @@ -40,13 +40,13 @@ } if (audioTrack) { audioSource = new Mediabunny.MediaStreamAudioTrackSource(audioTrack, { - codec: 'mp3', + codec: 'opus', bitrate: Mediabunny.QUALITY_MEDIUM }); audioSource.errorPromise.catch((d) => console.log("Hello!!???", d)); - //output.addAudioTrack(audioSource); + output.addAudioTrack(audioSource); } await output.start(); diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 68e6167..5bc8da9 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -17,6 +17,7 @@ export default withMermaid({ sitemap: { hostname: 'https://mediabunny.dev', }, + lastUpdated: true, head: [ ['link', { rel: 'icon', type: 'image/png', href: '/mediabunny-logo.png' }], ['link', { rel: 'icon', type: 'image/svg+xml', href: '/mediabunny-logo.svg' }], diff --git a/docs/guide/media-sinks.md b/docs/guide/media-sinks.md index 84747cb..077630a 100644 --- a/docs/guide/media-sinks.md +++ b/docs/guide/media-sinks.md @@ -97,9 +97,13 @@ await sink.getKeyPacket(5); // => EncodedPacket | null When retrieving a packet using a timestamp, the last packet (in [presentation order](#decode-vs-presentation-order)) with a timestamp less than or equal to the search timestamp will be returned. The methods return `null` if there exists no such packet. -There is a special method for retrieving the first packet (in [decode order](#decode-vs-presentation-order)): +There are special methods for retrieving the first packet (in [decode order](#decode-vs-presentation-order)): ```ts await sink.getFirstPacket(); // => EncodedPacket | null + +// The first packet is typically a key frame, but this is not required. +// This method returns the first key frame: +await sink.getFirstKeyPacket(); // => EncodedPacket | null ``` The last packet (in [presentation order](#decode-vs-presentation-order)) can be retrieved like so: ```ts diff --git a/docs/public/robots.txt b/docs/public/robots.txt new file mode 100644 index 0000000..32ad9f2 --- /dev/null +++ b/docs/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://mediabunny.dev/sitemap.xml \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3c36243..f003e96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mediabunny", - "version": "1.38.0", + "version": "1.39.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mediabunny", - "version": "1.38.0", + "version": "1.39.2", "license": "MPL-2.0", "workspaces": [ "packages/*" @@ -7751,9 +7751,9 @@ } }, "node_modules/mediabunny": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.37.0.tgz", - "integrity": "sha512-eV7M9IJ29pr/8RNL1sYtIxNbdMfDMN1hMwMaOFfNLhwuKKGSC+eKwiJFpdVjEJ3zrMA4LGerF4Hps0SENFSAlg==", + "version": "1.39.1", + "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.39.1.tgz", + "integrity": "sha512-gaQKDWtfLhgSN9tRj37lgznHf9MOvey7sbT6GdQA7mWl1kaE5R+P1lfl9CDsgtzongx12IG9X/y2vgZmTHzGDg==", "license": "MPL-2.0", "peer": true, "workspaces": [ @@ -12077,7 +12077,7 @@ }, "packages/aac-encoder": { "name": "@mediabunny/aac-encoder", - "version": "1.38.0", + "version": "1.39.2", "license": "MPL-2.0", "devDependencies": { "@types/emscripten": "^1.40.1" @@ -12092,7 +12092,7 @@ }, "packages/ac3": { "name": "@mediabunny/ac3", - "version": "1.38.0", + "version": "1.39.2", "license": "MPL-2.0", "devDependencies": { "@types/emscripten": "^1.40.1" @@ -12107,7 +12107,7 @@ }, "packages/flac-encoder": { "name": "@mediabunny/flac-encoder", - "version": "1.38.0", + "version": "1.39.2", "license": "MPL-2.0", "devDependencies": { "@types/emscripten": "^1.40.1" @@ -12122,7 +12122,7 @@ }, "packages/mp3-encoder": { "name": "@mediabunny/mp3-encoder", - "version": "1.38.0", + "version": "1.39.2", "license": "MPL-2.0", "devDependencies": { "@types/emscripten": "^1.40.1" diff --git a/package.json b/package.json index 669a648..4fb193f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mediabunny", "author": "Vanilagy", - "version": "1.38.0", + "version": "1.39.2", "description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.", "type": "module", "workspaces": [ diff --git a/packages/aac-encoder/package.json b/packages/aac-encoder/package.json index 786a388..faa9753 100644 --- a/packages/aac-encoder/package.json +++ b/packages/aac-encoder/package.json @@ -1,7 +1,7 @@ { "name": "@mediabunny/aac-encoder", "author": "Vanilagy", - "version": "1.38.0", + "version": "1.39.2", "description": "AAC encoder extension for Mediabunny, based on FFmpeg.", "main": "./dist/bundles/mediabunny-aac-encoder.mjs", "module": "./dist/bundles/mediabunny-aac-encoder.mjs", diff --git a/packages/ac3/package.json b/packages/ac3/package.json index de7d2d4..bd5b58c 100644 --- a/packages/ac3/package.json +++ b/packages/ac3/package.json @@ -1,7 +1,7 @@ { "name": "@mediabunny/ac3", "author": "Vanilagy", - "version": "1.38.0", + "version": "1.39.2", "description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on FFmpeg.", "main": "./dist/bundles/mediabunny-ac3.mjs", "module": "./dist/bundles/mediabunny-ac3.mjs", diff --git a/packages/flac-encoder/package.json b/packages/flac-encoder/package.json index ef0109d..fe3aac3 100644 --- a/packages/flac-encoder/package.json +++ b/packages/flac-encoder/package.json @@ -1,7 +1,7 @@ { "name": "@mediabunny/flac-encoder", "author": "Vanilagy", - "version": "1.38.0", + "version": "1.39.2", "description": "FLAC encoder extension for Mediabunny, based on libFLAC.", "main": "./dist/bundles/mediabunny-flac-encoder.mjs", "module": "./dist/bundles/mediabunny-flac-encoder.mjs", diff --git a/packages/mp3-encoder/package.json b/packages/mp3-encoder/package.json index 6128c54..522f6a3 100644 --- a/packages/mp3-encoder/package.json +++ b/packages/mp3-encoder/package.json @@ -1,7 +1,7 @@ { "name": "@mediabunny/mp3-encoder", "author": "Vanilagy", - "version": "1.38.0", + "version": "1.39.2", "description": "MP3 encoder extension for Mediabunny, based on LAME.", "main": "./dist/bundles/mediabunny-mp3-encoder.mjs", "module": "./dist/bundles/mediabunny-mp3-encoder.mjs", diff --git a/src/conversion.ts b/src/conversion.ts index 46fc182..298ce6b 100644 --- a/src/conversion.ts +++ b/src/conversion.ts @@ -515,6 +515,9 @@ export class Conversion { /** * Whether this conversion, as it has been configured, is valid and can be executed. If this field is `false`, check * the `discardedTracks` field for reasons. + * + * Note: a conversion having discarded tracks does not automatically mean it is invalid; if the remaining, utilized + * tracks make for a valid output file, the conversion is still allowed. */ isValid = false; /** The list of tracks that are included in the output file. */ diff --git a/src/input-format.ts b/src/input-format.ts index e7cb1a6..9a54dc3 100644 --- a/src/input-format.ts +++ b/src/input-format.ts @@ -542,7 +542,7 @@ export class MpegTsInputFormat extends InputFormat { } else if (bytes[0] === 0x47 && bytes[TS_PACKET_SIZE + 16] === 0x47) { // MPEG-TS with Forward Error Correction return true; - } else if (bytes[4] === 0x47 && bytes[4 + TS_PACKET_SIZE] === 0x47) { + } else if (bytes[4] === 0x47 && bytes[4 + TS_PACKET_SIZE + 4] === 0x47) { // MPEG-2-TS (DVHS) return true; } diff --git a/src/media-sink.ts b/src/media-sink.ts index dcebc04..23c4be2 100644 --- a/src/media-sink.ts +++ b/src/media-sink.ts @@ -529,7 +529,7 @@ export abstract class BaseMediaSampleSink< const packetSink = this._createPacketSink(); const keyPacket = await packetSink.getKeyPacket(startTimestamp, { verifyKeyPackets: true }) - ?? await packetSink.getFirstKeyPacket(); + ?? await packetSink.getFirstKeyPacket({ verifyKeyPackets: true }); let currentPacket: EncodedPacket | null = keyPacket; diff --git a/src/media-source.ts b/src/media-source.ts index c3b4604..e47679b 100644 --- a/src/media-source.ts +++ b/src/media-source.ts @@ -1076,6 +1076,8 @@ export class MediaStreamVideoTrackSource extends VideoSource { private _lastVideoFrame: VideoFrame | null = null; /** @internal */ private _timerHandle: UnthrottledTimerHandle | null = null; + /** @internal */ + private _videoElement: HTMLVideoElement | null = null; /** A promise that rejects upon any error within this source. This promise never resolves. */ get errorPromise() { @@ -1160,12 +1162,13 @@ export class MediaStreamVideoTrackSource extends VideoSource { while (now - lastFrameTime > 1000 / frameRate) { lastFrameTime += 1000 / frameRate; const timestamp = firstVideoFrameTimestamp + frameCount / frameRate; - const clone = new VideoFrame(this._lastVideoFrame, { + + const frame = new VideoFrame(this._videoElement ?? this._lastVideoFrame, { timestamp: 1e6 * timestamp, duration: 1e6 / frameRate, }); - addVideoFrame(clone, now); + addVideoFrame(frame, now); } }; @@ -1298,8 +1301,44 @@ export class MediaStreamVideoTrackSource extends VideoSource { }; mediaStreamTrackProcessorWorker!.addEventListener('message', this._workerListener); + } else if (frameRate !== null) { + // No MediaStreamTrackProcessor support at all (e.g. Firefox), but we have a frame rate, so we can + // manually sample from a hidden