Reorder stuff, rename stuff, add license header

This commit is contained in:
Vanilagy
2025-08-29 22:52:27 +02:00
parent c222973571
commit cba0525e51
15 changed files with 180 additions and 172 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ import {
UNDETERMINED_LANGUAGE,
} from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { readBytes, Reader } from '../reader2';
import { readBytes, Reader } from '../reader';
import { FrameHeader, MAX_FRAME_HEADER_SIZE, MIN_FRAME_HEADER_SIZE, readFrameHeader } from './adts-reader';
const SAMPLES_PER_AAC_FRAME = 1024;
+1 -1
View File
@@ -7,7 +7,7 @@
*/
import { Bitstream } from '../misc';
import { FileSlice, readBytes } from '../reader2';
import { FileSlice, readBytes } from '../reader';
export const MIN_FRAME_HEADER_SIZE = 7;
export const MAX_FRAME_HEADER_SIZE = 9;
+1 -1
View File
@@ -27,7 +27,7 @@ import { OggDemuxer } from './ogg/ogg-demuxer';
import { WaveDemuxer } from './wave/wave-demuxer';
import { MAX_FRAME_HEADER_SIZE, MIN_FRAME_HEADER_SIZE, readFrameHeader } from './adts/adts-reader';
import { AdtsDemuxer } from './adts/adts-demuxer';
import { readAscii } from './reader2';
import { readAscii } from './reader';
/**
* Base class representing an input media file format.
+1 -1
View File
@@ -9,7 +9,7 @@
import { Demuxer } from './demuxer';
import { InputFormat } from './input-format';
import { assert } from './misc';
import { Reader } from './reader2';
import { Reader } from './reader';
import { Source } from './source';
/**
+1 -1
View File
@@ -82,7 +82,7 @@ import {
readU64Be,
readU8,
readAscii,
} from '../reader2';
} from '../reader';
type InternalTrack = {
id: number;
+1 -1
View File
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { FileSlice, readAscii, readI32Be, readU32Be, readU64Be, readU8 } from '../reader2';
import { FileSlice, readAscii, readI32Be, readU32Be, readU64Be, readU8 } from '../reader';
export const MIN_BOX_HEADER_SIZE = 8;
export const MAX_BOX_HEADER_SIZE = 16;
+1 -1
View File
@@ -8,7 +8,7 @@
import { MediaCodec } from '../codec';
import { assertNever, textDecoder, textEncoder } from '../misc';
import { FileSlice, readBytes, Reader, readF32Be, readF64Be, readU8 } from '../reader2';
import { FileSlice, readBytes, Reader, readF32Be, readF64Be, readU8 } from '../reader';
import { Writer } from '../writer';
export interface EBMLElement {
+1 -1
View File
@@ -69,7 +69,7 @@ import {
searchForNextElementId,
} from './ebml';
import { buildMatroskaMimeType } from './matroska-misc';
import { FileSlice, readBytes, Reader, readI16Be, readU8 } from '../reader2';
import { FileSlice, readBytes, Reader, readI16Be, readU8 } from '../reader';
type Segment = {
seekHeadSeen: boolean;
+1 -1
View File
@@ -15,7 +15,7 @@ import { assert, AsyncMutex, binarySearchExact, binarySearchLessOrEqual, UNDETER
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { FrameHeader, getXingOffset, INFO, XING } from '../../shared/mp3-misc';
import { readId3, readNextFrameHeader } from './mp3-reader';
import { readBytes, Reader, readU32Be } from '../reader2';
import { readBytes, Reader, readU32Be } from '../reader';
type Sample = {
timestamp: number;
+1 -1
View File
@@ -7,7 +7,7 @@
*/
import { FRAME_HEADER_SIZE, FrameHeader, readFrameHeader } from '../../shared/mp3-misc';
import { FileSlice, readAscii, Reader, readU32Be } from '../reader2';
import { FileSlice, readAscii, Reader, readU32Be } from '../reader';
export const readId3 = (slice: FileSlice) => {
const tag = readAscii(slice, 3);
+1 -1
View File
@@ -14,7 +14,7 @@ import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
import { PacketRetrievalOptions } from '../media-sink';
import { assert, findLast, roundToPrecision, toDataView, UNDETERMINED_LANGUAGE } from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { readBytes, Reader } from '../reader2';
import { readBytes, Reader } from '../reader';
import { buildOggMimeType, computeOggPageCrc, extractSampleMetadata, OggCodecInfo } from './ogg-misc';
import {
findNextPageHeader,
+1 -1
View File
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { FileSlice, readI64Le, readU32Le, readU8 } from '../reader2';
import { FileSlice, readI64Le, readU32Le, readU8 } from '../reader';
import { OGGS } from './ogg-misc';
export const MIN_PAGE_HEADER_SIZE = 27;
+8
View File
@@ -1,3 +1,11 @@
/*!
* Copyright (c) 2025-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { clamp, MaybePromise, toDataView } from './misc';
import { Source } from './source';
+159 -159
View File
@@ -93,165 +93,6 @@ export class BufferSource extends Source {
}
}
/**
* Options for defining a StreamSource.
* @public
*/
export type StreamSourceOptions = {
/**
* Called when data is requested. Must return or resolve to the bytes from the specified byte range, or a stream
* that yields these bytes.
*/
read: (start: number, end: number) => MaybePromise<Uint8Array | ReadableStream<Uint8Array>>;
/** Called when the size of the entire file is requested. Must return or resolve to the size in bytes. */
getSize: () => MaybePromise<number>;
/** The maximum number of bytes the cache is allowed to hold in memory. Defaults to 8 MiB. */
maxCacheSize?: number;
/**
* Specifies the prefetch profile that the reader should use with this source. A prefetch propfile specifies the
* pattern with which bytes outside of the requested range are preloaded to reduce latency for future reads.
*
* - `'none'` (default): No prefetching; only the data needed in the moment is requested.
* - `'fileSystem'`: File system-optimized prefetching: a small amount of data is prefetched bidirectionally.
* - `'network'`: Network-optimized prefetching, or more generally, prefetching optimized for any high-latency
* environment: tries to minimize the amount of read calls and aggressively prefetches data when sequential access
* patterns are detected.
*/
prefetchProfile?: 'none' | 'fileSystem' | 'network';
};
/**
* A general-purpose, callback-driven source that can get its data from anywhere.
* @public
*/
export class StreamSource extends Source {
/** @internal */
_options: StreamSourceOptions;
/** @internal */
_orchestrator: ReadOrchestrator;
constructor(options: StreamSourceOptions) {
if (!options || typeof options !== 'object') {
throw new TypeError('options must be an object.');
}
if (typeof options.read !== 'function') {
throw new TypeError('options.read must be a function.');
}
if (typeof options.getSize !== 'function') {
throw new TypeError('options.getSize must be a function.');
}
if (
options.maxCacheSize !== undefined
&& (!Number.isInteger(options.maxCacheSize) || options.maxCacheSize < 0)
) {
throw new TypeError('options.maxCacheSize, when provided, must be a non-negative integer.');
}
if (options.prefetchProfile && !['none', 'fileSystem', 'network'].includes(options.prefetchProfile)) {
throw new TypeError(
'options.prefetchProfile, when provided, must be one of \'none\', \'fileSystem\' or \'network\'.',
);
}
super();
this._options = options;
this._orchestrator = new ReadOrchestrator({
maxCacheSize: options.maxCacheSize ?? (8 * 2 ** 20 /* 8 MiB */),
maxWorkerCount: 2, // Fixed for now, *should* be fine
prefetchProfile: PREFETCH_PROFILES[options.prefetchProfile ?? 'none'],
runWorker: this._runWorker.bind(this),
});
}
/** @internal */
_retrieveSize(): MaybePromise<number> {
const result = this._options.getSize();
if (result instanceof Promise) {
return result.then((size) => {
if (!Number.isInteger(size) || size < 0) {
throw new TypeError('options.getSize must return or resolve to a non-negative integer.');
}
this._orchestrator.fileSize = size;
return size;
});
} else {
if (!Number.isInteger(result) || result < 0) {
throw new TypeError('options.getSize must return or resolve to a non-negative integer.');
}
this._orchestrator.fileSize = result;
return result;
}
}
/** @internal */
_read(start: number, end: number): MaybePromise<ReadResult> {
return this._orchestrator.read(start, end);
}
private async _runWorker(worker: ReadWorker) {
while (worker.currentPos < worker.targetPos && !worker.aborted) {
const originalCurrentPos = worker.currentPos;
const originalTargetPos = worker.targetPos;
let data = this._options.read(worker.currentPos, originalTargetPos);
if (data instanceof Promise) data = await data;
if (data instanceof Uint8Array) {
if (data.length !== originalTargetPos - worker.currentPos) {
// Yes, we're that strict
throw new Error(
`options.read returned a Uint8Array with unexpected length: Requested ${
originalTargetPos - worker.currentPos
} bytes, but got ${data.length}.`,
);
}
this.onread?.(worker.currentPos, worker.currentPos + data.length);
this._orchestrator.supplyWorkerData(worker, data);
} else if (data instanceof ReadableStream) {
const reader = data.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
if (worker.currentPos < originalTargetPos) {
// Yes, we're *that* strict
throw new Error(
`ReadableStream returned by options.read ended before supplying enough data.`
+ ` Requested ${originalTargetPos - originalCurrentPos} bytes, but got ${
worker.currentPos - originalCurrentPos
}`,
);
}
break;
}
if (!(value instanceof Uint8Array)) {
throw new TypeError('ReadableStream returned by options.read must yield Uint8Array chunks.');
}
this.onread?.(worker.currentPos, worker.currentPos + value.length);
this._orchestrator.supplyWorkerData(worker, value);
if (worker.currentPos >= originalTargetPos || worker.aborted) {
break;
}
}
} else {
throw new TypeError('options.read must return or resolve to a Uint8Array or a ReadableStream.');
}
}
}
}
export type BlobSourceOptions = {
/** The maximum number of bytes the cache is allowed to hold in memory. Defaults to 8 MiB. */
maxCacheSize?: number;
@@ -566,6 +407,165 @@ export class UrlSource extends Source {
}
}
/**
* Options for defining a StreamSource.
* @public
*/
export type StreamSourceOptions = {
/**
* Called when data is requested. Must return or resolve to the bytes from the specified byte range, or a stream
* that yields these bytes.
*/
read: (start: number, end: number) => MaybePromise<Uint8Array | ReadableStream<Uint8Array>>;
/** Called when the size of the entire file is requested. Must return or resolve to the size in bytes. */
getSize: () => MaybePromise<number>;
/** The maximum number of bytes the cache is allowed to hold in memory. Defaults to 8 MiB. */
maxCacheSize?: number;
/**
* Specifies the prefetch profile that the reader should use with this source. A prefetch propfile specifies the
* pattern with which bytes outside of the requested range are preloaded to reduce latency for future reads.
*
* - `'none'` (default): No prefetching; only the data needed in the moment is requested.
* - `'fileSystem'`: File system-optimized prefetching: a small amount of data is prefetched bidirectionally.
* - `'network'`: Network-optimized prefetching, or more generally, prefetching optimized for any high-latency
* environment: tries to minimize the amount of read calls and aggressively prefetches data when sequential access
* patterns are detected.
*/
prefetchProfile?: 'none' | 'fileSystem' | 'network';
};
/**
* A general-purpose, callback-driven source that can get its data from anywhere.
* @public
*/
export class StreamSource extends Source {
/** @internal */
_options: StreamSourceOptions;
/** @internal */
_orchestrator: ReadOrchestrator;
constructor(options: StreamSourceOptions) {
if (!options || typeof options !== 'object') {
throw new TypeError('options must be an object.');
}
if (typeof options.read !== 'function') {
throw new TypeError('options.read must be a function.');
}
if (typeof options.getSize !== 'function') {
throw new TypeError('options.getSize must be a function.');
}
if (
options.maxCacheSize !== undefined
&& (!Number.isInteger(options.maxCacheSize) || options.maxCacheSize < 0)
) {
throw new TypeError('options.maxCacheSize, when provided, must be a non-negative integer.');
}
if (options.prefetchProfile && !['none', 'fileSystem', 'network'].includes(options.prefetchProfile)) {
throw new TypeError(
'options.prefetchProfile, when provided, must be one of \'none\', \'fileSystem\' or \'network\'.',
);
}
super();
this._options = options;
this._orchestrator = new ReadOrchestrator({
maxCacheSize: options.maxCacheSize ?? (8 * 2 ** 20 /* 8 MiB */),
maxWorkerCount: 2, // Fixed for now, *should* be fine
prefetchProfile: PREFETCH_PROFILES[options.prefetchProfile ?? 'none'],
runWorker: this._runWorker.bind(this),
});
}
/** @internal */
_retrieveSize(): MaybePromise<number> {
const result = this._options.getSize();
if (result instanceof Promise) {
return result.then((size) => {
if (!Number.isInteger(size) || size < 0) {
throw new TypeError('options.getSize must return or resolve to a non-negative integer.');
}
this._orchestrator.fileSize = size;
return size;
});
} else {
if (!Number.isInteger(result) || result < 0) {
throw new TypeError('options.getSize must return or resolve to a non-negative integer.');
}
this._orchestrator.fileSize = result;
return result;
}
}
/** @internal */
_read(start: number, end: number): MaybePromise<ReadResult> {
return this._orchestrator.read(start, end);
}
private async _runWorker(worker: ReadWorker) {
while (worker.currentPos < worker.targetPos && !worker.aborted) {
const originalCurrentPos = worker.currentPos;
const originalTargetPos = worker.targetPos;
let data = this._options.read(worker.currentPos, originalTargetPos);
if (data instanceof Promise) data = await data;
if (data instanceof Uint8Array) {
if (data.length !== originalTargetPos - worker.currentPos) {
// Yes, we're that strict
throw new Error(
`options.read returned a Uint8Array with unexpected length: Requested ${
originalTargetPos - worker.currentPos
} bytes, but got ${data.length}.`,
);
}
this.onread?.(worker.currentPos, worker.currentPos + data.length);
this._orchestrator.supplyWorkerData(worker, data);
} else if (data instanceof ReadableStream) {
const reader = data.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
if (worker.currentPos < originalTargetPos) {
// Yes, we're *that* strict
throw new Error(
`ReadableStream returned by options.read ended before supplying enough data.`
+ ` Requested ${originalTargetPos - originalCurrentPos} bytes, but got ${
worker.currentPos - originalCurrentPos
}`,
);
}
break;
}
if (!(value instanceof Uint8Array)) {
throw new TypeError('ReadableStream returned by options.read must yield Uint8Array chunks.');
}
this.onread?.(worker.currentPos, worker.currentPos + value.length);
this._orchestrator.supplyWorkerData(worker, value);
if (worker.currentPos >= originalTargetPos || worker.aborted) {
break;
}
}
} else {
throw new TypeError('options.read must return or resolve to a Uint8Array or a ReadableStream.');
}
}
}
}
type PrefetchProfile = (start: number, end: number, workers: ReadWorker[]) => {
start: number;
end: number;
+1 -1
View File
@@ -13,7 +13,7 @@ import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
import { PacketRetrievalOptions } from '../media-sink';
import { assert, UNDETERMINED_LANGUAGE } from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { readAscii, readBytes, Reader, readU16, readU32, readU64 } from '../reader2';
import { readAscii, readBytes, Reader, readU16, readU32, readU64 } from '../reader';
export enum WaveFormat {
PCM = 0x0001,