/** * HttpSource - SharedArrayBuffer Streaming with Atomics * * Uses SharedArrayBuffer for zero-copy data sharing. * Atomics for thread-safe concurrent access. */ import type { SourceAdapter } from "./SourceAdapter"; export declare class HttpSource implements SourceAdapter { private url; private headers; private size; private position; private _contentDispositionFilename; private headBuffer; private metadataCache; private metadataCacheBytes; private sharedBuffer; private headerView; private dataView; private useSharedBuffer; private fallbackBuffer; private fallbackStart; private fallbackWritePos; private fallbackStreaming; private reader; private abortController; private streamError; private maxBufferedEnd; private fullyBuffered; private rangeUnsupported; private linearMode; private onLinearMode; private readMax; private recentReads; private consecutiveForceRestarts; private lastForceRestartTime; private readonly MAX_FORCE_RESTARTS; private consecutiveOneOffFetches; private readonly MAX_ONEOFF_BEFORE_RESTART; private bufferSize; private totalBytesDownloaded; private streamStartTime; private lastSpeedBytes; private lastSpeedTime; private currentSpeed; private maxBufferSizeMB; constructor(url: string, headers?: Record, maxBufferSizeMB?: number); /** * Initialize buffer (SharedArrayBuffer if available, fallback otherwise) * Starts with minimum size (2MB), will be resized to 3% of file size when known */ private initBuffer; /** * Override the maximum buffer size cap at runtime. Takes effect on the * next resizeBuffer() call (typically the post-resolveSize pass) — and * immediately re-runs the buffer-sizing logic if a size is already * known, so UI attribute changes reflect without needing a reload. * Pass a number of megabytes. 0 or negative values are ignored. */ setMaxBufferSize(megabytes: number): void; /** * Register a callback fired once when the source falls back to linear * (forward-only, non-seekable) playback because the server has no Range * support and the file is too large to cache whole. The UI uses this to * hide the timeline and disable seeking/thumbnails. */ setOnLinearMode(cb: () => void): void; /** True once the source is in forward-only linear (non-seekable) playback. */ isLinearMode(): boolean; /** * True once we've confirmed the server has no Range support (covers both the * full-cache and the linear fallback). Callers use it to skip features that * need scattered random-access reads (e.g. the thumbnail pipeline). */ isRangeUnsupported(): boolean; /** * Resize buffer based on file size (3% of file, clamped to min/max) */ private resizeBuffer; /** * Atomic operations for SharedArrayBuffer */ private atomicGetWritePos; private atomicSetWritePos; private atomicGetBufferStart; private atomicSetBufferStart; private atomicIsStreaming; private atomicSetStreaming; private atomicIncrementVersion; /** * Try to acquire lock (non-blocking) */ private tryLock; private unlock; /** * Resolve the total file size. Default implementation issues a HEAD * request and parses Content-Length + Content-Disposition. Override to * source the size from elsewhere (auth token response, database, etc.); * throw on failure. */ protected resolveSize(): Promise; /** * Recover the total file size from a 1-byte ranged GET when HEAD didn't * carry Content-Length. Reads the total out of Content-Range; returns null * if the server gives us nothing usable. The body is cancelled immediately — * we only want headers, and a server that ignores Range would otherwise * start streaming the whole file. */ protected resolveSizeViaRange(): Promise; /** * Last-resort size probe: a plain (un-ranged) GET. Catches servers that strip * Content-Length on HEAD and don't CORS-expose Content-Range on a 206, yet * still send Content-Length on a full 200 response. The body is cancelled the * moment headers are in, so nothing past the headers is downloaded. */ protected resolveSizeViaPlainGet(): Promise; /** Pull a download filename out of a Content-Disposition header, if present. */ private setFilenameFromDisposition; /** * Build the HTTP headers used for every outbound request (HEAD, range * GET, stream GET). Default implementation just returns the static * headers provided to the constructor. Override to inject per-request * auth/signing headers (token, HMAC signature, nonce, timestamp, ...); * note this is called many times across a playback session, so any * expensive work should be cached/memoised by the subclass. * * @param range Optional byte range the caller will request. Subclasses * that sign the range (e.g. HMAC over `offset/length`) need * this; pass-through callers can ignore it. */ protected buildRequestHeaders(range?: { offset: number; length: number; }): Promise>; getSize(): Promise; getContentDispositionFilename(): string | null; private get bufferEnd(); private isInBuffer; private getBuffer; /** * Read-only peek into the persistent head cache. * Returns a copy if the full range is covered, null otherwise. * Does not mutate position/state, safe for cross-source borrowing. */ peekHead(offset: number, length: number): Uint8Array | null; /** * Record a freshly-served small read into the metadata LRU. * Ignores reads larger than METADATA_CACHE_MAX_CHUNK (those are payload, * not metadata). The caller must pass bytes that will not be mutated — * we keep the reference as-is. read() already hands us fresh Uint8Arrays. */ private cacheMetadataRead; /** * Read-only peek into the metadata LRU. * Returns a fresh copy if any cached chunk fully covers [offset, offset+length). * Bumps matched entry to most-recent. Safe for cross-source borrowing. */ peekMetadata(offset: number, length: number): Uint8Array | null; /** * Read-only peek into the sliding window buffer. * Returns a copy if the full range is present, null otherwise. * Seqlock-guarded via HEADER.VERSION — if the window shifts mid-copy * (new stream started), returns null so the caller can fall back. * Does not mutate position/state, safe for cross-source borrowing. */ peekRange(offset: number, length: number): Uint8Array | null; /** * Start streaming from offset */ private startStream; private readStreamBackground; /** * Consume a single 200 response as the whole file from byte 0, used when the * server has no Range support. Two outcomes: * - File fits the buffer cap → cache it entirely → full random access * (seek/thumbnails keep working once downloaded). * - File exceeds the cap (or size unknown) → bounded forward-only sliding * window (linearMode): playback is linear, seeking impossible. The UI is * notified via onLinearMode so it can hide the timeline. * The response body MUST start at byte 0 (caller guarantees startOffset===0). */ private consumeNonRangeStream; /** * Append a chunk to the buffer for the non-range stream. In full-cache mode * the buffer always has room. In linearMode it slides the window forward by * discarding already-consumed bytes, applying backpressure (waiting for the * demuxer to catch up) when the window can't be advanced yet. */ private writeSequential; /** * Slide the linear-mode window forward to make room for new download. Keeps a * trailing history of up to LINEAR_TRAIL_BYTES behind the playhead so the user * can seek backward into already-played content that's still in RAM — only the * bytes older than that are dropped. Returns false when nothing can be dropped * yet (playhead hasn't advanced past the trailing window), so the caller * applies backpressure. This bounds read-ahead to (bufferSize - trail). */ private slideWindow; private stopStream; private waitForData; read(offset: number, length: number): Promise; private _readInternal; private readFromBuffer; seek(offset: number): number; getPosition(): number; /** * Get the shared buffer for zero-copy access from workers */ getSharedBuffer(): SharedArrayBuffer | null; close(): void; getKey(): string; getUrl(): string; /** * Returns true when the entire file has been downloaded and is cached in the buffer. * In this state, all seek/replay operations are served from memory (zero network). */ isFullyCached(): boolean; /** * Get the current buffered end position in bytes * This represents the furthest byte that has been buffered * Uses the maximum of current buffer window and historical max position, * but caps it to not exceed what's actually available */ getBufferedEnd(): number; /** * Get the current buffer start position in bytes */ getBufferStart(): number; /** * Get network stats for nerd stats overlay */ getNetworkStats(): { totalBytes: number; currentSpeed: number; elapsed: number; }; } export declare function createHttpSource(url: string, headers?: Record, maxBufferSizeMB?: number): Promise; //# sourceMappingURL=HttpSource.d.ts.map