mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 02:43:48 +02:00
Merge branch 'main' into hls
This commit is contained in:
+1
-1
@@ -223,7 +223,7 @@
|
||||
start,
|
||||
end: start + 5,
|
||||
//start: 0,
|
||||
//end: 10
|
||||
end: 10
|
||||
},
|
||||
});
|
||||
console.log(conversion);
|
||||
|
||||
+4
-4
@@ -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();
|
||||
|
||||
@@ -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' }],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://mediabunny.dev/sitemap.xml
|
||||
Generated
+9
-9
@@ -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"
|
||||
|
||||
+1
-1
@@ -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": [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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. */
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
+48
-3
@@ -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 <video> element instead.
|
||||
const video = document.createElement('video');
|
||||
video.style.position = 'fixed';
|
||||
video.style.left = '-10000px';
|
||||
video.style.top = '-10000px';
|
||||
video.style.width = '1px';
|
||||
video.style.height = '1px';
|
||||
video.style.opacity = '0';
|
||||
video.style.pointerEvents = 'none';
|
||||
video.muted = true;
|
||||
video.srcObject = new MediaStream([this._track]);
|
||||
document.body.appendChild(video);
|
||||
this._videoElement = video;
|
||||
|
||||
video.addEventListener('loadeddata', () => {
|
||||
if (errored || !this._videoElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This will be the first frame
|
||||
const frame = new VideoFrame(video, {
|
||||
timestamp: 1000 * performance.now(),
|
||||
});
|
||||
onVideoFrame(frame);
|
||||
frame.close();
|
||||
}, { once: true });
|
||||
|
||||
void video.play().catch((error) => {
|
||||
errored = true;
|
||||
this._promiseWithResolvers.reject(error);
|
||||
});
|
||||
} else {
|
||||
throw new Error('MediaStreamTrackProcessor is required but not supported by this browser.');
|
||||
throw new Error(
|
||||
'When no explicit frame rate is set, MediaStreamTrackProcessor is required; but it\'s not supported'
|
||||
+ ' by this browser.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1329,6 +1368,12 @@ export class MediaStreamVideoTrackSource extends VideoSource {
|
||||
}
|
||||
this._lastVideoFrame?.close();
|
||||
|
||||
if (this._videoElement) {
|
||||
this._videoElement.srcObject = null;
|
||||
this._videoElement.remove();
|
||||
this._videoElement = null;
|
||||
}
|
||||
|
||||
if (this._workerTrackId !== null) {
|
||||
assert(this._workerListener);
|
||||
|
||||
|
||||
@@ -175,10 +175,10 @@ export class MpegTsDemuxer extends Demuxer {
|
||||
// MPEG-TS with Forward Error Correction
|
||||
this.packetOffset = 0;
|
||||
this.packetStride = TS_PACKET_SIZE + 16;
|
||||
} else if (startingBytes[4] === 0x47 && startingBytes[4 + TS_PACKET_SIZE] === 0x47) {
|
||||
} else if (startingBytes[4] === 0x47 && startingBytes[4 + TS_PACKET_SIZE + 4] === 0x47) {
|
||||
// MPEG-2-TS (DVHS)
|
||||
this.packetOffset = 4;
|
||||
this.packetStride = TS_PACKET_SIZE;
|
||||
this.packetStride = TS_PACKET_SIZE + 4;
|
||||
} else {
|
||||
throw new Error('Unreachable.');
|
||||
}
|
||||
|
||||
+21
-18
@@ -46,12 +46,16 @@ export abstract class Muxer {
|
||||
|
||||
private trackTimestampInfo = new WeakMap<OutputTrack, {
|
||||
maxTimestamp: number;
|
||||
maxTimestampBeforeLastKeyPacket: number;
|
||||
maxTimestampBeforeLastKeyPacket: number | null;
|
||||
}>();
|
||||
|
||||
protected validateAndNormalizeTimestamp(track: OutputTrack, timestampInSeconds: number, isKeyPacket: boolean) {
|
||||
timestampInSeconds += track.source._timestampOffset;
|
||||
|
||||
if (timestampInSeconds < 0) {
|
||||
throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`);
|
||||
}
|
||||
|
||||
let timestampInfo = this.trackTimestampInfo.get(track);
|
||||
if (!timestampInfo) {
|
||||
if (!isKeyPacket) {
|
||||
@@ -60,29 +64,28 @@ export abstract class Muxer {
|
||||
|
||||
timestampInfo = {
|
||||
maxTimestamp: timestampInSeconds,
|
||||
maxTimestampBeforeLastKeyPacket: timestampInSeconds,
|
||||
maxTimestampBeforeLastKeyPacket: null,
|
||||
};
|
||||
this.trackTimestampInfo.set(track, timestampInfo);
|
||||
}
|
||||
} else {
|
||||
if (isKeyPacket) {
|
||||
timestampInfo.maxTimestampBeforeLastKeyPacket = timestampInfo.maxTimestamp;
|
||||
}
|
||||
|
||||
if (timestampInSeconds < 0) {
|
||||
throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`);
|
||||
}
|
||||
if (
|
||||
timestampInfo.maxTimestampBeforeLastKeyPacket !== null
|
||||
&& timestampInSeconds < timestampInfo.maxTimestampBeforeLastKeyPacket
|
||||
) {
|
||||
throw new Error(
|
||||
`Timestamps cannot be smaller than the largest timestamp of the previous GOP (a GOP begins with a`
|
||||
+ ` key packet and ends right before the next key packet). Got ${timestampInSeconds}s, but largest`
|
||||
+ ` timestamp is ${timestampInfo.maxTimestampBeforeLastKeyPacket}s.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isKeyPacket) {
|
||||
timestampInfo.maxTimestampBeforeLastKeyPacket = timestampInfo.maxTimestamp;
|
||||
timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds);
|
||||
}
|
||||
|
||||
if (timestampInSeconds < timestampInfo.maxTimestampBeforeLastKeyPacket) {
|
||||
throw new Error(
|
||||
`Timestamps cannot be smaller than the largest timestamp of the previous GOP (a GOP begins with a key`
|
||||
+ ` packet and ends right before the next key packet). Got ${timestampInSeconds}s, but largest`
|
||||
+ ` timestamp is ${timestampInfo.maxTimestampBeforeLastKeyPacket}s.`,
|
||||
);
|
||||
}
|
||||
|
||||
timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds);
|
||||
|
||||
return timestampInSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user