mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 21:33:52 +02:00
Merge main into release for tag v1.27.5
This commit is contained in:
+2
-1
@@ -100,6 +100,7 @@
|
|||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
video: () => ({
|
video: () => ({
|
||||||
|
forceTranscode: true,
|
||||||
allowRotationMetadata: false,
|
allowRotationMetadata: false,
|
||||||
//width: 720,
|
//width: 720,
|
||||||
//frameRate: 30,
|
//frameRate: 30,
|
||||||
@@ -181,7 +182,7 @@
|
|||||||
},
|
},
|
||||||
trim: {
|
trim: {
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 4
|
//end: 4
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(conversion);
|
console.log(conversion);
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ Sometimes, you may want to cancel an ongoing conversion process. For this, use t
|
|||||||
await conversion.cancel(); // Resolves once the conversion is canceled
|
await conversion.cancel(); // Resolves once the conversion is canceled
|
||||||
```
|
```
|
||||||
|
|
||||||
This automatically frees up all resources used by the conversion process.
|
This automatically frees up all resources used by the conversion process and will cause any ongoing call to `execute` to throw a `ConversionCanceledError`.
|
||||||
|
|
||||||
## Video options
|
## Video options
|
||||||
|
|
||||||
|
|||||||
Generated
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.27.4",
|
"version": "1.27.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.27.4",
|
"version": "1.27.5",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@@ -7739,9 +7739,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mediabunny": {
|
"node_modules/mediabunny": {
|
||||||
"version": "1.27.3",
|
"version": "1.27.4",
|
||||||
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.27.3.tgz",
|
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.27.4.tgz",
|
||||||
"integrity": "sha512-hlzmgzMznp9DhA5fMJKS5yEAyfCUMxAc+DbSPxD4J1J2cYVl1L+pZLndkt5xLlD5aB5eHEnphHMW14ammMlUXg==",
|
"integrity": "sha512-j69K0yiXYMHyb5g4XyhYjbz1VpllRVyyDGF5lqYTsrdF0XoDRoGReoRvon9lnENv5yY5A5u+09dnqISwXvS86Q==",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
@@ -12065,7 +12065,7 @@
|
|||||||
},
|
},
|
||||||
"packages/mp3-encoder": {
|
"packages/mp3-encoder": {
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"version": "1.27.4",
|
"version": "1.27.5",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/emscripten": "^1.40.1"
|
"@types/emscripten": "^1.40.1"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.27.4",
|
"version": "1.27.5",
|
||||||
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.27.4",
|
"version": "1.27.5",
|
||||||
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
||||||
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
|
|||||||
+21
-2
@@ -822,7 +822,7 @@ export class Conversion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._canceled) {
|
if (this._canceled) {
|
||||||
await new Promise(() => {}); // Never resolve
|
throw new ConversionCanceledError();
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.output.finalize();
|
await this.output.finalize();
|
||||||
@@ -832,7 +832,10 @@ export class Conversion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cancels the conversion process. Does nothing if the conversion is already complete. */
|
/**
|
||||||
|
* Cancels the conversion process, causing any ongoing `execute` call to throw a `ConversionCanceledError`.
|
||||||
|
* Does nothing if the conversion is already complete.
|
||||||
|
*/
|
||||||
async cancel() {
|
async cancel() {
|
||||||
if (this.output.state === 'finalizing' || this.output.state === 'finalized') {
|
if (this.output.state === 'finalizing' || this.output.state === 'finalized') {
|
||||||
return;
|
return;
|
||||||
@@ -1159,6 +1162,7 @@ export class Conversion {
|
|||||||
|
|
||||||
for await (const sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
|
for await (const sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
|
||||||
if (this._canceled) {
|
if (this._canceled) {
|
||||||
|
sample.close();
|
||||||
lastSample?.close();
|
lastSample?.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1441,6 +1445,7 @@ export class Conversion {
|
|||||||
const sink = new AudioSampleSink(track);
|
const sink = new AudioSampleSink(track);
|
||||||
for await (const sample of sink.samples(undefined, this._endTimestamp)) {
|
for await (const sample of sink.samples(undefined, this._endTimestamp)) {
|
||||||
if (this._canceled) {
|
if (this._canceled) {
|
||||||
|
sample.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1551,6 +1556,7 @@ export class Conversion {
|
|||||||
|
|
||||||
for await (const sample of iterator) {
|
for await (const sample of iterator) {
|
||||||
if (this._canceled) {
|
if (this._canceled) {
|
||||||
|
sample.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1589,6 +1595,19 @@ export class Conversion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a conversion couldn't complete due to being canceled.
|
||||||
|
* @group Conversion
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class ConversionCanceledError extends Error {
|
||||||
|
/** Creates a new {@link ConversionCanceledError}. */
|
||||||
|
constructor(message = 'Conversion has been canceled.') {
|
||||||
|
super(message);
|
||||||
|
this.name = 'ConversionCanceledError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const MAX_TIMESTAMP_GAP = 5;
|
const MAX_TIMESTAMP_GAP = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ export {
|
|||||||
ConversionOptions,
|
ConversionOptions,
|
||||||
ConversionVideoOptions,
|
ConversionVideoOptions,
|
||||||
ConversionAudioOptions,
|
ConversionAudioOptions,
|
||||||
|
ConversionCanceledError,
|
||||||
DiscardedTrack,
|
DiscardedTrack,
|
||||||
} from './conversion';
|
} from './conversion';
|
||||||
export {
|
export {
|
||||||
|
|||||||
+2
-2
@@ -155,7 +155,7 @@ export class MatroskaInputFormat extends InputFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dataSize = readElementSize(headerSlice);
|
const dataSize = readElementSize(headerSlice);
|
||||||
if (dataSize === null) {
|
if (typeof dataSize !== 'number') {
|
||||||
return false; // Miss me with that shit
|
return false; // Miss me with that shit
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ export class MatroskaInputFormat extends InputFormat {
|
|||||||
|
|
||||||
const { id, size } = header;
|
const { id, size } = header;
|
||||||
const dataStartPos = dataSlice.filePos;
|
const dataStartPos = dataSlice.filePos;
|
||||||
if (size === null) return false;
|
if (size === undefined) return false;
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case EBMLId.EBMLVersion: {
|
case EBMLId.EBMLVersion: {
|
||||||
|
|||||||
+48
-11
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { MediaCodec } from '../codec';
|
import { MediaCodec } from '../codec';
|
||||||
import { assertNever, textDecoder, textEncoder } from '../misc';
|
import { assert, assertNever, textDecoder, textEncoder } from '../misc';
|
||||||
import { FileSlice, readBytes, Reader, readF32Be, readF64Be, readU8 } from '../reader';
|
import { FileSlice, readBytes, Reader, readF32Be, readF64Be, readU8 } from '../reader';
|
||||||
import { Writer } from '../writer';
|
import { Writer } from '../writer';
|
||||||
|
|
||||||
@@ -470,6 +470,10 @@ export const MIN_HEADER_SIZE = 2; // 1-byte ID and 1-byte size
|
|||||||
export const MAX_HEADER_SIZE = 2 * MAX_VAR_INT_SIZE; // 8-byte ID and 8-byte size
|
export const MAX_HEADER_SIZE = 2 * MAX_VAR_INT_SIZE; // 8-byte ID and 8-byte size
|
||||||
|
|
||||||
export const readVarIntSize = (slice: FileSlice) => {
|
export const readVarIntSize = (slice: FileSlice) => {
|
||||||
|
if (slice.remainingLength < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const firstByte = readU8(slice);
|
const firstByte = readU8(slice);
|
||||||
slice.skip(-1);
|
slice.skip(-1);
|
||||||
|
|
||||||
@@ -484,10 +488,19 @@ export const readVarIntSize = (slice: FileSlice) => {
|
|||||||
mask >>= 1;
|
mask >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if we have enough bytes to read the full varint
|
||||||
|
if (slice.remainingLength < width) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return width;
|
return width;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readVarInt = (slice: FileSlice) => {
|
export const readVarInt = (slice: FileSlice) => {
|
||||||
|
if (slice.remainingLength < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Read the first byte to determine the width of the variable-length integer
|
// Read the first byte to determine the width of the variable-length integer
|
||||||
const firstByte = readU8(slice);
|
const firstByte = readU8(slice);
|
||||||
|
|
||||||
@@ -503,6 +516,11 @@ export const readVarInt = (slice: FileSlice) => {
|
|||||||
mask >>= 1;
|
mask >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (slice.remainingLength < width - 1) {
|
||||||
|
// Not enough bytes
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// First byte's value needs the marker bit cleared
|
// First byte's value needs the marker bit cleared
|
||||||
let value = firstByte & (mask - 1);
|
let value = firstByte & (mask - 1);
|
||||||
|
|
||||||
@@ -563,18 +581,33 @@ export const readElementId = (slice: FileSlice) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (slice.remainingLength < size) {
|
||||||
|
return null; // It don't fit
|
||||||
|
}
|
||||||
|
|
||||||
const id = readUnsignedInt(slice, size);
|
const id = readUnsignedInt(slice, size);
|
||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readElementSize = (slice: FileSlice) => {
|
/** Returns `undefined` to indicate the EBML undefined size. Returns `null` if the size couldn't be read. */
|
||||||
let size: number | null = readU8(slice);
|
export const readElementSize = (slice: FileSlice): number | undefined | null => {
|
||||||
|
// Need at least 1 byte to read the size
|
||||||
|
if (slice.remainingLength < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstByte = readU8(slice);
|
||||||
|
|
||||||
|
if (firstByte === 0xff) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (size === 0xff) {
|
|
||||||
size = null;
|
|
||||||
} else {
|
|
||||||
slice.skip(-1);
|
slice.skip(-1);
|
||||||
size = readVarInt(slice);
|
const size = readVarInt(slice);
|
||||||
|
|
||||||
|
if (size === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// In some (livestreamed) files, this is the value of the size field. While this technically is just a very
|
// In some (livestreamed) files, this is the value of the size field. While this technically is just a very
|
||||||
// large number, it is intended to behave like the reserved size 0xFF, meaning the size is undefined. We
|
// large number, it is intended to behave like the reserved size 0xFF, meaning the size is undefined. We
|
||||||
@@ -582,20 +615,24 @@ export const readElementSize = (slice: FileSlice) => {
|
|||||||
// nonetheless.
|
// nonetheless.
|
||||||
// eslint-disable-next-line no-loss-of-precision
|
// eslint-disable-next-line no-loss-of-precision
|
||||||
if (size === 0x00ffffffffffffff) {
|
if (size === 0x00ffffffffffffff) {
|
||||||
size = null;
|
return undefined;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readElementHeader = (slice: FileSlice) => {
|
export const readElementHeader = (slice: FileSlice) => {
|
||||||
|
assert(slice.remainingLength >= MIN_HEADER_SIZE);
|
||||||
|
|
||||||
const id = readElementId(slice);
|
const id = readElementId(slice);
|
||||||
if (id === null) {
|
if (id === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size = readElementSize(slice);
|
const size = readElementSize(slice);
|
||||||
|
if (size === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return { id, size };
|
return { id, size };
|
||||||
};
|
};
|
||||||
@@ -720,8 +757,8 @@ export const CODEC_STRING_MAP: Partial<Record<MediaCodec, string>> = {
|
|||||||
'webvtt': 'S_TEXT/WEBVTT',
|
'webvtt': 'S_TEXT/WEBVTT',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function assertDefinedSize(size: number | null): asserts size is number {
|
export function assertDefinedSize(size: number | undefined): asserts size is number {
|
||||||
if (size === null) {
|
if (size === undefined) {
|
||||||
throw new Error('Undefined element size is used in a place where it is not supported.');
|
throw new Error('Undefined element size is used in a place where it is not supported.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ type InternalTrack = {
|
|||||||
codecId: string | null;
|
codecId: string | null;
|
||||||
codecPrivate: Uint8Array | null;
|
codecPrivate: Uint8Array | null;
|
||||||
defaultDuration: number | null;
|
defaultDuration: number | null;
|
||||||
|
defaultDurationNs: number | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
languageCode: string;
|
languageCode: string;
|
||||||
decodingInstructions: DecodingInstruction[];
|
decodingInstructions: DecodingInstruction[];
|
||||||
@@ -346,7 +347,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
} else if (id === EBMLId.Segment) { // Segment found!
|
} else if (id === EBMLId.Segment) { // Segment found!
|
||||||
await this.readSegment(dataStartPos, size);
|
await this.readSegment(dataStartPos, size);
|
||||||
|
|
||||||
if (size === null) {
|
if (size === undefined) {
|
||||||
// Segment sizes can be undefined (common in livestreamed files), so assume this is the last
|
// Segment sizes can be undefined (common in livestreamed files), so assume this is the last
|
||||||
// and only segment
|
// and only segment
|
||||||
break;
|
break;
|
||||||
@@ -364,7 +365,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
// doesn't contain any of the clusters that follow it. In the case, we apply the following logic: if
|
// doesn't contain any of the clusters that follow it. In the case, we apply the following logic: if
|
||||||
// we find a top-level cluster, attribute it to the previous segment.
|
// we find a top-level cluster, attribute it to the previous segment.
|
||||||
|
|
||||||
if (size === null) {
|
if (size === undefined) {
|
||||||
// Just in case this is one of those weird sizeless clusters, let's do our best and still try to
|
// Just in case this is one of those weird sizeless clusters, let's do our best and still try to
|
||||||
// determine its size.
|
// determine its size.
|
||||||
const nextElementPos = await searchForNextElementId(
|
const nextElementPos = await searchForNextElementId(
|
||||||
@@ -389,7 +390,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
async readSegment(segmentDataStart: number, dataSize: number | null) {
|
async readSegment(segmentDataStart: number, dataSize: number | undefined) {
|
||||||
this.currentSegment = {
|
this.currentSegment = {
|
||||||
seekHeadSeen: false,
|
seekHeadSeen: false,
|
||||||
infoSeen: false,
|
infoSeen: false,
|
||||||
@@ -406,7 +407,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
cuePoints: [],
|
cuePoints: [],
|
||||||
|
|
||||||
dataStartPos: segmentDataStart,
|
dataStartPos: segmentDataStart,
|
||||||
elementEndPos: dataSize === null
|
elementEndPos: dataSize === undefined
|
||||||
? null // Assume it goes until the end of the file
|
? null // Assume it goes until the end of the file
|
||||||
: segmentDataStart + dataSize,
|
: segmentDataStart + dataSize,
|
||||||
clusterSeekStartPos: segmentDataStart,
|
clusterSeekStartPos: segmentDataStart,
|
||||||
@@ -483,7 +484,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
break; // Stop at the first cluster
|
break; // Stop at the first cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size === null) {
|
if (size === undefined) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
currentPos = dataStartPos + size;
|
currentPos = dataStartPos + size;
|
||||||
@@ -536,6 +537,13 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
this.currentSegment.timestampFactor = 1e9 / 1e6;
|
this.currentSegment.timestampFactor = 1e9 / 1e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute default duration for all tracks now that we have the timestamp factor
|
||||||
|
for (const track of this.currentSegment.tracks) {
|
||||||
|
if (track.defaultDurationNs !== null) {
|
||||||
|
track.defaultDuration = (this.currentSegment.timestampFactor * track.defaultDurationNs) / 1e9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Put default tracks first
|
// Put default tracks first
|
||||||
this.currentSegment.tracks.sort((a, b) => Number(b.disposition.default) - Number(a.disposition.default));
|
this.currentSegment.tracks.sort((a, b) => Number(b.disposition.default) - Number(a.disposition.default));
|
||||||
|
|
||||||
@@ -606,7 +614,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
let size = elementHeader.size;
|
let size = elementHeader.size;
|
||||||
const dataStartPos = headerSlice.filePos;
|
const dataStartPos = headerSlice.filePos;
|
||||||
|
|
||||||
if (size === null) {
|
if (size === undefined) {
|
||||||
// The cluster's size is undefined (can happen in livestreamed files). We'd still like to know the size of
|
// The cluster's size is undefined (can happen in livestreamed files). We'd still like to know the size of
|
||||||
// it, so we have no other choice but to iterate over the EBML structure until we find an element at level
|
// it, so we have no other choice but to iterate over the EBML structure until we find an element at level
|
||||||
// 0 or 1, indicating the end of the cluster (all elements inside the cluster are at level 2).
|
// 0 or 1, indicating the end of the cluster (all elements inside the cluster are at level 2).
|
||||||
@@ -908,9 +916,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readContiguousElements(slice: FileSlice, stopIds?: number[]) {
|
readContiguousElements(slice: FileSlice, stopIds?: number[]) {
|
||||||
const startIndex = slice.filePos;
|
while (slice.remainingLength >= MIN_HEADER_SIZE) {
|
||||||
|
|
||||||
while (slice.filePos - startIndex <= slice.length - MIN_HEADER_SIZE) {
|
|
||||||
const startPos = slice.filePos;
|
const startPos = slice.filePos;
|
||||||
const foundElement = this.traverseElement(slice, stopIds);
|
const foundElement = this.traverseElement(slice, stopIds);
|
||||||
|
|
||||||
@@ -996,6 +1002,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
codecId: null,
|
codecId: null,
|
||||||
codecPrivate: null,
|
codecPrivate: null,
|
||||||
defaultDuration: null,
|
defaultDuration: null,
|
||||||
|
defaultDurationNs: null,
|
||||||
name: null,
|
name: null,
|
||||||
languageCode: UNDETERMINED_LANGUAGE,
|
languageCode: UNDETERMINED_LANGUAGE,
|
||||||
decodingInstructions: [],
|
decodingInstructions: [],
|
||||||
@@ -1005,6 +1012,11 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
|
|
||||||
this.readContiguousElements(slice.slice(dataStartPos, size));
|
this.readContiguousElements(slice.slice(dataStartPos, size));
|
||||||
|
|
||||||
|
// Check if track was disabled during parsing (e.g., by FlagEnabled being 0)
|
||||||
|
if (!this.currentTrack) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.currentTrack.decodingInstructions.some((instruction) => {
|
if (this.currentTrack.decodingInstructions.some((instruction) => {
|
||||||
return instruction.data?.type !== 'decompress'
|
return instruction.data?.type !== 'decompress'
|
||||||
|| instruction.scope !== ContentEncodingScope.Block
|
|| instruction.scope !== ContentEncodingScope.Block
|
||||||
@@ -1203,9 +1215,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
|
|
||||||
case EBMLId.DefaultDuration: {
|
case EBMLId.DefaultDuration: {
|
||||||
if (!this.currentTrack) break;
|
if (!this.currentTrack) break;
|
||||||
|
this.currentTrack.defaultDurationNs = readUnsignedInt(slice, size);
|
||||||
this.currentTrack.defaultDuration
|
|
||||||
= this.currentTrack.segment.timestampFactor * readUnsignedInt(slice, size) / 1e9;
|
|
||||||
}; break;
|
}; break;
|
||||||
|
|
||||||
case EBMLId.Name: {
|
case EBMLId.Name: {
|
||||||
@@ -2222,7 +2232,7 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size === null) {
|
if (size === undefined) {
|
||||||
// Undefined element size (can happen in livestreamed files). In this case, we need to do some
|
// Undefined element size (can happen in livestreamed files). In this case, we need to do some
|
||||||
// searching to determine the actual size of the element.
|
// searching to determine the actual size of the element.
|
||||||
|
|
||||||
|
|||||||
+4
-6
@@ -134,12 +134,10 @@ export abstract class MediaSource {
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async _flushOrWaitForOngoingClose(forceClose: boolean) {
|
async _flushOrWaitForOngoingClose(forceClose: boolean) {
|
||||||
if (this._closingPromise) {
|
return this._closingPromise ??= (async () => {
|
||||||
// Since closing also flushes, we don't want to do it twice
|
await this._flushAndClose(forceClose);
|
||||||
return this._closingPromise;
|
this._closed = true;
|
||||||
} else {
|
})();
|
||||||
return this._flushAndClose(forceClose);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { test } from 'vitest';
|
||||||
|
import { Output } from '../../src/output.js';
|
||||||
|
import { WebMOutputFormat } from '../../src/output-format.js';
|
||||||
|
import { BufferTarget } from '../../src/target.js';
|
||||||
|
import { VideoSampleSource } from '../../src/media-source.js';
|
||||||
|
import { VideoSample } from '../../src/sample.js';
|
||||||
|
import { QUALITY_MEDIUM } from '../../src/encode.js';
|
||||||
|
|
||||||
|
test('VideoSampleSource.close() should be idempotent after finalize()', async () => {
|
||||||
|
const output = new Output({
|
||||||
|
format: new WebMOutputFormat(),
|
||||||
|
target: new BufferTarget(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const videoSource = new VideoSampleSource({
|
||||||
|
codec: 'vp8',
|
||||||
|
bitrate: QUALITY_MEDIUM,
|
||||||
|
});
|
||||||
|
|
||||||
|
output.addVideoTrack(videoSource);
|
||||||
|
await output.start();
|
||||||
|
|
||||||
|
const canvas = new OffscreenCanvas(100, 100);
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
ctx.fillStyle = 'red';
|
||||||
|
ctx.fillRect(0, 0, 100, 100);
|
||||||
|
|
||||||
|
const sample = new VideoSample(canvas, { timestamp: 0, duration: 1 / 30 });
|
||||||
|
await videoSource.add(sample);
|
||||||
|
sample.close();
|
||||||
|
|
||||||
|
await output.finalize();
|
||||||
|
|
||||||
|
videoSource.close(); // This previously threw
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user