import P2PLoader from './P2PLoader'; import CDNLoader from './CDNLoader'; import BalancerOptions from '../Utils/Options'; import SegmentStorage from '../Storage/SegmentStorage'; import { VideoSegment } from '../Storage/VideoSegment'; import CdnBalancer from '../CdnBalancer'; import ResourceIdentifier from '../manifest/ResourceIdentifier'; import P2PManifestRegistry from './P2PManifestRegistry'; import { P2PSegmentIdResolver } from './P2PSegmentIdResolver'; import DiskSegmentStore from '../Storage/DiskSegmentStore'; /** * @class * @description Request load main class, it manages and chooses between CDN and P2P requests and evaluates the requests. * @exports Loader */ export default class Loader { private _accountCode; private _resource; private readonly resourceIdentifier; /** * V2 manifest registry: parses HLS/DASH manifests as they arrive, tracks * per-rendition bandwidth and the canonical SwarmIdentity used to join the * same P2P swarm as iOS / Android peers. */ private readonly p2pManifestRegistry; /** * V2 segment identity resolver: delegates to the registry when available, * otherwise falls back to a URL-based canonical key. Produces the 32-hex * segmentIds exchanged over the wire with native peers. */ private readonly p2pSegmentIdResolver; /** * V2 segment cache with ACQUIRING -> READY state machine and (where * supported) IndexedDB-backed persistence for cross-session seeding. */ readonly diskSegmentStore: DiskSegmentStore; private readonly _options; private _balancerBusinessObject; private readonly statsReportBusinessObject; P2PLoader: P2PLoader; CDNLoader: CDNLoader; storage: SegmentStorage; private _segments; segmentsMap: Map; uuid: string; private isEnabled; private apiHost; private manifestModelHits; private manifestFallbacks; /** * Constructs loader. * @param {BalancerOptions} options Options object. */ constructor(accountCode: string, options: BalancerOptions, apiHost: string); getReportInterval(): number; private camelToSnake; loadCDNBalancerData(firstRequest?: boolean): void; /** * Chooses to request to CDN or P2P and manages the response calling the callback. * @param {URL} url URL object for the request. * @param {callback} callback Callback method to call back when loaded.F * @param {Object} headers Optional headers of the request. * @param {number} retries Number of retries before giving up to load a segment if it fails, optional. * @param {boolean} forceArrayBuffer False by default, set as true if is expected to get the manifest response * in ArrayBuffer format. * @public */ processSegment(url: URL, download: boolean, callbacks: callback[], headers: { [key: string]: string; }, retries?: number, forceArrayBuffer?: boolean, stats?: fragStats): VideoSegment; /** * Android `P2pProvider.fetch` / iOS `P2PProvider.fetch` port: * 1. Local disk cache hit -> serve immediately, no network. * 2. Swarm access denied -> fall back to CDN. * 3. Leader election -> if a peer owns the segment, request from them. * 4. Election window elapses with no candidate -> fall back to CDN. * * Runs as a detached Promise so `processSegment` keeps its sync contract * with the player's interceptor. */ private _dispatchP2pV2Async; /** * Single entry point for P2P -> CDN transitions. Flips `useP2P` off, * re-asks CDNLoader for a URL and registers the segment as an active CDN * request so the existing XHR retry machinery picks it up. The switch * counter is incremented at most once per segment (idempotent via * `segment.p2pSwitchRecorded`), so repeated V2/V1 fallbacks for the same * segment never double-count. Call sites are expected to set * `segment.p2pFailureReason` before calling so the reason is consumed * here rather than passed as a parameter; unset defaults to `'errors'`. * Returns `true` when a CDN URL was assigned and the caller can proceed * with the HTTP request, `false` otherwise. */ _fallBackP2pToCdn(segment: VideoSegment): boolean; onProcessSegmentFail(segment: VideoSegment, retry: boolean): void; onProcessSegmentSuccess(segment: VideoSegment): void; /** * Returns the map of unfinished/active CDN requests. * @returns {Map} The map with the requests. * @public */ enableCDN(name: string, enabled: boolean): boolean; setActive(name: string, enabled: boolean): boolean; setMaxBandwith(name: string, bandwidth: number): boolean; onAbort(xhr: XMLHttpRequest, segment: VideoSegment): void; /** * Arm the connect and total timers for one CDN attempt. Each provider hop * uses a fresh XHR so both timers naturally reset per provider (matches * Android CdnTimeoutSettings: totalTimeout is per-attempt, not per-session). * Values <= 0 mean "no deadline" — the corresponding timer is not armed. * * When `probeOverrides` is supplied (PR6 first-chunk probe), the connect * and total timers use those stricter values instead of the defaults. */ armAttemptTimers(xhr: XMLHttpRequest, segment: VideoSegment, probeOverrides?: { connectTimeoutMs: number; totalDownloadTimeoutMs: number; }): void; /** * Re-arm the read timeout. Read timeout is "between socket reads" — armed at * HEADERS_RECEIVED and re-armed on every progress tick. <= 0 disables. */ armReadTimeout(xhr: XMLHttpRequest, segment: VideoSegment): void; clearConnectTimer(xhr: XMLHttpRequest): void; /** * Recompute the trial call deadline from the announced Content-Length, the * current good CDN's bandwidth, and that CDN's idle ratio (L0). Replaces * the active total-attempt timer ONLY when the new deadline is stricter * than the baseline already armed (Android parity: * TrialDeadlineEventListener.responseHeadersEnd, "stricter only" guard). * * NOTE: the Android impl reads L0 from a single global StatsCollector * shared across all providers; the JS impl tracks concurrency per-Cdn. * For the trial decision the current CDN's idle fraction is the relevant * signal (it's the CDN we're protecting from underrun) so the divergence * is benign in `cdnPriority`/single-CDN-active modes — and a closer match * to the writeup's intent ("CDN idle") than a global aggregate would be. */ private _recomputeTrialDeadline; clearAttemptTimers(xhr: XMLHttpRequest, segment: VideoSegment): void; loadEM(xhr: XMLHttpRequest, url: URL, e: ProgressEvent): void; loadStartEM(xhr: XMLHttpRequest, segment: VideoSegment, e: ProgressEvent): void; progressEM(xhr: XMLHttpRequest, segment: VideoSegment, e: ProgressEvent): void; readyStateChangeEM(xhr: XMLHttpRequest, segment: VideoSegment, cdnBalancer: CdnBalancer): boolean; timeout(xhr: XMLHttpRequest, segment: VideoSegment): void; /** * Canonical XHR exit point. `loadend` fires after every termination path — * success, HTTP error, network error, abort, browser timeout — so it is the * one place where teardown is guaranteed to run. Android parity * (`plugin-android@02c4f055` "cancel timeout deadline on every body-source * error path"): without this, a network-level failure that never reaches * `readyState=4` (DNS fail, CORS reject, connection refused) leaves the * connect/total deadlines armed until they fire and `abort()` an already- * dead XHR. `clearAttemptTimers` is idempotent so the double-clear on the * normal success path (which also clears at `readyState=4`) is harmless. */ loadEndEM(xhr: XMLHttpRequest, segment: VideoSegment, _e: ProgressEvent): void; errorEM(xhr: XMLHttpRequest, segment: VideoSegment, e: ProgressEvent): void; getStats(): LoaderStats; destroy(): void; enable(): void; disable(): void; getIsEnabled(): boolean; getResourceIdentifier(): ResourceIdentifier; getP2PManifestRegistry(): P2PManifestRegistry; getP2PSegmentIdResolver(): P2PSegmentIdResolver; getDiskSegmentStore(): DiskSegmentStore; getManifestResolutionStats(): { parseCount: number; resolveCount: number; resolveHits: number; resolveMisses: number; lookaheadHits: number; lastHitFastHits: number; lastHitForceHits: number; multiPassHits: number; subManifestAttachments: number; manifestCount: number; lookaheadSize: number; modelHits: number; fallbackCount: number; }; private getByteRangeOffset; }