import type { Readable } from "node:stream"; /** * Construction-time options for {@link Mp4SegmentAssembler}. * * @property segmentTimeout - Optional watchdog window, in milliseconds. The timer arms when the initialization segment resolves (we begin expecting media segments) * and re-arms on each completed media segment. If no segment arrives within the window, the assembler aborts with * `HbpuAbortError("timeout")` and the generator terminates cleanly. Typical value for HKSV is a little under five seconds. * @property signal - Optional parent {@link AbortSignal} to compose with the assembler's internal controller. When the parent aborts, the assembler tears * down and the segment generator exits. * * @category FFmpeg */ export interface Mp4SegmentAssemblerInit { segmentTimeout?: number; signal?: AbortSignal; } /** * The kind of a segment yielded by {@link Mp4SegmentAssembler.stream}. `"init"` is the one-shot initialization segment; `"media"` is a continuous media fragment. A * consumer that paces or forwards every item uniformly can read {@link Mp4Segment.bytes} without branching; a consumer that must treat the init segment specially * branches on this field. * * @category FFmpeg */ export type Mp4SegmentKind = "init" | "media"; /** * A single fMP4 segment yielded by {@link Mp4SegmentAssembler.stream}, tagged with its kind so a consumer can tell the one-shot initialization segment apart from the * media fragments that follow it without relying on positional ordering. * * @property bytes - The complete segment bytes: for `"init"`, the concatenated initialization boxes (typically `ftyp` + `moov`); for `"media"`, a concatenated * `moof` + `mdat` pair. * @property kind - `"init"` for the single leading initialization segment, `"media"` for each subsequent media fragment. * * @category FFmpeg */ export interface Mp4Segment { bytes: Buffer; kind: Mp4SegmentKind; } /** * AsyncDisposable fMP4 segment assembler that converts a Readable byte source into an init segment promise and a media-segment async generator. * * Construction kicks off a background drain loop that feeds {@link Mp4BoxParser} from the source's `data` events and routes each parsed box through a small state * machine: everything before the first `moof` accumulates into the initialization segment; from the first `moof` onward, boxes accumulate into the current media * segment until an `mdat` flushes the accumulated pair to the output queue. * * The single public teardown verb is {@link Mp4SegmentAssembler.abort}, mirroring `AbortController.abort()`. `Symbol.asyncDispose` is implemented in terms of it and * awaits the drain loop's completion before returning, so `await using` guarantees the assembler has fully unwound by the time the surrounding scope exits. * * @example * * ```ts * import { Mp4SegmentAssembler } from "homebridge-plugin-utils"; * * await using assembler = new Mp4SegmentAssembler(ffmpegStdout, { segmentTimeout: 4500, signal: session.signal }); * * const initSegment = await assembler.initSegment; * * for await (const segment of assembler.segments()) { * * // Forward segment bytes to HomeKit. * } * ``` * * @see Mp4BoxParser * * @category FFmpeg */ export declare class Mp4SegmentAssembler implements AsyncDisposable { #private; /** * The composed abort signal representing this assembler's lifetime. Aborts exactly once when the source ends, the source errors, the parent signal fires, the * watchdog timeout expires, or {@link Mp4SegmentAssembler.abort} is called; the reason encoded on `signal.reason` names the cause. */ readonly signal: AbortSignal; /** * Promise that resolves with the concatenated initialization-segment bytes (typically `ftyp` + `moov`) once the first `moof` box arrives on the source. Rejects with * `this.signal.reason` if the assembler is aborted before the initialization segment completes. */ readonly initSegment: Promise; /** * Construct and start a new fMP4 segment assembler. * * The drain loop starts synchronously as part of construction: by the time the constructor returns, the source's `data` events are being observed and the parser is * ready to emit boxes. There is no separate `start()` step. * * @param source - Any {@link Readable} producing fMP4 byte chunks. Typically an FFmpeg process's stdout; any Readable works, which keeps the class testable in * isolation with in-memory fixture streams. * @param init - Optional init options. See {@link Mp4SegmentAssemblerInit}. */ constructor(source: Readable, init?: Mp4SegmentAssemblerInit); /** * Abort the assembler and tear it down. Defaults to `HbpuAbortError("shutdown")` when no reason is supplied; explicit reasons pass through unchanged. * * Safe to call more than once: subsequent calls are no-ops because the underlying signal only aborts once. Calling `abort()` after natural completion is also safe * for the same reason. * * @param reason - Optional abort reason. Typically an {@link HbpuAbortError}; platform errors (`TimeoutError`, `AbortError`) also interoperate by convention. */ abort(reason?: unknown): void; /** * `AsyncDisposable` implementation. Aborts the assembler (defaulting to `"shutdown"`) and awaits actual drain-loop completion before returning, so callers using * `await using` are guaranteed every internal listener has been detached by the time the block exits. * * @returns A promise that resolves once the drain loop has fully exited. */ [Symbol.asyncDispose](): Promise; /** * `true` once `this.signal` has aborted. Derived from the signal; no independent state. */ get aborted(): boolean; /** * `true` when the abort reason indicates a timeout. Matches both the canonical `HbpuAbortError("timeout")` emitted by the inter-segment watchdog and the platform * `TimeoutError` emitted by `AbortSignal.timeout()`. The branching lives in {@link isTimeoutReason} so this getter stays a one-line delegation and every * resource class in the library shares one definition of "timeout." */ get isTimedOut(): boolean; /** * The number of completed media segments buffered between the drain loop and the {@link Mp4SegmentAssembler.segments} consumer - the segments the producer has * assembled but the consumer has not yet pulled. A consumer pacing its reads slower than the source produces accrues a reserve here, and that reserve is what * absorbs an upstream stall: the consumer keeps pulling buffered segments while no new ones arrive. It is zero in steady state when the consumer keeps pace. */ get bufferedSegments(): number; /** * Async generator yielding each completed media segment (concatenated `moof` + `mdat` pair) as a single Buffer. * * Yields only after {@link Mp4SegmentAssembler.initSegment} has resolved - the init segment is not surfaced through this stream. Terminates cleanly when the source * ends, the assembler aborts, or the optional caller signal aborts; in every case the queue is drained before the generator returns, so a consumer never loses a * segment that was already assembled before teardown. * * **Single-consumer only.** The internal parked-waiter slot is single-writer; calling `segments()` concurrently with another consumer on the same assembler - including * the {@link Mp4SegmentAssembler.stream} view, which drives this generator internally - is unsupported and will hang one of the consumers when the producer's wake-up * resolves only the later parker. If fan-out is needed, tee at the consumer side by replicating each yielded Buffer into per-consumer queues external to the assembler. * * @param init - Optional init options. `signal` composes with the assembler's own signal - aborting it terminates only this generator call, not the assembler. * * @returns An async generator yielding concatenated `moof` + `mdat` pair buffers in stream order. */ segments(init?: { signal?: AbortSignal; }): AsyncGenerator; /** * Async generator yielding the whole segment stream as a kind-tagged sequence: exactly one {@link Mp4Segment} of kind `"init"` carrying the initialization bytes, * followed by one of kind `"media"` per completed media fragment. This is a third view over the same single-pass pipeline, composed from {@link initSegment} and * {@link segments} - it lets a consumer forward the init segment and the media segments through a single loop without tracking which item is which by position. * * Terminates cleanly on the same conditions as {@link segments}: the source ends, the assembler aborts, or the optional caller signal aborts; queued media drains * before the generator returns, so no assembled segment is lost. If the assembler is aborted before the initialization segment arrives, the generator returns without * yielding anything. * * **Single-consumer only.** `stream()` drives {@link segments} internally, so it shares the one parked-waiter slot. Use `stream()` OR the {@link initSegment} / * {@link segments} pair on a single assembler, never both concurrently - mixing them competes for the same drain and hangs one consumer. * * @param init - Optional init options. `signal` composes with the assembler's own signal - aborting it terminates only this generator call, not the assembler. * * @returns An async generator yielding one `"init"` segment followed by `"media"` segments in stream order. */ stream(init?: { signal?: AbortSignal; }): AsyncGenerator; } //# sourceMappingURL=mp4-assembler.d.ts.map