npaw-plugin-nwf
Version:
NPAW's Plugin
185 lines (184 loc) • 8.85 kB
TypeScript
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<string, string>;
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<string,VideoSegment>} 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;
}