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