/** * Hybrid decoder: libav.js demux + WebCodecs VideoDecoder + libav audio decode. * * This is the hardware-accelerated path for files in containers mediabunny * can't read (AVI, ASF, FLV) but whose codecs ARE browser-supported. * libav.js handles demuxing, then: * * - **Video**: bridge.packetToEncodedVideoChunk → VideoDecoder (hardware) * - **Audio**: libav ff_decode_multi (software). Chrome's AudioDecoder * rejects raw MP3 packets from AVI, and audio decode is cheap enough * that software decode is fine. * * The demux pump loop, seek handling, and synthetic timestamp logic mirror * fallback/decoder.ts. The key difference is the video decode path. */ import { loadLibav, type LibavVariant } from "../fallback/libav-loader.js"; import { VideoRenderer } from "../fallback/video-renderer.js"; import { AudioOutput } from "../fallback/audio-output.js"; import type { MediaContext } from "../../types.js"; import { dbg } from "../../util/debug.js"; import { pickLibavVariant } from "../fallback/variant-routing.js"; import { sanitizePacketTimestamp, libavFrameToInterleavedFloat32, packetPtsSec, } from "../../util/libav-demux.js"; export interface HybridDecoderHandles { destroy(): Promise; seek(timeSec: number): Promise; /** Swap the active audio track — rebuilds the libav audio decoder + reseeks. */ setAudioTrack(trackId: number, timeSec: number): Promise; stats(): Record; onFatalError(handler: (reason: string) => void): void; /** * The demuxer's read-ahead frontier in seconds — the highest pts * observed on any packet handed back from `ff_read_frame_multi`. * Monotonically non-decreasing: seeks don't reset it, since the * frontier represents "how far we've ever demuxed through this * source," which matches what a seek-bar buffered indicator should * show. Backs `