import { SegmentState, SegmentLeaderRole } from '../Utils/Constants'; import { TrackTypeValue } from '../Loaders/P2PSegmentIdResolver'; export type SegmentStateValue = (typeof SegmentState)[keyof typeof SegmentState]; export type SegmentLeaderRoleValue = (typeof SegmentLeaderRole)[keyof typeof SegmentLeaderRole]; /** * Persisted metadata for a stored segment. Kept separate from the bytes so it * can be loaded cheaply at startup to rebuild the in-memory index without * touching the (potentially large) payload blobs. */ export type StoredSegmentMetadata = { id: string; url: string; trackType: TrackTypeValue; rendition?: string; expectedSize: number; state: SegmentStateValue; leaderRole: SegmentLeaderRoleValue; createdAt: number; lastUsedAt: number; writtenBytes: number; failureReason?: string; }; /** * Persistence backend interface. The MVP ships with `IndexedDBBackend` for * browsers (parity with iOS `FileManager` + Android filesystem) and * `InMemoryBackend` as an automatic fallback for Node / jsdom / SSR. * All methods are async: callers must await them. */ export interface DiskSegmentStoreBackend { /** Load the index of all persisted segment metadata. */ loadAllMetadata(): Promise; /** Persist the metadata for a segment (upsert). */ putMetadata(meta: StoredSegmentMetadata): Promise; /** Persist the final byte payload for a segment (once READY). */ putBytes(id: string, bytes: Uint8Array): Promise; /** Fetch the persisted byte payload; returns undefined if not stored. */ getBytes(id: string): Promise; /** Delete both metadata and payload for a segment. */ delete(id: string): Promise; /** Drop the entire store (metadata + payloads). */ clearAll(): Promise; } /** * Persistence backed by the browser's IndexedDB. Origin-scoped, quota-bounded, * survives reload. Mirrors the persistence semantics of iOS `FileManager` and * Android filesystem used by the native `DiskSegmentStore`. */ export declare class IndexedDBBackend implements DiskSegmentStoreBackend { private dbPromise?; static isSupported(): boolean; private openDb; loadAllMetadata(): Promise; putMetadata(meta: StoredSegmentMetadata): Promise; putBytes(id: string, bytes: Uint8Array): Promise; getBytes(id: string): Promise; delete(id: string): Promise; clearAll(): Promise; } /** Fallback used when IndexedDB is unavailable (Node, jsdom, SSR). */ export declare class InMemoryBackend implements DiskSegmentStoreBackend { private meta; private bytes; loadAllMetadata(): Promise; putMetadata(meta: StoredSegmentMetadata): Promise; putBytes(id: string, data: Uint8Array): Promise; getBytes(id: string): Promise; delete(id: string): Promise; clearAll(): Promise; } /** Picks the best available backend. */ export declare function createDefaultBackend(): DiskSegmentStoreBackend; /** * A segment in the local cache. Mirrors iOS `StoredSegment` and Android * `DiskSegmentStore.Entry`. Metadata is always authoritative in memory for the * state machine (ACQUIRING / READY / FAILED / EVICTED); bytes live in memory * during acquisition (for streaming reads) and are flushed through to * IndexedDB on `markReady` so they survive across sessions. */ export declare class StoredSegment { readonly id: string; readonly url: string; readonly trackType: TrackTypeValue; readonly rendition?: string; expectedSize: number; state: SegmentStateValue; leaderRole: SegmentLeaderRoleValue; readonly createdAt: number; lastUsedAt: number; writtenBytes: number; failureReason?: string; /** Per-chunk buffer used while ACQUIRING, discarded once flattened. */ private _chunks; /** Flat payload held in RAM during the session after `markReady`. */ private _finalData?; /** True if the bytes live only in the backend (rebuilt from hydrate()). */ private _bytesHydratedFromBackend; private _waiters; constructor(meta: StoredSegmentMetadata); toMetadata(): StoredSegmentMetadata; touch(): void; isVisibleForRendition(activeVideoRendition: string | undefined): boolean; /** Fast sync read from RAM (returns undefined if bytes live only in backend). */ readDataSync(): Uint8Array | undefined; /** Internal helpers used by the store. */ appendBytes(bytes: Uint8Array): void; finalizeReady(): Uint8Array; adoptFinalData(bytes: Uint8Array): void; needsBytesFromBackend(): boolean; markHydratedWithoutBytes(): void; discard(): void; /** Read in-memory bytes at `offset` (used by GrowingSegmentReader). */ readBytesAt(offset: number, maxBytes: number): Uint8Array | undefined; /** Register a one-shot waiter notified on next state change or append. */ subscribeWaiter(waiter: (state: SegmentStateValue) => void): () => void; private _notifyWaiters; } /** * Segment store with the iOS/Android public API, backed by IndexedDB by * default. The in-memory mirror keeps the state-machine synchronous (so * P2PLoader/CDNLoader flow works like on native) while byte I/O is async * against the backend. * * Typical lifecycle of a captured segment: * 1. `createOrGetAcquiring` → an in-memory `StoredSegment` with state=ACQUIRING. * Metadata is written-through to the backend immediately (for crash recovery). * 2. `append(id, bytes)` while the CDN stream flows. Bytes stay in RAM; any * open `GrowingSegmentReader` is notified so peers can stream the in-flight * segment (early announcement / ACQUIRING serve). * 3. `markReady(id)` flattens the chunks, writes them to the backend (so they * survive a reload), keeps a copy in RAM for the current session, and * returns the finalized segment. * 4. Eviction via `evictExpiredAndOverflow` or explicit `evict(id)` removes * both RAM and backend copies. */ export default class DiskSegmentStore { private segments; private backend; private hydrated; /** * Serialized write chain for backend mutations. Metadata and byte writes * submitted via `queueBackendWrite` run in submission order. Prevents * races where a later-submitted READY write lands on disk before the * earlier ACQUIRING write, leaving a stale state in IndexedDB. */ private writeChain; constructor(backend?: DiskSegmentStoreBackend); /** * Allows tests to await all pending write-through flushes. Returns a * promise that resolves once the internal write chain is quiescent. */ flushPendingWrites(): Promise; private queueBackendWrite; /** * Loads the persisted index from the backend. Does NOT read the byte * payloads — those are fetched lazily on `readDataAsync` / `openReader` * first access. Call once at startup. */ hydrate(): Promise; createOrGetAcquiring(id: string, url: string, trackType: TrackTypeValue, rendition: string | undefined, expectedSize: number, leaderRole: SegmentLeaderRoleValue): StoredSegment; append(id: string, bytes: Uint8Array): void; markReady(id: string): StoredSegment | undefined; markFailed(id: string, reason?: string): StoredSegment | undefined; evict(id: string): StoredSegment | undefined; getReady(id: string): StoredSegment | undefined; get(id: string): StoredSegment | undefined; readyIds(activeVideoRendition?: string): Set; totalReadyBytes(): number; readyCount(): number; evictExpiredAndOverflow(retentionMs: number, maxBytes: number): StoredSegment[]; openReader(id: string): GrowingSegmentReader | undefined; clear(): Promise; /** * Async payload read. Prefers RAM (`readDataSync`); if the bytes were * persisted in a previous session, fetches them from the backend on demand * and caches the result for subsequent reads. */ readDataAsync(id: string): Promise; /** Internal: used by GrowingSegmentReader to pull bytes for READY, ejected-from-RAM entries. */ _backfillBytes(seg: StoredSegment): Promise; private persistMetadata; } /** * Streaming reader over a `StoredSegment`. Returns chunks as they are * appended; awaits up to `waitTimeoutMs` on each call when the segment is * still ACQUIRING. Parity with iOS `GrowingSegmentReader` (sync-in-Swift, * Promise-based in JS). */ export declare class GrowingSegmentReader { private segment; private store?; private offset; private closed; constructor(segment: StoredSegment, store?: DiskSegmentStore); expectedSize(): number; currentState(): SegmentStateValue; hasPendingBytes(): boolean; readNextChunk(maxBytes: number, waitTimeoutMs: number): Promise; close(): void; }