/** * ThumbnailHttpSource - Buffered HTTP source for thumbnail extraction. * * Uses a simple sliding buffer to cache data and reduce HTTP requests. * Each seek position triggers a larger chunk fetch, and subsequent reads * are served from the buffer until a new seek is needed. */ import type { SourceAdapter } from "./SourceAdapter"; /** * Minimal interface describing a source we can peek into without mutating. * HttpSource implements this structurally via peekMetadata() / peekRange(). */ export interface PeekableSource { peekMetadata(offset: number, length: number): Uint8Array | null; peekRange(offset: number, length: number): Uint8Array | null; /** Opening bytes the main source held on to. Optional: not every peekable * source keeps a head cache. */ peekHead?(offset: number, length: number): Uint8Array | null; } export declare class ThumbnailHttpSource implements SourceAdapter { private url; private headers; private size; private position; private abortController; /** * This source's own lifetime, aborted by close(). * * `abortController` above only ever covered one path; the size probes and the * ranged read each built their own local controller (the read's on a 10s * timer), which close() could not see. So tearing the preview pipeline down * mid-hover left a range request downloading to its own deadline. */ private readonly lifetimeAbort; private rangeUnsupported; private buffer; private bufferStart; private bufferEnd; /** * The end of the window currently STREAMING IN, and the machinery to wait on * it. `bufferEnd` is how far the bytes have actually got; `fillEnd` is where * they are going. While the two differ, a fetch is still arriving. * * A read used to wait for the whole 2MB window before it saw a single byte, * and the demuxer asks for 32KB. Measured on a 404MB AV1 MKV over R2: one * hover = one 32KB read, served by a 2048KB download the preview blocked on * for 7.0s, of which the first byte arrived after 0.33s. The window is the * right size — it is what keeps a hover to ONE request — but nothing about * it requires the answer to wait for its tail. */ private fillEnd; private fillToken; private fillWaiters; /** Wake everything waiting on the stream to reach further. */ private notifyFill; /** * Abandon whatever is streaming in: the bytes it is carrying are no longer * wanted, or the buffer they were going into has gone. */ private invalidateFill; /** * Wait until the in-flight window has reached `target`, or has stopped * short of it. True when the bytes are there. */ private awaitFilled; private borrowSource; constructor(url: string, headers?: Record, borrowSource?: PeekableSource | null); setBorrowSource(source: PeekableSource | null): void; /** * Seed the file size from the main source so getSize() skips its own HEAD / * ranged-GET probe. Essential for non-range servers where that probe can't * recover a size (Cloudflare strips Content-Length on HEAD and may chunk the * 200 GET) — the main source already knows the size, so just reuse it. */ seedSize(size: number): void; getSize(): Promise; /** * Recover total size from a 1-byte ranged GET when HEAD lacks Content-Length. * Body is cancelled immediately — we only need the Content-Range header. */ private resolveSizeViaRange; /** * Check if requested data is in buffer */ private isInBuffer; /** * Read data - uses buffer cache to minimize HTTP requests */ read(offset: number, length: number): Promise; seek(offset: number): number; getPosition(): number; /** * Stop the window that is still streaming in, keeping what has arrived. * * The window is 2MB so that the next hovers of a scrub land inside it, but * the frame that asked for it needed its first 32KB: measured at 12 Mbit, * the preview was on screen 0.17s into the request and the other 1.9MB kept * downloading for 1.3s after — four seconds of it on a 4 Mbit link — for a * pointer that had stopped moving. The bytes already here still serve any * read that falls inside them. */ stopFill(): void; /** * Clear buffer to free memory when thumbnails aren't being actively generated * Call this after thumbnail generation is complete */ clearBuffer(): void; close(): void; getKey(): string; getUrl(): string; getBufferedEnd(): number; getBufferStart(): number; } export declare function createThumbnailHttpSource(url: string, headers?: Record): Promise; //# sourceMappingURL=ThumbnailHttpSource.d.ts.map