Start work on HlsOutputFormat

This commit is contained in:
Vanilagy
2026-03-17 15:25:06 +01:00
parent e42fc85f19
commit dd8621b6d5
17 changed files with 619 additions and 72 deletions
+56 -14
View File
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { AsyncMutex, isIso639Dash2LanguageCode, Rotation } from './misc';
import { assert, AsyncMutex, isIso639Dash2LanguageCode, MaybePromise, Rotation } from './misc';
import { MetadataTags, TrackDisposition, validateMetadataTags, validateTrackDisposition } from './metadata';
import { Muxer } from './muxer';
import { OutputFormat } from './output-format';
@@ -14,6 +14,10 @@ import { AudioSource, MediaSource, SubtitleSource, VideoSource } from './media-s
import { Target } from './target';
import { Writer } from './writer';
export type TargetRequest = {
path: string;
};
/**
* The options for creating an Output object.
* @group Output files
@@ -26,7 +30,8 @@ export type OutputOptions<
/** The format of the output file. */
format: F;
/** The target to which the file will be written. */
target: T;
target: T | ((request: TargetRequest) => MaybePromise<T>);
rootPath?: string;
};
/**
@@ -154,14 +159,16 @@ export class Output<
/** The format of the output file. */
format: F;
/** The target to which the file will be written. */
target: T;
target: T | ((request: TargetRequest) => MaybePromise<T>);
/** The current state of the output. */
state: 'pending' | 'started' | 'canceled' | 'finalizing' | 'finalized' = 'pending';
/** @internal */
_rootPath: string | null;
/** @internal */
_muxer: Muxer;
/** @internal */
_writer: Writer;
_rootWriterPromise: Promise<Writer> | null = null;
/** @internal */
_tracks: OutputTrack[] = [];
/** @internal */
@@ -186,22 +193,52 @@ export class Output<
if (!(options.format instanceof OutputFormat)) {
throw new TypeError('options.format must be an OutputFormat.');
}
if (!(options.target instanceof Target)) {
if (!(options.target instanceof Target) && typeof options.target !== 'function') {
throw new TypeError('options.target must be a Target.');
}
if (options.target._output) {
throw new Error('Target is already used for another output.');
if (options.target instanceof Target) {
if (options.target._output) {
throw new Error('Target is already used for another output.');
}
options.target._output = this;
}
if (options.rootPath !== undefined && typeof options.rootPath !== 'string') {
throw new TypeError('options.rootPath, when provided, must be a string.');
}
if (typeof options.target === 'function' && options.rootPath === undefined) {
throw new Error('options.rootPath must be provided when options.target is a function.');
}
options.target._output = this;
this.format = options.format;
this.target = options.target;
this._writer = options.target._createWriter();
this._rootPath = options.rootPath ?? null;
this._muxer = options.format._createMuxer(this);
}
_getTarget(request: TargetRequest) {
assert(typeof this.target === 'function');
return this.target(request);
}
_getRootWriter() {
return this._rootWriterPromise ??= (async () => {
let writer: Writer;
if (typeof this.target === 'function') {
assert(this._rootPath !== null);
const rootTarget = await this._getTarget({ path: this._rootPath });
writer = rootTarget._createWriter();
} else {
writer = this.target._createWriter();
}
writer.start();
return writer;
})();
}
/** Adds a video track to the output with the given source. Can only be called before the output is started. */
addVideoTrack(source: VideoSource, metadata: VideoTrackMetadata = {}) {
if (!(source instanceof VideoSource)) {
@@ -400,7 +437,6 @@ export class Output<
return this._startPromise = (async () => {
this.state = 'started';
this._writer.start();
const release = await this._mutex.acquire();
@@ -445,7 +481,9 @@ export class Output<
const promises = this._tracks.map(x => x.source._flushOrWaitForOngoingClose(true)); // Force close
await Promise.all(promises);
await this._writer.close();
if (this._rootWriterPromise) {
await (await this._rootWriterPromise).close();
}
release();
})();
@@ -477,8 +515,12 @@ export class Output<
await this._muxer.finalize();
await this._writer.flush();
await this._writer.finalize();
if (this._rootWriterPromise) {
console.log('HERE HERE');
const rootWriter = await this._rootWriterPromise;
await rootWriter.flush();
await rootWriter.finalize();
}
this.state = 'finalized';