/** * Demuxer - FFmpeg-based demuxer using Asyncify for async I/O */ import type { SourceAdapter } from "../source/SourceAdapter"; import type { Track, VideoTrack, AudioTrack, SubtitleTrack, MediaInfo, Packet } from "../types"; import { WasmBindings, type MoviWasmModule } from "../wasm"; export declare class Demuxer { private source; private module; private bindings; private tracks; private duration; /** This demuxer holds the shared main-playback module — see claimSharedModule. */ private _holdsSharedModule; private isOpened; private wasmBinary?; private useNewWasmInstance; /** * @param source - Data source adapter * @param wasmBinary - Optional WASM binary * @param useNewWasmInstance - If true, creates isolated WASM instance (for preview pipeline) */ constructor(source: SourceAdapter, wasmBinary?: Uint8Array, useNewWasmInstance?: boolean); /** * Open the media file and parse metadata */ open(): Promise; /** * Last resort for a container that stores no duration — demux to EOF and take * the largest packet end timestamp (see movi_scan_duration in the WASM layer). * * The files this rescues are downloads that were never finalised: a Matroska * muxed in live mode has an unknown-size segment, no Duration element and no * Cues, so FFmpeg reports AV_NOPTS_VALUE and the player shows 0:00 with a dead * seek bar. Native `ffprobe` prints duration=N/A on the same file. * * The scan is only ever expensive in I/O, so what it costs depends entirely on * where the bytes come from, and the gating splits three ways: * * - Local (a File, an object URL, or the desktop shell's loopback server): * a disk read. Scan it, no size limit. * - Segmented (HLS/DASH): never. Those are assembled from a playlist whose * window slides, and the stream wrapper — not the container — is the * authority on their duration. Handing one a scanned window length would * clamp the clock to a number that goes stale as the window moves. * - Plain remote: only when the server declares a size and that size is small * enough to be worth the download. Requiring Content-Length is also what * keeps chunked/endless responses out. */ private recoverMissingDuration; /** * Are this source's bytes free to read end-to-end? These skip the size gate * the missing-duration scan applies to everything else. * * A File or an object URL is disk- or memory-backed, so a full pass is free. * Loopback HTTP counts too, and has to: the desktop shell can't use file:// * (the demuxer needs COOP/COEP, which file:// can't carry) so it serves * OS-opened files from 127.0.0.1 as /_local?p=. Those arrive here as an * http: key even though every byte comes off the local disk — which is why * the scan quietly did nothing in the desktop app while working in the web * one on the same file. * * A loopback origin alone isn't sufficient, though: the same server also * exposes /_proxy?url= to stream a *remote* URL past CORS. So reject * any loopback URL that carries an absolute http(s) URL in its query — that's * the network wearing a local address. */ /** * How long a BACKGROUND rescan may spend reading this source end to end, or * null when it isn't worth attempting. * * The blocking scan above is capped in the single seconds because a load is * waiting on it, and on anything but a small file it gives up having read a * few percent (a 291MB Matroska got 14MB in). Off the load path there is * nothing to hold up, so the budget can be the honest cost of one pass — * what remains is deciding which sources are worth a pass at all. * * Local: bytes are free, but the pass is still real disk and real battery, * so it is bounded by a size the scan can finish in about a minute at a * pessimistic 10MB/s. Remote keeps the download gate it already had. */ static durationRescanBudgetMs(key: string, size: number): number | null; static isLocallyBacked(key: string): boolean; /** * Enumerate all tracks */ private enumerateTracks; /** * Convert StreamInfo to Track */ /** * Convert StreamInfo to Track */ private convertStreamInfo; /** * Check if subtitle codec is image-based */ private isImageSubtitle; /** * Normalize FFmpeg color primaries to WebCodecs enum values */ private normalizeColorPrimaries; /** * Normalize FFmpeg color transfer to WebCodecs enum values */ private normalizeColorTransfer; /** * Normalize FFmpeg color matrix names to WebCodecs enum values */ private normalizeColorMatrix; /** * Get all tracks */ getTracks(): Track[]; /** * Get video tracks */ getVideoTracks(): VideoTrack[]; /** * Get audio tracks */ getAudioTracks(): AudioTrack[]; /** * Get subtitle tracks */ getSubtitleTracks(): SubtitleTrack[]; /** * Get extradata for a track */ getExtradata(trackId: number): Uint8Array | null; /** * Seek to timestamp (async due to Asyncify) */ seek(timestamp: number, flags?: number): Promise; /** * Read next packet (async due to Asyncify) */ readPacket(): Promise; /** * Get duration */ getDuration(): number; /** * Close and cleanup */ close(): void; /** * Extract embedded cover art (attached_pic) as the raw encoded image * bytes (jpeg / png), or null if the source has none. * * Runs in a short-lived, isolated WASM context — NOT the live playback * demuxer — so reading the artwork packet never moves the main read * position and can't disturb playback or seeking. The cover art is the * single keyframe of the still-image "video" stream, so readKeyframe(0) * returns its packet directly; no decode pass and no extra C exports * are needed (a dedicated movi_get_attached_pic_data export would shift * the WASM layout and trip a latent FFmpeg audio overflow — see project * memory "Album Art Crashes WASM"). Caller owns MIME-typing and decode * into an ImageBitmap. Best-effort: any failure resolves to null. */ static extractAttachedPicture(source: SourceAdapter, fileSize: number, wasmBinary?: Uint8Array): Promise; getBindings(): WasmBindings | null; getModule(): MoviWasmModule | null; } //# sourceMappingURL=Demuxer.d.ts.map