mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 13:23:51 +02:00
Merge main into release for tag v1.55.3
This commit is contained in:
+7
-2
@@ -17,8 +17,13 @@
|
||||
source: new Mediabunny.BlobSource(file),
|
||||
});
|
||||
|
||||
const track = await input.getPrimaryVideoTrack();
|
||||
console.log(await track.getDecoderConfig());
|
||||
const track = await input.getPrimaryAudioTrack();
|
||||
const sink = new Mediabunny.AudioSampleSink(track);
|
||||
|
||||
for await (const sample of sink.samples()) {
|
||||
console.log(sample);
|
||||
sample.close()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
Generated
+10
-10
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mediabunny",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mediabunny",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"license": "MPL-2.0",
|
||||
"workspaces": [
|
||||
".",
|
||||
@@ -12958,7 +12958,7 @@
|
||||
},
|
||||
"packages/aac-encoder": {
|
||||
"name": "@mediabunny/aac-encoder",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
@@ -12973,7 +12973,7 @@
|
||||
},
|
||||
"packages/ac3": {
|
||||
"name": "@mediabunny/ac3",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
@@ -12988,7 +12988,7 @@
|
||||
},
|
||||
"packages/dts": {
|
||||
"name": "@mediabunny/dts",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
@@ -13003,7 +13003,7 @@
|
||||
},
|
||||
"packages/flac-encoder": {
|
||||
"name": "@mediabunny/flac-encoder",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
@@ -13018,7 +13018,7 @@
|
||||
},
|
||||
"packages/mp3-encoder": {
|
||||
"name": "@mediabunny/mp3-encoder",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
@@ -13033,7 +13033,7 @@
|
||||
},
|
||||
"packages/prores": {
|
||||
"name": "@mediabunny/prores",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"turbores": "^1.2.2"
|
||||
@@ -13048,10 +13048,10 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@mediabunny/server",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@mediabunny/prores": "^1.55.2",
|
||||
"@mediabunny/prores": "^1.55.3",
|
||||
"node-av": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "mediabunny",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"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.55.2",
|
||||
"version": "1.55.3",
|
||||
"description": "AAC encoder extension for Mediabunny, based on libavcodec.",
|
||||
"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.55.2",
|
||||
"version": "1.55.3",
|
||||
"description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on libavcodec.",
|
||||
"main": "./dist/bundles/mediabunny-ac3.mjs",
|
||||
"module": "./dist/bundles/mediabunny-ac3.mjs",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/dts",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"description": "DTS decoder and encoder extension for Mediabunny, based on libavcodec.",
|
||||
"main": "./dist/bundles/mediabunny-dts.mjs",
|
||||
"module": "./dist/bundles/mediabunny-dts.mjs",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/flac-encoder",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"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.55.2",
|
||||
"version": "1.55.3",
|
||||
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
||||
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/prores",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"description": "Apple ProRes decoder extension for Mediabunny, based on TurboRes.",
|
||||
"main": "./dist/bundles/mediabunny-prores.mjs",
|
||||
"module": "./dist/bundles/mediabunny-prores.mjs",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/server",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.55.2",
|
||||
"version": "1.55.3",
|
||||
"description": "Adds full video and audio decoder and encoder support to Mediabunny for use in server-side environments (Node, Bun, Deno). Based on NodeAV.",
|
||||
"main": "./dist/bundles/mediabunny-server.cjs",
|
||||
"module": "./dist/bundles/mediabunny-server.mjs",
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"node-av": "^6.0.0",
|
||||
"@mediabunny/prores": "^1.55.2"
|
||||
"@mediabunny/prores": "^1.55.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mediabunny": "^1.45.0"
|
||||
|
||||
+124
-41
@@ -10,10 +10,11 @@ import { Bitstream } from './bitstream';
|
||||
|
||||
export type AacAudioSpecificConfig = {
|
||||
objectType: number;
|
||||
coreObjectType: number;
|
||||
frequencyIndex: number;
|
||||
sampleRate: number | null;
|
||||
channelConfiguration: number;
|
||||
numberOfChannels: number | null;
|
||||
outputSampleRate: number | null;
|
||||
outputNumberOfChannels: number | null;
|
||||
};
|
||||
|
||||
export const aacFrequencyTable = [
|
||||
@@ -30,20 +31,8 @@ export const parseAacAudioSpecificConfig = (bytes: Uint8Array | null): AacAudioS
|
||||
|
||||
const bitstream = new Bitstream(bytes);
|
||||
|
||||
let objectType = bitstream.readBits(5);
|
||||
if (objectType === 31) {
|
||||
objectType = 32 + bitstream.readBits(6);
|
||||
}
|
||||
|
||||
const frequencyIndex = bitstream.readBits(4);
|
||||
let sampleRate: number | null = null;
|
||||
if (frequencyIndex === 15) {
|
||||
sampleRate = bitstream.readBits(24);
|
||||
} else {
|
||||
if (frequencyIndex < aacFrequencyTable.length) {
|
||||
sampleRate = aacFrequencyTable[frequencyIndex]!;
|
||||
}
|
||||
}
|
||||
const objectType = readAacObjectType(bitstream);
|
||||
const { frequencyIndex, sampleRate } = readAacSamplingFrequency(bitstream);
|
||||
|
||||
const channelConfiguration = bitstream.readBits(4);
|
||||
let numberOfChannels: number | null = null;
|
||||
@@ -51,61 +40,155 @@ export const parseAacAudioSpecificConfig = (bytes: Uint8Array | null): AacAudioS
|
||||
numberOfChannels = aacChannelMap[channelConfiguration]!;
|
||||
}
|
||||
|
||||
let coreObjectType = objectType;
|
||||
let psPresent = false;
|
||||
let outputSampleRate = sampleRate;
|
||||
|
||||
if (objectType === 5 || objectType === 29) {
|
||||
// Explicit hierarchical signaling: everything read so far describes the core coder, and the rate the
|
||||
// decoder actually outputs follows right here
|
||||
psPresent = objectType === 29;
|
||||
|
||||
outputSampleRate = readAacSamplingFrequency(bitstream).sampleRate;
|
||||
coreObjectType = readAacObjectType(bitstream);
|
||||
|
||||
if (coreObjectType === 22) {
|
||||
bitstream.skipBits(4); // extensionChannelConfiguration
|
||||
}
|
||||
} else {
|
||||
// There may be SBR/PS flags sitting behind a sync word after the config of the core coder. We find them by
|
||||
// scanning for the sync word, just like FFmpeg does.
|
||||
while (bitstream.getBitsLeft() > 15) {
|
||||
const searchStart = bitstream.pos;
|
||||
|
||||
if (bitstream.readBits(11) !== 0x2b7) {
|
||||
bitstream.pos = searchStart + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (readAacObjectType(bitstream) === 5 && bitstream.readBits(1)) {
|
||||
outputSampleRate = readAacSamplingFrequency(bitstream).sampleRate;
|
||||
|
||||
if (bitstream.getBitsLeft() > 11 && bitstream.readBits(11) === 0x548) {
|
||||
psPresent = !!bitstream.readBits(1);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (numberOfChannels !== null && numberOfChannels > 1) {
|
||||
// PS only ever upmixes a mono core. A stereo signal means whatever we read was bogus
|
||||
psPresent = false;
|
||||
}
|
||||
|
||||
return {
|
||||
objectType,
|
||||
coreObjectType,
|
||||
frequencyIndex,
|
||||
sampleRate,
|
||||
channelConfiguration,
|
||||
numberOfChannels,
|
||||
outputSampleRate,
|
||||
outputNumberOfChannels: psPresent && numberOfChannels === 1
|
||||
? 2
|
||||
: numberOfChannels,
|
||||
};
|
||||
};
|
||||
|
||||
const readAacObjectType = (bitstream: Bitstream) => {
|
||||
const objectType = bitstream.readBits(5);
|
||||
return objectType === 31 ? 32 + bitstream.readBits(6) : objectType;
|
||||
};
|
||||
|
||||
const readAacSamplingFrequency = (bitstream: Bitstream) => {
|
||||
const frequencyIndex = bitstream.readBits(4);
|
||||
|
||||
if (frequencyIndex === 15) {
|
||||
return {
|
||||
frequencyIndex,
|
||||
sampleRate: bitstream.readBits(24),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
frequencyIndex,
|
||||
sampleRate: frequencyIndex < aacFrequencyTable.length
|
||||
? aacFrequencyTable[frequencyIndex]!
|
||||
: null,
|
||||
};
|
||||
};
|
||||
|
||||
export const buildAacAudioSpecificConfig = (config: {
|
||||
objectType: number;
|
||||
sampleRate: number;
|
||||
numberOfChannels: number;
|
||||
outputSampleRate: number;
|
||||
outputNumberOfChannels: number;
|
||||
}) => {
|
||||
let frequencyIndex = aacFrequencyTable.indexOf(config.sampleRate);
|
||||
let customSampleRate: number | null = null;
|
||||
const usesSbr = config.objectType === 5 || config.objectType === 29;
|
||||
const usesPs = config.objectType === 29;
|
||||
|
||||
if (frequencyIndex === -1) {
|
||||
frequencyIndex = 15;
|
||||
customSampleRate = config.sampleRate;
|
||||
}
|
||||
// SBR runs the core coder at half the output rate, and PS upmixes a mono core to stereo
|
||||
const coreSampleRate = usesSbr ? config.outputSampleRate / 2 : config.outputSampleRate;
|
||||
const coreNumberOfChannels = usesPs ? 1 : config.outputNumberOfChannels;
|
||||
|
||||
const channelConfiguration = aacChannelMap.indexOf(config.numberOfChannels);
|
||||
const channelConfiguration = aacChannelMap.indexOf(coreNumberOfChannels);
|
||||
if (channelConfiguration === -1) {
|
||||
throw new TypeError(`Unsupported number of channels: ${config.numberOfChannels}`);
|
||||
throw new TypeError(`Unsupported number of channels: ${config.outputNumberOfChannels}`);
|
||||
}
|
||||
|
||||
let bitCount = 5 + 4 + 4;
|
||||
// Object type, sampling frequency, channel configuration, then a bare GASpecificConfig
|
||||
let bitCount = 5 + 4 + 4 + 3;
|
||||
if (config.objectType >= 32) {
|
||||
bitCount += 6;
|
||||
}
|
||||
if (frequencyIndex === 15) {
|
||||
if (findAacFrequencyIndex(coreSampleRate) === 15) {
|
||||
bitCount += 24;
|
||||
}
|
||||
if (usesSbr) {
|
||||
bitCount += 4 + 5; // Extension sampling frequency and the object type of the core coder
|
||||
if (findAacFrequencyIndex(config.outputSampleRate) === 15) {
|
||||
bitCount += 24;
|
||||
}
|
||||
}
|
||||
|
||||
const byteCount = Math.ceil(bitCount / 8);
|
||||
const bytes = new Uint8Array(byteCount);
|
||||
const bitstream = new Bitstream(bytes);
|
||||
|
||||
if (config.objectType < 32) {
|
||||
bitstream.writeBits(5, config.objectType);
|
||||
} else {
|
||||
bitstream.writeBits(5, 31);
|
||||
bitstream.writeBits(6, config.objectType - 32);
|
||||
writeAacObjectType(bitstream, config.objectType);
|
||||
writeAacSamplingFrequency(bitstream, coreSampleRate);
|
||||
bitstream.writeBits(4, channelConfiguration);
|
||||
|
||||
if (usesSbr) {
|
||||
writeAacSamplingFrequency(bitstream, config.outputSampleRate);
|
||||
writeAacObjectType(bitstream, 2); // AAC-LC underneath
|
||||
}
|
||||
|
||||
bitstream.writeBits(3, 0); // frameLengthFlag, dependsOnCoreCoder, extensionFlag
|
||||
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const writeAacObjectType = (bitstream: Bitstream, objectType: number) => {
|
||||
if (objectType < 32) {
|
||||
bitstream.writeBits(5, objectType);
|
||||
} else {
|
||||
bitstream.writeBits(5, 31);
|
||||
bitstream.writeBits(6, objectType - 32);
|
||||
}
|
||||
};
|
||||
|
||||
const writeAacSamplingFrequency = (bitstream: Bitstream, sampleRate: number) => {
|
||||
const frequencyIndex = findAacFrequencyIndex(sampleRate);
|
||||
bitstream.writeBits(4, frequencyIndex);
|
||||
|
||||
if (frequencyIndex === 15) {
|
||||
bitstream.writeBits(24, customSampleRate!);
|
||||
bitstream.writeBits(24, sampleRate);
|
||||
}
|
||||
};
|
||||
|
||||
bitstream.writeBits(4, channelConfiguration);
|
||||
|
||||
return bytes;
|
||||
const findAacFrequencyIndex = (sampleRate: number) => {
|
||||
const index = aacFrequencyTable.indexOf(sampleRate);
|
||||
return index === -1 ? 15 : index;
|
||||
};
|
||||
|
||||
export type AdtsHeaderTemplate = {
|
||||
@@ -117,8 +200,8 @@ export const buildAdtsHeaderTemplate = (config: AacAudioSpecificConfig): AdtsHea
|
||||
const header = new Uint8Array(7);
|
||||
const bitstream = new Bitstream(header);
|
||||
|
||||
const { objectType, frequencyIndex, channelConfiguration } = config;
|
||||
const profile = objectType - 1;
|
||||
const { coreObjectType, frequencyIndex, channelConfiguration } = config;
|
||||
const profile = coreObjectType - 1;
|
||||
|
||||
bitstream.writeBits(12, 0b1111_11111111); // Syncword
|
||||
bitstream.writeBits(1, 0); // MPEG Version
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
AsyncMutex,
|
||||
binarySearchExact,
|
||||
binarySearchLessOrEqual,
|
||||
isThenable,
|
||||
UNDETERMINED_LANGUAGE,
|
||||
} from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
@@ -84,7 +85,7 @@ export class AdtsDemuxer extends Demuxer {
|
||||
// Skip all ID3v2 tags at the start of the file
|
||||
while (true) {
|
||||
let slice = this.reader.requestSlice(this.lastLoadedPos, ID3_V2_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (!slice) {
|
||||
this.lastSampleLoaded = true;
|
||||
@@ -105,7 +106,7 @@ export class AdtsDemuxer extends Demuxer {
|
||||
MIN_ADTS_FRAME_HEADER_SIZE,
|
||||
MAX_ADTS_FRAME_HEADER_SIZE,
|
||||
);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) {
|
||||
this.lastSampleLoaded = true;
|
||||
return;
|
||||
@@ -167,7 +168,7 @@ export class AdtsDemuxer extends Demuxer {
|
||||
|
||||
while (true) {
|
||||
let headerSlice = this.reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
|
||||
if (isThenable(headerSlice)) headerSlice = await headerSlice;
|
||||
if (!headerSlice) break;
|
||||
|
||||
const id3V2Header = readId3V2Header(headerSlice);
|
||||
@@ -176,7 +177,7 @@ export class AdtsDemuxer extends Demuxer {
|
||||
}
|
||||
|
||||
let contentSlice = this.reader.requestSlice(headerSlice.filePos, id3V2Header.size);
|
||||
if (contentSlice instanceof Promise) contentSlice = await contentSlice;
|
||||
if (isThenable(contentSlice)) contentSlice = await contentSlice;
|
||||
if (!contentSlice) break;
|
||||
|
||||
parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags);
|
||||
@@ -306,7 +307,7 @@ class AdtsAudioTrackBacking implements InputAudioTrackBacking {
|
||||
data = PLACEHOLDER_DATA;
|
||||
} else {
|
||||
let slice = this.demuxer.reader.requestSlice(rawSample.dataStart, rawSample.dataSize);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (!slice) {
|
||||
return null; // Data didn't fit into the rest of the file
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { assert, MaybePromise } from './misc';
|
||||
import { assert, isThenable, MaybePromise } from './misc';
|
||||
import { readBytes, Reader } from './reader';
|
||||
|
||||
// Inspired in part by https://github.com/halloweeks/AES-128-CBC/blob/main/AES_128_CBC.h
|
||||
@@ -258,7 +258,7 @@ export const createAes128CbcDecryptStream = (
|
||||
const requestedLength = CHUNK_SIZE + BLOCK_SIZE;
|
||||
|
||||
let nextSlice = reader.requestSliceRange(pos, 0, requestedLength);
|
||||
if (nextSlice instanceof Promise) nextSlice = await nextSlice;
|
||||
if (isThenable(nextSlice)) nextSlice = await nextSlice;
|
||||
if (!nextSlice || nextSlice.length === 0) {
|
||||
// Due to padding, this should never happen
|
||||
throw new Error('Invalid ciphertext.');
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
assert,
|
||||
AsyncMutex,
|
||||
binarySearchLessOrEqual,
|
||||
isThenable,
|
||||
textDecoder,
|
||||
UNDETERMINED_LANGUAGE,
|
||||
} from '../misc';
|
||||
@@ -108,7 +109,7 @@ export class FlacDemuxer extends Demuxer {
|
||||
let currentPos = 0;
|
||||
while (true) {
|
||||
let headerSlice = this.reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
|
||||
if (isThenable(headerSlice)) headerSlice = await headerSlice;
|
||||
|
||||
if (!headerSlice) {
|
||||
this.lastSampleLoaded = true;
|
||||
@@ -121,7 +122,7 @@ export class FlacDemuxer extends Demuxer {
|
||||
}
|
||||
|
||||
let contentSlice = this.reader.requestSlice(headerSlice.filePos, id3V2Header.size);
|
||||
if (contentSlice instanceof Promise) contentSlice = await contentSlice;
|
||||
if (isThenable(contentSlice)) contentSlice = await contentSlice;
|
||||
assert(contentSlice);
|
||||
|
||||
parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags);
|
||||
@@ -136,7 +137,7 @@ export class FlacDemuxer extends Demuxer {
|
||||
|| currentPos < this.reader.fileSize
|
||||
) {
|
||||
let sizeSlice = this.reader.requestSlice(currentPos, 4);
|
||||
if (sizeSlice instanceof Promise) sizeSlice = await sizeSlice;
|
||||
if (isThenable(sizeSlice)) sizeSlice = await sizeSlice;
|
||||
currentPos += 4;
|
||||
|
||||
if (sizeSlice === null) {
|
||||
@@ -160,7 +161,7 @@ export class FlacDemuxer extends Demuxer {
|
||||
currentPos,
|
||||
size,
|
||||
);
|
||||
if (streamInfoBlock instanceof Promise) streamInfoBlock = await streamInfoBlock;
|
||||
if (isThenable(streamInfoBlock)) streamInfoBlock = await streamInfoBlock;
|
||||
|
||||
assert(streamInfoBlock);
|
||||
if (streamInfoBlock === null) {
|
||||
@@ -219,7 +220,7 @@ export class FlacDemuxer extends Demuxer {
|
||||
currentPos,
|
||||
size,
|
||||
);
|
||||
if (vorbisCommentBlock instanceof Promise) vorbisCommentBlock = await vorbisCommentBlock;
|
||||
if (isThenable(vorbisCommentBlock)) vorbisCommentBlock = await vorbisCommentBlock;
|
||||
|
||||
assert(vorbisCommentBlock);
|
||||
|
||||
@@ -237,7 +238,7 @@ export class FlacDemuxer extends Demuxer {
|
||||
currentPos,
|
||||
size,
|
||||
);
|
||||
if (pictureBlock instanceof Promise) pictureBlock = await pictureBlock;
|
||||
if (isThenable(pictureBlock)) pictureBlock = await pictureBlock;
|
||||
|
||||
assert(pictureBlock);
|
||||
const pictureType = readU32Be(pictureBlock);
|
||||
@@ -775,7 +776,7 @@ class FlacAudioTrackBacking implements InputAudioTrackBacking {
|
||||
rawSample.byteOffset,
|
||||
rawSample.byteSize,
|
||||
);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (!slice) {
|
||||
return null; // Data didn't fit into the rest of the file
|
||||
|
||||
+50
-21
@@ -29,14 +29,14 @@ import { OggDemuxer } from './ogg/ogg-demuxer';
|
||||
import { WaveDemuxer } from './wave/wave-demuxer';
|
||||
import { MAX_ADTS_FRAME_HEADER_SIZE, MIN_ADTS_FRAME_HEADER_SIZE, readAdtsFrameHeader } from './adts/adts-reader';
|
||||
import { AdtsDemuxer } from './adts/adts-demuxer';
|
||||
import { readAscii, readBytes, readU32Be } from './reader';
|
||||
import { readAscii, readBytes, readU32Be, readU64Be } from './reader';
|
||||
import { FlacDemuxer } from './flac/flac-demuxer';
|
||||
import { MpegTsDemuxer } from './mpeg-ts/mpeg-ts-demuxer';
|
||||
import { TS_PACKET_SIZE } from './mpeg-ts/mpeg-ts-misc';
|
||||
import { HlsDemuxer } from './hls/hls-demuxer';
|
||||
import { HLS_MIME_TYPE } from './hls/hls-misc';
|
||||
import { PathedSource } from './source';
|
||||
import { MaybePromise } from './misc';
|
||||
import { isThenable, MaybePromise } from './misc';
|
||||
|
||||
/**
|
||||
* Base class representing an input media file format.
|
||||
@@ -75,7 +75,7 @@ export abstract class IsobmffInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
protected async _getMajorBrand(input: Input) {
|
||||
let slice = input._reader.requestSlice(0, 12);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return null;
|
||||
|
||||
slice.skip(4);
|
||||
@@ -116,12 +116,41 @@ export class Mp4InputFormat extends IsobmffInputFormat {
|
||||
return majorBrand !== 'qt ';
|
||||
}
|
||||
|
||||
let slice = input._reader.requestSlice(4, 4);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (!slice) return false;
|
||||
let pos = 0;
|
||||
for (let iter = 0; iter < 10; iter++) { // Bounded loop
|
||||
let slice = input._reader.requestSlice(pos, 8);
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return false;
|
||||
|
||||
const fourCc = readAscii(slice, 4);
|
||||
return fourCc === 'moof' || fourCc === 'sidx'; // Seen in HLS for example
|
||||
let size = readU32Be(slice);
|
||||
let headerSize = 8;
|
||||
if (size === 1) {
|
||||
let sizeExtensionSlice = input._reader.requestSlice(pos + 8, 8);
|
||||
if (isThenable(sizeExtensionSlice)) sizeExtensionSlice = await sizeExtensionSlice;
|
||||
if (!sizeExtensionSlice) return false;
|
||||
|
||||
size = readU64Be(sizeExtensionSlice);
|
||||
headerSize = 16;
|
||||
}
|
||||
|
||||
if (size < headerSize) {
|
||||
return false; // Hardly a box is it
|
||||
}
|
||||
|
||||
const fourCc = readAscii(slice, 4);
|
||||
if (fourCc === 'moof' || fourCc === 'sidx') { // Seen in HLS for example
|
||||
return true;
|
||||
} else if (fourCc === 'emsg' || fourCc === 'prft' || fourCc === 'free') {
|
||||
// These are boxes that can appear before the first moof in DASH Media Segments. They're a strong signal
|
||||
// that this is indeed an fMP4 segment, but just to be sure, let's walk the box structure to make sure
|
||||
// it stays sane.
|
||||
pos += size;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get name() {
|
||||
@@ -169,7 +198,7 @@ export class MatroskaInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
protected async isSupportedEBMLOfDocType(input: Input, desiredDocType: string) {
|
||||
let headerSlice = input._reader.requestSlice(0, MAX_HEADER_SIZE);
|
||||
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
|
||||
if (isThenable(headerSlice)) headerSlice = await headerSlice;
|
||||
if (!headerSlice) return false;
|
||||
|
||||
const varIntSize = readVarIntSize(headerSlice);
|
||||
@@ -192,7 +221,7 @@ export class MatroskaInputFormat extends InputFormat {
|
||||
}
|
||||
|
||||
let dataSlice = input._reader.requestSlice(headerSlice.filePos, dataSize);
|
||||
if (dataSlice instanceof Promise) dataSlice = await dataSlice;
|
||||
if (isThenable(dataSlice)) dataSlice = await dataSlice;
|
||||
if (!dataSlice) return false;
|
||||
|
||||
const startPos = headerSlice.filePos;
|
||||
@@ -295,7 +324,7 @@ export class Mp3InputFormat extends InputFormat {
|
||||
|
||||
while (true) {
|
||||
let slice = input._reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const id3V2Header = readId3V2Header(slice);
|
||||
@@ -315,7 +344,7 @@ export class Mp3InputFormat extends InputFormat {
|
||||
const xingOffset = getXingOffset(firstHeader.mpegVersionId, firstHeader.channel);
|
||||
|
||||
let slice = input._reader.requestSlice(firstResult.startPos + xingOffset, 4);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return false;
|
||||
|
||||
const word = readU32Be(slice);
|
||||
@@ -376,7 +405,7 @@ export class WaveInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input: Input) {
|
||||
let slice = input._reader.requestSlice(0, 12);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return false;
|
||||
|
||||
const riffType = readAscii(slice, 4);
|
||||
@@ -416,7 +445,7 @@ export class OggInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input: Input) {
|
||||
let slice = input._reader.requestSlice(0, 4);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return false;
|
||||
|
||||
return readAscii(slice, 4) === 'OggS';
|
||||
@@ -451,7 +480,7 @@ export class FlacInputFormat extends InputFormat {
|
||||
// There might be ID3v2 headers at the start, skip 'em
|
||||
while (true) {
|
||||
let slice = input._reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const id3V2Header = readId3V2Header(slice);
|
||||
@@ -463,7 +492,7 @@ export class FlacInputFormat extends InputFormat {
|
||||
}
|
||||
|
||||
let slice = input._reader.requestSlice(currentPos, 4);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return false;
|
||||
|
||||
return readAscii(slice, 4) === 'fLaC';
|
||||
@@ -498,7 +527,7 @@ export class AdtsInputFormat extends InputFormat {
|
||||
|
||||
while (true) {
|
||||
let slice = input._reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const id3V2Header = readId3V2Header(slice);
|
||||
@@ -514,7 +543,7 @@ export class AdtsInputFormat extends InputFormat {
|
||||
MIN_ADTS_FRAME_HEADER_SIZE,
|
||||
MAX_ADTS_FRAME_HEADER_SIZE,
|
||||
);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return false;
|
||||
|
||||
const firstHeader = readAdtsFrameHeader(slice);
|
||||
@@ -529,7 +558,7 @@ export class AdtsInputFormat extends InputFormat {
|
||||
MIN_ADTS_FRAME_HEADER_SIZE,
|
||||
MAX_ADTS_FRAME_HEADER_SIZE,
|
||||
);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return false;
|
||||
|
||||
const secondHeader = readAdtsFrameHeader(slice);
|
||||
@@ -573,7 +602,7 @@ export class MpegTsInputFormat extends InputFormat {
|
||||
async _canReadInput(input: Input) {
|
||||
const lengthToCheck = TS_PACKET_SIZE + 16 + 1;
|
||||
let slice = input._reader.requestSlice(0, lengthToCheck);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return false;
|
||||
|
||||
const bytes = readBytes(slice, lengthToCheck);
|
||||
@@ -618,7 +647,7 @@ export class HlsInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input: Input) {
|
||||
let slice = input._reader.requestSlice(0, 7);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return false;
|
||||
|
||||
const isM3u8 = readAscii(slice, 7) === '#EXTM3U';
|
||||
|
||||
+9
-9
@@ -12,7 +12,7 @@ import { customAudioDecoders, customVideoDecoders } from './custom-coder';
|
||||
import { Input } from './input';
|
||||
import { Logging } from './logging';
|
||||
import { EncodedPacketSink, PacketRetrievalOptions } from './media-sink';
|
||||
import { assert, MaybePromise, Rational, Rotation, roundToDivisor, simplifyRational } from './misc';
|
||||
import { assert, isThenable, MaybePromise, Rational, Rotation, roundToDivisor, simplifyRational } from './misc';
|
||||
import { TrackType } from './output';
|
||||
import { EncodedPacket, PacketType } from './packet';
|
||||
import { TrackDisposition } from './metadata';
|
||||
@@ -528,7 +528,7 @@ export abstract class InputTrack {
|
||||
}
|
||||
|
||||
const requireSync = <T>(value: MaybePromise<T>, getterName: string, asyncName: string): T => {
|
||||
if (value instanceof Promise) {
|
||||
if (isThenable(value)) {
|
||||
throw new Error(
|
||||
`'${getterName}' is deprecated and not available synchronously for this track. Use the preferred`
|
||||
+ ` '${asyncName}()' instead.`,
|
||||
@@ -554,7 +554,7 @@ const toValidatedPredicate = <T extends InputTrack>(
|
||||
};
|
||||
|
||||
const result = predicate(track);
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
return result.then(handle);
|
||||
}
|
||||
return handle(result);
|
||||
@@ -1225,7 +1225,7 @@ export const toValidatedInputTrackQuery = <T extends InputTrack>(
|
||||
};
|
||||
|
||||
const result = query.filter!(track);
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
return result.then(handle);
|
||||
} else {
|
||||
return handle(result);
|
||||
@@ -1248,7 +1248,7 @@ export const toValidatedInputTrackQuery = <T extends InputTrack>(
|
||||
};
|
||||
|
||||
const result = query.sortBy!(track);
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
return result.then(handle);
|
||||
} else {
|
||||
return handle(result);
|
||||
@@ -1274,7 +1274,7 @@ export const mergeInputTrackQueries = <T extends InputTrack>(
|
||||
return queryB?.filter?.(track) ?? true;
|
||||
};
|
||||
|
||||
if (resultA instanceof Promise) {
|
||||
if (isThenable(resultA)) {
|
||||
return resultA.then(handleResultA);
|
||||
} else {
|
||||
return handleResultA(resultA);
|
||||
@@ -1294,7 +1294,7 @@ export const mergeInputTrackQueries = <T extends InputTrack>(
|
||||
];
|
||||
};
|
||||
|
||||
if (resultA instanceof Promise || resultB instanceof Promise) {
|
||||
if (isThenable(resultA) || isThenable(resultB)) {
|
||||
return Promise.all([resultA, resultB]).then(([resultA, resultB]) => {
|
||||
return join(resultA, resultB);
|
||||
});
|
||||
@@ -1313,7 +1313,7 @@ export const queryInputTracks = async <T extends InputTrack>(
|
||||
let matched = tracks;
|
||||
if (query?.filter) {
|
||||
const filterMatches = tracks.map(t => query.filter!(t));
|
||||
const hasAsyncFilter = filterMatches.some(x => x instanceof Promise);
|
||||
const hasAsyncFilter = filterMatches.some(x => isThenable(x));
|
||||
if (hasAsyncFilter) {
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
const resolvedFilterMatches = await Promise.all(filterMatches);
|
||||
@@ -1328,7 +1328,7 @@ export const queryInputTracks = async <T extends InputTrack>(
|
||||
}
|
||||
|
||||
const sortValues = matched.map(t => query.sortBy!(t));
|
||||
const hasAsyncSort = sortValues.some(x => x instanceof Promise);
|
||||
const hasAsyncSort = sortValues.some(x => isThenable(x));
|
||||
const resolvedSortValues = hasAsyncSort
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
? await Promise.all(sortValues)
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
COLOR_PRIMARIES_MAP_INVERSE,
|
||||
findLastIndex,
|
||||
isIso639Dash2LanguageCode,
|
||||
isThenable,
|
||||
last,
|
||||
MATRIX_COEFFICIENTS_MAP_INVERSE,
|
||||
normalizeRotation,
|
||||
@@ -364,7 +365,7 @@ export class IsobmffDemuxer extends Demuxer {
|
||||
|
||||
while (true) {
|
||||
let slice = this.reader.requestSliceRange(currentPos, MIN_BOX_HEADER_SIZE, MAX_BOX_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const startPos = currentPos;
|
||||
@@ -380,7 +381,7 @@ export class IsobmffDemuxer extends Demuxer {
|
||||
// Found moov, load it
|
||||
|
||||
let moovSlice = this.reader.requestSlice(slice.filePos, boxInfo.contentSize);
|
||||
if (moovSlice instanceof Promise) moovSlice = await moovSlice;
|
||||
if (isThenable(moovSlice)) moovSlice = await moovSlice;
|
||||
if (!moovSlice) break;
|
||||
|
||||
this.moovSlice = moovSlice;
|
||||
@@ -431,7 +432,7 @@ export class IsobmffDemuxer extends Demuxer {
|
||||
|
||||
// The last 4 bytes may contain the size of the mfra box at the end of the file
|
||||
let lastWordSlice = this.reader.requestSlice(this.reader.fileSize - 4, 4);
|
||||
if (lastWordSlice instanceof Promise) lastWordSlice = await lastWordSlice;
|
||||
if (isThenable(lastWordSlice)) lastWordSlice = await lastWordSlice;
|
||||
assert(lastWordSlice);
|
||||
|
||||
const lastWord = readU32Be(lastWordSlice);
|
||||
@@ -443,7 +444,7 @@ export class IsobmffDemuxer extends Demuxer {
|
||||
MIN_BOX_HEADER_SIZE,
|
||||
MAX_BOX_HEADER_SIZE,
|
||||
);
|
||||
if (mfraHeaderSlice instanceof Promise) mfraHeaderSlice = await mfraHeaderSlice;
|
||||
if (isThenable(mfraHeaderSlice)) mfraHeaderSlice = await mfraHeaderSlice;
|
||||
|
||||
if (mfraHeaderSlice) {
|
||||
const boxInfo = readBoxHeader(mfraHeaderSlice);
|
||||
@@ -451,7 +452,7 @@ export class IsobmffDemuxer extends Demuxer {
|
||||
if (boxInfo && boxInfo.name === 'mfra') {
|
||||
// We found the mfra box, allowing for much better random access. Let's parse it.
|
||||
let mfraSlice = this.reader.requestSlice(mfraHeaderSlice.filePos, boxInfo.contentSize);
|
||||
if (mfraSlice instanceof Promise) mfraSlice = await mfraSlice;
|
||||
if (isThenable(mfraSlice)) mfraSlice = await mfraSlice;
|
||||
|
||||
if (mfraSlice) {
|
||||
this.readContiguousBoxes(mfraSlice);
|
||||
@@ -678,14 +679,14 @@ export class IsobmffDemuxer extends Demuxer {
|
||||
}
|
||||
|
||||
let headerSlice = this.reader.requestSliceRange(startPos, MIN_BOX_HEADER_SIZE, MAX_BOX_HEADER_SIZE);
|
||||
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
|
||||
if (isThenable(headerSlice)) headerSlice = await headerSlice;
|
||||
assert(headerSlice);
|
||||
|
||||
const moofBoxInfo = readBoxHeader(headerSlice);
|
||||
assert(moofBoxInfo?.name === 'moof');
|
||||
|
||||
let entireSlice = this.reader.requestSlice(startPos, moofBoxInfo.totalSize);
|
||||
if (entireSlice instanceof Promise) entireSlice = await entireSlice;
|
||||
if (isThenable(entireSlice)) entireSlice = await entireSlice;
|
||||
assert(entireSlice);
|
||||
|
||||
this.traverseBox(entireSlice);
|
||||
@@ -1608,11 +1609,11 @@ export class IsobmffDemuxer extends Demuxer {
|
||||
if (track.info.codec === 'aac') {
|
||||
// Let's try to deduce more accurate values directly from the AudioSpecificConfig:
|
||||
const audioSpecificConfig = parseAacAudioSpecificConfig(track.info.codecDescription);
|
||||
if (audioSpecificConfig.numberOfChannels !== null) {
|
||||
track.info.numberOfChannels = audioSpecificConfig.numberOfChannels;
|
||||
if (audioSpecificConfig.outputNumberOfChannels !== null) {
|
||||
track.info.numberOfChannels = audioSpecificConfig.outputNumberOfChannels;
|
||||
}
|
||||
if (audioSpecificConfig.sampleRate !== null) {
|
||||
track.info.sampleRate = audioSpecificConfig.sampleRate;
|
||||
if (audioSpecificConfig.outputSampleRate !== null) {
|
||||
track.info.sampleRate = audioSpecificConfig.outputSampleRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3144,24 +3145,31 @@ abstract class IsobmffTrackBacking implements InputTrackBacking {
|
||||
sampleInfo.sampleOffset,
|
||||
sampleInfo.sampleSize,
|
||||
);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) {
|
||||
return null; // Data is outside
|
||||
}
|
||||
|
||||
data = readBytes(slice, sampleInfo.sampleSize);
|
||||
|
||||
if (this.internalTrack.encryptionAuxInfo) {
|
||||
assert(this.internalTrack.encryptionInfo);
|
||||
if (this.internalTrack.encryptionInfo) {
|
||||
let sampleEncryption: SampleEncryptionInfo | null = null;
|
||||
|
||||
const entries = await resolveEncryptionAuxInfo(
|
||||
this.internalTrack.demuxer.reader,
|
||||
this.internalTrack.encryptionInfo,
|
||||
this.internalTrack.encryptionAuxInfo,
|
||||
);
|
||||
if (this.internalTrack.encryptionAuxInfo) {
|
||||
const entries = await resolveEncryptionAuxInfo(
|
||||
this.internalTrack.demuxer.reader,
|
||||
this.internalTrack.encryptionInfo,
|
||||
this.internalTrack.encryptionAuxInfo,
|
||||
);
|
||||
|
||||
if (sampleIndex < entries.length) {
|
||||
data = await decryptSample(this.internalTrack, entries[sampleIndex]!, data, null);
|
||||
if (sampleIndex < entries.length) {
|
||||
sampleEncryption = entries[sampleIndex]!;
|
||||
}
|
||||
}
|
||||
|
||||
sampleEncryption ??= getDefaultSampleEncryption(this.internalTrack.encryptionInfo);
|
||||
if (sampleEncryption) {
|
||||
data = await decryptSample(this.internalTrack, sampleEncryption, data, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3200,15 +3208,19 @@ abstract class IsobmffTrackBacking implements InputTrackBacking {
|
||||
fragmentSample.byteOffset,
|
||||
fragmentSample.byteSize,
|
||||
);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) {
|
||||
return null; // Data is outside
|
||||
}
|
||||
|
||||
data = readBytes(slice, fragmentSample.byteSize);
|
||||
|
||||
if (fragmentSample.encryption) {
|
||||
data = await decryptSample(this.internalTrack, fragmentSample.encryption, data, fragment);
|
||||
if (this.internalTrack.encryptionInfo) {
|
||||
const sampleEncryption = fragmentSample.encryption
|
||||
?? getDefaultSampleEncryption(this.internalTrack.encryptionInfo);
|
||||
if (sampleEncryption) {
|
||||
data = await decryptSample(this.internalTrack, sampleEncryption, data, fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3310,7 +3322,7 @@ abstract class IsobmffTrackBacking implements InputTrackBacking {
|
||||
|
||||
// Load the header
|
||||
let slice = demuxer.reader.requestSliceRange(currentPos, MIN_BOX_HEADER_SIZE, MAX_BOX_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const boxStartPos = currentPos;
|
||||
@@ -3737,7 +3749,7 @@ const resolveEncryptionAuxInfo = async (
|
||||
}
|
||||
|
||||
let slice = reader.requestSlice(aux.offset, totalSize);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) {
|
||||
throw new Error('Failed to read auxiliary encryption info.');
|
||||
}
|
||||
@@ -3779,6 +3791,17 @@ const resolveEncryptionAuxInfo = async (
|
||||
return entries;
|
||||
};
|
||||
|
||||
const getDefaultSampleEncryption = (encryptionInfo: TrackEncryptionInfo): SampleEncryptionInfo | null => {
|
||||
if (!encryptionInfo.defaultConstantIv) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
iv: encryptionInfo.defaultConstantIv,
|
||||
subsamples: null,
|
||||
};
|
||||
};
|
||||
|
||||
const decryptSample = async (
|
||||
track: InternalTrack,
|
||||
sampleEncryption: SampleEncryptionInfo,
|
||||
|
||||
@@ -525,8 +525,8 @@ export class IsobmffMuxer extends Muxer {
|
||||
|
||||
decoderConfig.description = buildAacAudioSpecificConfig({
|
||||
objectType: adtsFrame.objectType,
|
||||
sampleRate,
|
||||
numberOfChannels,
|
||||
outputSampleRate: sampleRate,
|
||||
outputNumberOfChannels: numberOfChannels,
|
||||
});
|
||||
requiresAdtsStripping = true;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { MediaCodec } from '../codec';
|
||||
import { assert, assertNever, textDecoder, textEncoder } from '../misc';
|
||||
import { assert, assertNever, isThenable, textDecoder, textEncoder } from '../misc';
|
||||
import { FileSlice, readBytes, Reader, readF32Be, readF64Be, readU8 } from '../reader';
|
||||
import { Writer } from '../writer';
|
||||
|
||||
@@ -688,7 +688,7 @@ export const searchForNextElementId = async (
|
||||
|
||||
while (until === null || currentPos < until) {
|
||||
let slice = reader.requestSliceRange(currentPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const elementHeader = readElementHeader(slice);
|
||||
@@ -716,7 +716,7 @@ export const resync = async (reader: Reader, startPos: number, ids: EBMLId[], un
|
||||
|
||||
while (currentPos < until) {
|
||||
let slice = reader.requestSliceRange(currentPos, 0, Math.min(CHUNK_SIZE, until - currentPos));
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
if (slice.length < MAX_VAR_INT_SIZE) break;
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
COLOR_PRIMARIES_MAP_INVERSE,
|
||||
findLastIndex,
|
||||
isIso639Dash2LanguageCode,
|
||||
isThenable,
|
||||
last,
|
||||
MATRIX_COEFFICIENTS_MAP_INVERSE,
|
||||
normalizeRotation,
|
||||
@@ -334,7 +335,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
// Loop over all top-level elements in the file
|
||||
while (true) {
|
||||
let slice = this.reader.requestSliceRange(currentPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const header = readElementHeader(slice);
|
||||
@@ -350,7 +351,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
assertDefinedSize(size);
|
||||
|
||||
let slice = this.reader.requestSlice(dataStartPos, size);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
this.readContiguousElements(slice);
|
||||
@@ -433,7 +434,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
|
||||
while (this.currentSegment.elementEndPos === null || currentPos < this.currentSegment.elementEndPos) {
|
||||
let slice = this.reader.requestSliceRange(currentPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const elementStartPos = currentPos;
|
||||
@@ -468,7 +469,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
assertDefinedSize(size);
|
||||
|
||||
let slice = this.reader.requestSlice(dataStartPos, size);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (slice) {
|
||||
this.readContiguousElements(slice);
|
||||
@@ -484,7 +485,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
assertDefinedSize(size);
|
||||
|
||||
let slice = this.reader.requestSlice(dataStartPos, size);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (slice) {
|
||||
this.readContiguousElements(slice);
|
||||
@@ -519,7 +520,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
MIN_HEADER_SIZE,
|
||||
MAX_HEADER_SIZE,
|
||||
);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) continue;
|
||||
|
||||
const header = readElementHeader(slice);
|
||||
@@ -533,7 +534,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
this.currentSegment[target.flag] = true;
|
||||
|
||||
let dataSlice = this.reader.requestSlice(slice.filePos, size);
|
||||
if (dataSlice instanceof Promise) dataSlice = await dataSlice;
|
||||
if (isThenable(dataSlice)) dataSlice = await dataSlice;
|
||||
if (!dataSlice) continue;
|
||||
|
||||
this.readContiguousElements(dataSlice);
|
||||
@@ -608,7 +609,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
}
|
||||
|
||||
let headerSlice = this.reader.requestSliceRange(startPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
|
||||
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
|
||||
if (isThenable(headerSlice)) headerSlice = await headerSlice;
|
||||
assert(headerSlice);
|
||||
|
||||
const elementStartPos = startPos;
|
||||
@@ -637,7 +638,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
|
||||
// Load the entire cluster
|
||||
let dataSlice = this.reader.requestSlice(dataStartPos, size);
|
||||
if (dataSlice instanceof Promise) dataSlice = await dataSlice;
|
||||
if (isThenable(dataSlice)) dataSlice = await dataSlice;
|
||||
|
||||
const cluster: Cluster = {
|
||||
segment,
|
||||
@@ -894,7 +895,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
MIN_HEADER_SIZE,
|
||||
MAX_HEADER_SIZE,
|
||||
);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) continue;
|
||||
|
||||
const header = readElementHeader(slice);
|
||||
@@ -907,7 +908,7 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
this.currentSegment = segment;
|
||||
|
||||
let dataSlice = this.reader.requestSlice(slice.filePos, size);
|
||||
if (dataSlice instanceof Promise) dataSlice = await dataSlice;
|
||||
if (isThenable(dataSlice)) dataSlice = await dataSlice;
|
||||
if (dataSlice) {
|
||||
this.readContiguousElements(dataSlice);
|
||||
}
|
||||
@@ -2347,7 +2348,7 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
|
||||
|
||||
// Load the header
|
||||
let slice = demuxer.reader.requestSliceRange(currentPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const elementStartPos = currentPos;
|
||||
@@ -2418,7 +2419,7 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
|
||||
// the first segment.
|
||||
|
||||
let slice = demuxer.reader.requestSliceRange(endPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const elementId = readElementId(slice);
|
||||
|
||||
@@ -858,8 +858,8 @@ export class MatroskaMuxer extends Muxer {
|
||||
|
||||
decoderConfig.description = buildAacAudioSpecificConfig({
|
||||
objectType: adtsFrame.objectType,
|
||||
sampleRate,
|
||||
numberOfChannels,
|
||||
outputSampleRate: sampleRate,
|
||||
outputNumberOfChannels: numberOfChannels,
|
||||
});
|
||||
requiresAdtsStripping = true;
|
||||
}
|
||||
|
||||
+5
-4
@@ -28,6 +28,7 @@ import {
|
||||
clamp,
|
||||
clearIntervalUnthrottled,
|
||||
floorToDivisor,
|
||||
isThenable,
|
||||
last,
|
||||
missingWebCodecsClassMessage,
|
||||
promiseWithResolvers,
|
||||
@@ -426,7 +427,7 @@ class VideoEncoderWrapper {
|
||||
// Apply the user-defined process function, if any
|
||||
if (config.transform?.process) {
|
||||
let processed = config.transform.process(videoSample);
|
||||
if (processed instanceof Promise) {
|
||||
if (isThenable(processed)) {
|
||||
processed = await processed;
|
||||
}
|
||||
|
||||
@@ -1937,7 +1938,7 @@ class AudioEncoderWrapper {
|
||||
if (config.transform?.process) {
|
||||
try {
|
||||
let processed = config.transform.process(audioSample);
|
||||
if (processed instanceof Promise) {
|
||||
if (isThenable(processed)) {
|
||||
processed = await processed;
|
||||
}
|
||||
|
||||
@@ -2256,8 +2257,8 @@ class AudioEncoderWrapper {
|
||||
|
||||
meta.decoderConfig.description = buildAacAudioSpecificConfig({
|
||||
objectType,
|
||||
numberOfChannels: meta.decoderConfig.numberOfChannels,
|
||||
sampleRate: meta.decoderConfig.sampleRate,
|
||||
outputNumberOfChannels: meta.decoderConfig.numberOfChannels,
|
||||
outputSampleRate: meta.decoderConfig.sampleRate,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+14
@@ -738,6 +738,20 @@ export const missingWebCodecsClassMessage = (className: string) => {
|
||||
*/
|
||||
export type MaybePromise<T> = T | Promise<T>;
|
||||
|
||||
const NativePromiseConstructor = /* #__PURE__ */ (async () => {})().constructor as PromiseConstructor;
|
||||
|
||||
/**
|
||||
* Needed to properly deal with custom Promise implementations and because this is closer to how the JS spec does it.
|
||||
*/
|
||||
export const isThenable = <T>(value: MaybePromise<T>): value is Promise<T> => {
|
||||
if (value instanceof NativePromiseConstructor || value instanceof Promise) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fall back to a crude duck typing check
|
||||
return typeof (value as PromiseLike<T> | null | undefined)?.then === 'function';
|
||||
};
|
||||
|
||||
/** Acts like `??` except the condition is -1 and not null/undefined. */
|
||||
export const coalesceIndex = (a: number, b: number) => {
|
||||
return a !== -1 ? a : b;
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
AsyncMutex,
|
||||
binarySearchExact,
|
||||
binarySearchLessOrEqual,
|
||||
isThenable,
|
||||
toDataView,
|
||||
UNDETERMINED_LANGUAGE,
|
||||
} from '../misc';
|
||||
@@ -102,7 +103,7 @@ export class Mp3Demuxer extends Demuxer {
|
||||
// Let's skip all ID3v2 tags at the start of the file
|
||||
while (true) {
|
||||
let slice = this.reader.requestSlice(this.lastLoadedPos, ID3_V2_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (!slice) {
|
||||
this.lastSampleLoaded = true;
|
||||
@@ -136,7 +137,7 @@ export class Mp3Demuxer extends Demuxer {
|
||||
const xingOffset = getXingOffset(header.mpegVersionId, header.channel);
|
||||
|
||||
let slice = this.reader.requestSlice(result.startPos + xingOffset, 4);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (slice) {
|
||||
const word = readU32Be(slice);
|
||||
const isXing = word === XING || word === INFO;
|
||||
@@ -151,7 +152,7 @@ export class Mp3Demuxer extends Demuxer {
|
||||
|
||||
if (!this.xingData) {
|
||||
let xingDataSlice = this.reader.requestSlice(result.startPos + xingOffset + 4, 12);
|
||||
if (xingDataSlice instanceof Promise) xingDataSlice = await xingDataSlice;
|
||||
if (isThenable(xingDataSlice)) xingDataSlice = await xingDataSlice;
|
||||
if (xingDataSlice) {
|
||||
const xingData = readBytes(xingDataSlice, 12);
|
||||
const view = toDataView(xingData);
|
||||
@@ -216,7 +217,7 @@ export class Mp3Demuxer extends Demuxer {
|
||||
|
||||
while (true) {
|
||||
let headerSlice = this.reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
|
||||
if (isThenable(headerSlice)) headerSlice = await headerSlice;
|
||||
if (!headerSlice) break;
|
||||
|
||||
const id3V2Header = readId3V2Header(headerSlice);
|
||||
@@ -227,7 +228,7 @@ export class Mp3Demuxer extends Demuxer {
|
||||
id3V2HeaderFound = true;
|
||||
|
||||
let contentSlice = this.reader.requestSlice(headerSlice.filePos, id3V2Header.size);
|
||||
if (contentSlice instanceof Promise) contentSlice = await contentSlice;
|
||||
if (isThenable(contentSlice)) contentSlice = await contentSlice;
|
||||
if (!contentSlice) break;
|
||||
|
||||
parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags);
|
||||
@@ -238,7 +239,7 @@ export class Mp3Demuxer extends Demuxer {
|
||||
if (!id3V2HeaderFound && this.reader.fileSize !== null && this.reader.fileSize >= ID3_V1_TAG_SIZE) {
|
||||
// Try reading an ID3v1 tag at the end of the file
|
||||
let slice = this.reader.requestSlice(this.reader.fileSize - ID3_V1_TAG_SIZE, ID3_V1_TAG_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
assert(slice);
|
||||
|
||||
const tag = readAscii(slice, 3);
|
||||
@@ -388,7 +389,7 @@ class Mp3AudioTrackBacking implements InputAudioTrackBacking {
|
||||
data = PLACEHOLDER_DATA;
|
||||
} else {
|
||||
let slice = this.demuxer.reader.requestSlice(rawSample.dataStart, rawSample.dataSize);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (!slice) {
|
||||
return null; // Data didn't fit into the rest of the file
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { MP3_FRAME_HEADER_SIZE, getMp3ChannelCount, Mp3FrameHeader, readMp3FrameHeader } from '../../shared/mp3-misc';
|
||||
import { isThenable } from '../misc';
|
||||
import { Reader, readU32Be } from '../reader';
|
||||
|
||||
export const readNextMp3FrameHeader = async (
|
||||
@@ -27,7 +28,7 @@ export const readNextMp3FrameHeader = async (
|
||||
: CHUNK_SIZE;
|
||||
|
||||
let slice = reader.requestSliceRange(currentPos, MP3_FRAME_HEADER_SIZE, maxLength);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice || slice.length < MP3_FRAME_HEADER_SIZE) break;
|
||||
|
||||
while (slice.remainingLength >= MP3_FRAME_HEADER_SIZE) {
|
||||
|
||||
@@ -63,6 +63,7 @@ import {
|
||||
COLOR_PRIMARIES_MAP_INVERSE,
|
||||
findLastIndex,
|
||||
floorToMultiple,
|
||||
isThenable,
|
||||
last,
|
||||
MATRIX_COEFFICIENTS_MAP_INVERSE,
|
||||
readExpGolomb,
|
||||
@@ -190,7 +191,7 @@ export class MpegTsDemuxer extends Demuxer {
|
||||
return this.metadataPromise ??= (async () => {
|
||||
const lengthToCheck = TS_PACKET_SIZE + 16 + 1;
|
||||
let startingSlice = this.reader.requestSlice(0, lengthToCheck);
|
||||
if (startingSlice instanceof Promise) startingSlice = await startingSlice;
|
||||
if (isThenable(startingSlice)) startingSlice = await startingSlice;
|
||||
assert(startingSlice);
|
||||
|
||||
const startingBytes = readBytes(startingSlice, lengthToCheck);
|
||||
@@ -951,7 +952,7 @@ export class MpegTsDemuxer extends Demuxer {
|
||||
|
||||
async readPacketHeader(pos: number): Promise<TsPacketHeader | null> {
|
||||
let slice = this.reader.requestSlice(pos, 4);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (!slice) {
|
||||
return null;
|
||||
@@ -987,7 +988,7 @@ export class MpegTsDemuxer extends Demuxer {
|
||||
async readPacket(pos: number): Promise<TsPacket | null> {
|
||||
// Code in here is duplicated from readPacketHeader for performance reasons
|
||||
let slice = this.reader.requestSlice(pos, TS_PACKET_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (!slice) {
|
||||
return null;
|
||||
@@ -2099,7 +2100,7 @@ class PacketReadingContext {
|
||||
|
||||
while (true) {
|
||||
let remaining = this.ensureBuffered(CHUNK_SIZE);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
if (remaining === 0) {
|
||||
break;
|
||||
@@ -2253,7 +2254,7 @@ class PacketReadingContext {
|
||||
|
||||
while (true) {
|
||||
let remaining = this.ensureBuffered(CHUNK_SIZE);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
const startPos = this.currentPos;
|
||||
|
||||
@@ -2269,7 +2270,7 @@ class PacketReadingContext {
|
||||
const possibleHeaderStartPos = this.currentPos;
|
||||
|
||||
let remaining = this.ensureBuffered(MAX_ADTS_FRAME_HEADER_SIZE);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
if (remaining < MAX_ADTS_FRAME_HEADER_SIZE) {
|
||||
return;
|
||||
@@ -2282,7 +2283,7 @@ class PacketReadingContext {
|
||||
this.seekTo(possibleHeaderStartPos);
|
||||
|
||||
let remaining = this.ensureBuffered(header.frameLength);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
return this.supplyPacket(
|
||||
remaining,
|
||||
@@ -2300,7 +2301,7 @@ class PacketReadingContext {
|
||||
const possibleHeaderStartPos = this.currentPos;
|
||||
|
||||
let remaining = this.ensureBuffered(MP3_FRAME_HEADER_SIZE);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
if (remaining < MP3_FRAME_HEADER_SIZE) {
|
||||
return;
|
||||
@@ -2314,7 +2315,7 @@ class PacketReadingContext {
|
||||
this.seekTo(possibleHeaderStartPos);
|
||||
|
||||
let remaining = this.ensureBuffered(result.header.totalSize);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
const duration = result.header.audioSamplesInFrame * TIMESCALE
|
||||
/ elementaryStream.info.sampleRate;
|
||||
@@ -2332,7 +2333,7 @@ class PacketReadingContext {
|
||||
|
||||
// Need at least 5 bytes for sync word + CRC + fscod/frmsizecod
|
||||
let remaining = this.ensureBuffered(5);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
if (remaining < 5) {
|
||||
return;
|
||||
@@ -2361,7 +2362,7 @@ class PacketReadingContext {
|
||||
this.seekTo(possibleSyncPos);
|
||||
|
||||
remaining = this.ensureBuffered(frameSize);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
const duration = Math.round(
|
||||
AC3_SAMPLES_PER_FRAME * TIMESCALE / elementaryStream.info.sampleRate,
|
||||
@@ -2377,7 +2378,7 @@ class PacketReadingContext {
|
||||
|
||||
// Need at least 5 bytes for E-AC-3 header parsing (sync word + frmsiz + fscod/numblkscod)
|
||||
let remaining = this.ensureBuffered(5);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
if (remaining < 5) {
|
||||
return;
|
||||
@@ -2399,7 +2400,7 @@ class PacketReadingContext {
|
||||
this.seekTo(possibleSyncPos);
|
||||
|
||||
remaining = this.ensureBuffered(frameSize);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
// Duration = numblks * 256 samples per block
|
||||
const samplesPerFrame = numblks * 256;
|
||||
@@ -2416,7 +2417,7 @@ class PacketReadingContext {
|
||||
const possibleSyncPos = this.currentPos;
|
||||
|
||||
let remaining = this.ensureBuffered(DTS_CORE_FRAME_HEADER_SIZE);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
if (remaining < DTS_CORE_FRAME_HEADER_SIZE) {
|
||||
return;
|
||||
@@ -2438,7 +2439,7 @@ class PacketReadingContext {
|
||||
|
||||
const headerBound = Math.min(leadingExss.frameSize, DTS_EXSS_MAX_HEADER_SIZE);
|
||||
let remaining = this.ensureBuffered(headerBound);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
leadingExss = parseDtsExssHeader(this.readBytes(remaining)) ?? leadingExss;
|
||||
}
|
||||
@@ -2456,7 +2457,7 @@ class PacketReadingContext {
|
||||
|
||||
const neededBytes = nextSubstreamPos + DTS_EXSS_HEADER_PREFIX_SIZE;
|
||||
let remaining = this.ensureBuffered(neededBytes);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
if (remaining < neededBytes) {
|
||||
break;
|
||||
@@ -2484,7 +2485,7 @@ class PacketReadingContext {
|
||||
this.seekTo(possibleSyncPos);
|
||||
|
||||
remaining = this.ensureBuffered(frameSize);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
if (isThenable(remaining)) remaining = await remaining;
|
||||
|
||||
const duration = Math.round(
|
||||
sampleCount * TIMESCALE / elementaryStream.info.sampleRate,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
AsyncMutex,
|
||||
binarySearchLessOrEqual,
|
||||
findLast,
|
||||
isThenable,
|
||||
last,
|
||||
roundIfAlmostInteger,
|
||||
toDataView,
|
||||
@@ -72,7 +73,7 @@ export class OggDemuxer extends Demuxer {
|
||||
|
||||
while (true) {
|
||||
let slice = this.reader.requestSliceRange(currentPos, MIN_PAGE_HEADER_SIZE, MAX_PAGE_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const page = readPageHeader(slice);
|
||||
@@ -274,7 +275,7 @@ export class OggDemuxer extends Demuxer {
|
||||
while (true) {
|
||||
// Load the entire page data
|
||||
let pageSlice = this.reader.requestSlice(currentPage.dataStartPos, currentPage.dataSize);
|
||||
if (pageSlice instanceof Promise) pageSlice = await pageSlice;
|
||||
if (isThenable(pageSlice)) pageSlice = await pageSlice;
|
||||
assert(pageSlice);
|
||||
const pageData = readBytes(pageSlice, currentPage.dataSize);
|
||||
|
||||
@@ -299,7 +300,7 @@ export class OggDemuxer extends Demuxer {
|
||||
let currentPos = currentPage.headerStartPos + currentPage.totalSize;
|
||||
while (true) {
|
||||
let headerSlice = this.reader.requestSliceRange(currentPos, MIN_PAGE_HEADER_SIZE, MAX_PAGE_HEADER_SIZE);
|
||||
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
|
||||
if (isThenable(headerSlice)) headerSlice = await headerSlice;
|
||||
if (!headerSlice) {
|
||||
return null;
|
||||
}
|
||||
@@ -358,7 +359,7 @@ export class OggDemuxer extends Demuxer {
|
||||
let currentPos = lastPacket.endPage.headerStartPos + lastPacket.endPage.totalSize;
|
||||
while (true) {
|
||||
let slice = this.reader.requestSliceRange(currentPos, MIN_PAGE_HEADER_SIZE, MAX_PAGE_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) {
|
||||
return null;
|
||||
}
|
||||
@@ -658,7 +659,7 @@ class OggAudioTrackBacking implements InputAudioTrackBacking {
|
||||
);
|
||||
|
||||
let searchSlice = this.demuxer.reader.requestSlice(searchStartPos, until - searchStartPos);
|
||||
if (searchSlice instanceof Promise) searchSlice = await searchSlice;
|
||||
if (isThenable(searchSlice)) searchSlice = await searchSlice;
|
||||
assert(searchSlice);
|
||||
|
||||
const found = findNextPageHeader(searchSlice, until);
|
||||
@@ -672,7 +673,7 @@ class OggAudioTrackBacking implements InputAudioTrackBacking {
|
||||
MIN_PAGE_HEADER_SIZE,
|
||||
MAX_PAGE_HEADER_SIZE,
|
||||
);
|
||||
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
|
||||
if (isThenable(headerSlice)) headerSlice = await headerSlice;
|
||||
assert(headerSlice);
|
||||
|
||||
const page = readPageHeader(headerSlice);
|
||||
@@ -685,7 +686,7 @@ class OggAudioTrackBacking implements InputAudioTrackBacking {
|
||||
pageValid = true;
|
||||
} else {
|
||||
let pageSlice = this.demuxer.reader.requestSlice(page.headerStartPos, page.totalSize);
|
||||
if (pageSlice instanceof Promise) pageSlice = await pageSlice;
|
||||
if (isThenable(pageSlice)) pageSlice = await pageSlice;
|
||||
assert(pageSlice);
|
||||
|
||||
// Validate the page by checking checksum
|
||||
@@ -759,7 +760,7 @@ class OggAudioTrackBacking implements InputAudioTrackBacking {
|
||||
|
||||
const nextPos = currentPage.headerStartPos + currentPage.totalSize;
|
||||
let slice = this.demuxer.reader.requestSliceRange(nextPos, MIN_PAGE_HEADER_SIZE, MAX_PAGE_HEADER_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
assert(slice);
|
||||
|
||||
const nextPage = readPageHeader(slice);
|
||||
|
||||
+13
-4
@@ -6,7 +6,16 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { assert, AsyncMutex, EventEmitter, isIso639Dash2LanguageCode, MaybePromise, Rotation, toArray } from './misc';
|
||||
import {
|
||||
assert,
|
||||
AsyncMutex,
|
||||
EventEmitter,
|
||||
isIso639Dash2LanguageCode,
|
||||
isThenable,
|
||||
MaybePromise,
|
||||
Rotation,
|
||||
toArray,
|
||||
} from './misc';
|
||||
import { MetadataTags, TrackDisposition, validateMetadataTags, validateTrackDisposition } from './metadata';
|
||||
import { Muxer } from './muxer';
|
||||
import { OutputFormat } from './output-format';
|
||||
@@ -451,7 +460,7 @@ export class Output<
|
||||
}
|
||||
|
||||
const rootTargetResult = this._getRootTarget();
|
||||
if (rootTargetResult instanceof Promise) {
|
||||
if (isThenable(rootTargetResult)) {
|
||||
throw new TypeError(errorMessage);
|
||||
}
|
||||
|
||||
@@ -516,7 +525,7 @@ export class Output<
|
||||
return result;
|
||||
};
|
||||
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
return result.then(handleResult);
|
||||
} else {
|
||||
return handleResult(result);
|
||||
@@ -604,7 +613,7 @@ export class Output<
|
||||
return target;
|
||||
};
|
||||
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
return this._rootTargetPromise = result.then(handleResult);
|
||||
} else {
|
||||
return handleResult(result);
|
||||
|
||||
+4
-4
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { InputDisposedError } from './input';
|
||||
import { assert, clamp, getUint24, MaybePromise, textDecoder, toDataView } from './misc';
|
||||
import { assert, clamp, getUint24, isThenable, MaybePromise, textDecoder, toDataView } from './misc';
|
||||
import { DEFAULT_MAX_READ_POSITION, DEFAULT_MIN_READ_POSITION, Source } from './source';
|
||||
|
||||
export class Reader {
|
||||
@@ -47,7 +47,7 @@ export class Reader {
|
||||
const end = start + length;
|
||||
const result = this.source._read(start, end, DEFAULT_MIN_READ_POSITION, DEFAULT_MAX_READ_POSITION);
|
||||
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
return result.then((x) => {
|
||||
if (!x) {
|
||||
return null;
|
||||
@@ -95,7 +95,7 @@ export class Reader {
|
||||
);
|
||||
};
|
||||
|
||||
if (promisedAttempt instanceof Promise) {
|
||||
if (isThenable(promisedAttempt)) {
|
||||
return promisedAttempt.then(handleAttempt);
|
||||
} else {
|
||||
return handleAttempt(promisedAttempt);
|
||||
@@ -121,7 +121,7 @@ export class Reader {
|
||||
}
|
||||
|
||||
let slice = this.requestSliceRange(currentSize, 0, CHUNK_SIZE);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (!slice || slice.length === 0) {
|
||||
break;
|
||||
|
||||
+4
-3
@@ -11,6 +11,7 @@ import {
|
||||
clamp,
|
||||
COLOR_PRIMARIES_MAP,
|
||||
isAllowSharedBufferSource,
|
||||
isThenable,
|
||||
MATRIX_COEFFICIENTS_MAP,
|
||||
Rotation,
|
||||
SECOND_TO_MICROSECOND_FACTOR,
|
||||
@@ -864,7 +865,7 @@ export class VideoSample implements Disposable {
|
||||
|
||||
if (this._data instanceof VideoSampleResource) {
|
||||
let result = this._data.getDataPlanes();
|
||||
if (result instanceof Promise) result = await result;
|
||||
if (isThenable(result)) result = await result;
|
||||
|
||||
if (
|
||||
!Array.isArray(result)
|
||||
@@ -1022,7 +1023,7 @@ export class VideoSample implements Disposable {
|
||||
}
|
||||
|
||||
const planes = this._data.getDataPlanes();
|
||||
if (planes instanceof Promise) {
|
||||
if (isThenable(planes)) {
|
||||
throw new Error(
|
||||
'Cannot convert a VideoSampleResource-backed VideoSample to VideoFrame if getDataPlanes() returns'
|
||||
+ ' a promise.',
|
||||
@@ -1586,7 +1587,7 @@ export class VideoSample implements Disposable {
|
||||
// Description's finalized; let's see if a registered transformer wants to handle it
|
||||
for (const transformer of registeredVideoSampleTransformers) {
|
||||
let result = transformer(this, description);
|
||||
if (result instanceof Promise) result = await result;
|
||||
if (isThenable(result)) result = await result;
|
||||
|
||||
if (result !== null) {
|
||||
return result;
|
||||
|
||||
+8
-7
@@ -14,6 +14,7 @@ import {
|
||||
closedIntervalsOverlap,
|
||||
FilePath,
|
||||
isNumber,
|
||||
isThenable,
|
||||
isWebKit,
|
||||
MaybePromise,
|
||||
mergeRequestInit,
|
||||
@@ -328,7 +329,7 @@ export abstract class PathedSource extends Source {
|
||||
return ref;
|
||||
};
|
||||
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
return result.then(handle);
|
||||
} else {
|
||||
return handle(result);
|
||||
@@ -387,7 +388,7 @@ export class CustomPathedSource extends PathedSource {
|
||||
return ref;
|
||||
};
|
||||
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
this._rootRequest = result.then(handle);
|
||||
} else {
|
||||
handle(result);
|
||||
@@ -894,7 +895,7 @@ export class UrlSource extends PathedSource {
|
||||
return result;
|
||||
};
|
||||
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
return result.then(processResult);
|
||||
} else {
|
||||
return processResult(result);
|
||||
@@ -1198,7 +1199,7 @@ export class UrlSource extends PathedSource {
|
||||
for (const slice of uniqueSlices) {
|
||||
const result = backing._read(slice.start, slice.start + slice.bytes.length);
|
||||
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
result.then((readResult) => {
|
||||
if (readResult) {
|
||||
// The backing's cache is empty at this point, so the read is guaranteed to produce
|
||||
@@ -1459,7 +1460,7 @@ export class CustomSource extends Source {
|
||||
|
||||
const result = this._options.getSize();
|
||||
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
return result.then((size) => {
|
||||
if (!Number.isInteger(size) || size < 0) {
|
||||
throw new TypeError('options.getSize must return or resolve to a non-negative integer.');
|
||||
@@ -1485,7 +1486,7 @@ export class CustomSource extends Source {
|
||||
const originalTargetPos = worker.targetPos;
|
||||
|
||||
let data = this._options.read(worker.currentPos, originalTargetPos);
|
||||
if (data instanceof Promise) data = await data;
|
||||
if (isThenable(data)) data = await data;
|
||||
|
||||
if (worker.aborted) {
|
||||
break;
|
||||
@@ -2706,7 +2707,7 @@ export class RangedSource extends Source {
|
||||
return result;
|
||||
};
|
||||
|
||||
if (result instanceof Promise) {
|
||||
if (isThenable(result)) {
|
||||
return result.then(processResult);
|
||||
} else {
|
||||
return processResult(result);
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Input } from '../input';
|
||||
import { InputAudioTrackBacking } from '../input-track';
|
||||
import { PacketRetrievalOptions } from '../media-sink';
|
||||
import { DEFAULT_TRACK_DISPOSITION, MetadataTags } from '../metadata';
|
||||
import { assert, UNDETERMINED_LANGUAGE } from '../misc';
|
||||
import { assert, isThenable, UNDETERMINED_LANGUAGE } from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { readAscii, readBytes, Reader, readU16, readU32, readU64 } from '../reader';
|
||||
import { ID3_V2_HEADER_SIZE, parseId3V2Tag, readId3V2Header } from '../id3';
|
||||
@@ -52,7 +52,7 @@ export class WaveDemuxer extends Demuxer {
|
||||
async readMetadata() {
|
||||
return this.metadataPromise ??= (async () => {
|
||||
let slice = this.reader.requestSlice(0, 12);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
assert(slice);
|
||||
|
||||
const riffType = readAscii(slice, 4);
|
||||
@@ -77,7 +77,7 @@ export class WaveDemuxer extends Demuxer {
|
||||
|
||||
while (totalFileSize === null || currentPos < totalFileSize) {
|
||||
let slice = this.reader.requestSlice(currentPos, 8);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) break;
|
||||
|
||||
const chunkId = readAscii(slice, 4);
|
||||
@@ -103,7 +103,7 @@ export class WaveDemuxer extends Demuxer {
|
||||
// File and data chunk sizes are defined in here instead
|
||||
|
||||
let ds64Slice = this.reader.requestSlice(startPos, chunkSize);
|
||||
if (ds64Slice instanceof Promise) ds64Slice = await ds64Slice;
|
||||
if (isThenable(ds64Slice)) ds64Slice = await ds64Slice;
|
||||
if (!ds64Slice) break;
|
||||
|
||||
const riffChunkSize = readU64(ds64Slice, littleEndian);
|
||||
@@ -136,7 +136,7 @@ export class WaveDemuxer extends Demuxer {
|
||||
|
||||
private async parseFmtChunk(startPos: number, size: number, littleEndian: boolean) {
|
||||
let slice = this.reader.requestSlice(startPos, size);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return; // File too short
|
||||
|
||||
let formatTag = readU16(slice, littleEndian);
|
||||
@@ -206,7 +206,7 @@ export class WaveDemuxer extends Demuxer {
|
||||
|
||||
private async parseListChunk(startPos: number, size: number, littleEndian: boolean) {
|
||||
let slice = this.reader.requestSlice(startPos, size);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return; // File too short
|
||||
|
||||
const infoType = readAscii(slice, 4);
|
||||
@@ -303,7 +303,7 @@ export class WaveDemuxer extends Demuxer {
|
||||
private async parseId3Chunk(startPos: number, size: number) {
|
||||
// Parse ID3 tag embedded in WAV file (non-default, but used a lot in practice anyway)
|
||||
let slice = this.reader.requestSlice(startPos, size);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
if (!slice) return; // File too short
|
||||
|
||||
const id3V2Header = readId3V2Header(slice);
|
||||
@@ -489,7 +489,7 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
|
||||
// requested slice actually exists.
|
||||
|
||||
let slice = this.demuxer.reader.requestSlice(this.demuxer.dataStart + startOffset, sizeInBytes);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
|
||||
if (!slice) {
|
||||
return null;
|
||||
@@ -501,7 +501,7 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
|
||||
data = PLACEHOLDER_DATA;
|
||||
} else {
|
||||
let slice = this.demuxer.reader.requestSlice(this.demuxer.dataStart + startOffset, sizeInBytes);
|
||||
if (slice instanceof Promise) slice = await slice;
|
||||
if (isThenable(slice)) slice = await slice;
|
||||
assert(slice);
|
||||
|
||||
data = readBytes(slice, sizeInBytes);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import { Input } from '../../src/input.js';
|
||||
import { UrlSource } from '../../src/source.js';
|
||||
import { ALL_FORMATS } from '../../src/input-format.js';
|
||||
import { AudioSampleSink } from '../../src/media-sink.js';
|
||||
import { assert } from '../../src/misc.js';
|
||||
|
||||
test('Encrypted MP4 without senc', async () => {
|
||||
using input = new Input({
|
||||
source: new UrlSource('/639955605-b752f7ad-7ce8-43c2-8ae9-88e74c6ae696.mp4'),
|
||||
formats: ALL_FORMATS,
|
||||
formatOptions: {
|
||||
isobmff: {
|
||||
resolveKeyId: () => 'aabbccddeeff00112233445566778899',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
assert(audioTrack);
|
||||
|
||||
// Test that it can decode
|
||||
const sink = new AudioSampleSink(audioTrack);
|
||||
using firstSample = await sink.getSample(0);
|
||||
assert(firstSample);
|
||||
expect(firstSample.timestamp).toBe(0);
|
||||
});
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Mp4OutputFormat,
|
||||
Output,
|
||||
} from '../../src/index.js';
|
||||
import { assert, toUint8Array } from '../../src/misc.js';
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
@@ -160,3 +161,23 @@ test('Annex B', async () => {
|
||||
const firstPacket = (await sink.getFirstPacket())!;
|
||||
expect([...firstPacket.data.slice(0, 4)]).toEqual([0, 0, 0, 1]);
|
||||
});
|
||||
|
||||
test('HE-AAC v2 audio config is parsed correctly', async () => {
|
||||
const filePath = path.join(__dirname, '..', 'public/he-aac-v2.mp4');
|
||||
using input = new Input({
|
||||
source: new FilePathSource(filePath),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const track = await input.getPrimaryAudioTrack();
|
||||
assert(track);
|
||||
|
||||
expect(await track.getSampleRate()).toBe(44100);
|
||||
expect(await track.getNumberOfChannels()).toBe(2);
|
||||
|
||||
const decoderConfig = (await track.getDecoderConfig())!;
|
||||
expect(decoderConfig.codec).toBe('mp4a.40.29');
|
||||
expect(decoderConfig.sampleRate).toBe(44100);
|
||||
expect(decoderConfig.numberOfChannels).toBe(2);
|
||||
expect([...toUint8Array(decoderConfig.description!)]).toEqual([0xeb, 0x8a, 0x08, 0x00]);
|
||||
});
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user