diff --git a/packages/p2p-media-loader-core/src/core.ts b/packages/p2p-media-loader-core/src/core.ts index 76e01baa..12a27d1e 100644 --- a/packages/p2p-media-loader-core/src/core.ts +++ b/packages/p2p-media-loader-core/src/core.ts @@ -32,6 +32,7 @@ export class Core { }; static readonly DEFAULT_STREAM_CONFIG: StreamConfig = { + isP2PDisabled: false, simultaneousHttpDownloads: 3, simultaneousP2PDownloads: 3, highDemandTimeWindow: 15, @@ -147,15 +148,16 @@ export class Core { * core.applyDynamicConfig(dynamicConfig); */ applyDynamicConfig(dynamicConfig: DynamicCoreConfig) { - overrideConfig(this.commonCoreConfig, dynamicConfig); - overrideConfig(this.mainStreamConfig, dynamicConfig); - overrideConfig(this.secondaryStreamConfig, dynamicConfig); + const { mainStream, secondaryStream } = dynamicConfig; + + this.overrideAllConfigs(dynamicConfig, mainStream, secondaryStream); - if (dynamicConfig.mainStream) { - overrideConfig(this.mainStreamConfig, dynamicConfig.mainStream); + if (this.mainStreamConfig.isP2PDisabled) { + this.destroyStreamLoader("main"); } - if (dynamicConfig.secondaryStream) { - overrideConfig(this.secondaryStreamConfig, dynamicConfig.secondaryStream); + + if (this.secondaryStreamConfig.isP2PDisabled) { + this.destroyStreamLoader("secondary"); } } @@ -282,6 +284,7 @@ export class Core { } const segment = this.identifySegment(segmentLocalId); + const loader = this.getStreamHybridLoader(segment); void loader.loadSegment(segment, callbacks); } @@ -330,6 +333,30 @@ export class Core { this.streamDetails.isLive = isLive; } + isSegmentLoadableByP2PCore(segmentLocalId: string): boolean { + try { + const segment = this.identifySegment(segmentLocalId); + + if ( + segment.stream.type === "main" && + this.mainStreamConfig.isP2PDisabled + ) { + return false; + } + + if ( + segment.stream.type === "secondary" && + this.secondaryStreamConfig.isP2PDisabled + ) { + return false; + } + + return true; + } catch { + return false; + } + } + /** * Cleans up resources used by the Core instance, including destroying any active stream loaders * and clearing stored segments. @@ -362,6 +389,34 @@ export class Core { return segment; } + private overrideAllConfigs( + dynamicConfig: DynamicCoreConfig, + mainStream?: Partial, + secondaryStream?: Partial, + ) { + overrideConfig(this.commonCoreConfig, dynamicConfig); + overrideConfig(this.mainStreamConfig, dynamicConfig); + overrideConfig(this.secondaryStreamConfig, dynamicConfig); + + if (mainStream) { + overrideConfig(this.mainStreamConfig, mainStream); + } + + if (secondaryStream) { + overrideConfig(this.secondaryStreamConfig, secondaryStream); + } + } + + private destroyStreamLoader(streamType: "main" | "secondary") { + if (streamType === "main") { + this.mainStreamLoader?.destroy(); + this.mainStreamLoader = undefined; + } else { + this.secondaryStreamLoader?.destroy(); + this.secondaryStreamLoader = undefined; + } + } + private getStreamHybridLoader(segment: SegmentWithStream) { if (segment.stream.type === "main") { this.mainStreamLoader ??= this.createNewHybridLoader(segment); diff --git a/packages/p2p-media-loader-core/src/declarations.d.ts b/packages/p2p-media-loader-core/src/declarations.d.ts index 499902c5..7d41e9ae 100644 --- a/packages/p2p-media-loader-core/src/declarations.d.ts +++ b/packages/p2p-media-loader-core/src/declarations.d.ts @@ -33,6 +33,8 @@ declare module "bittorrent-tracker" { connect: () => void; data: (data: Uint8Array) => void; close: () => void; + finish: () => void; + end: () => void; error: (error: { code: string }) => void; }; diff --git a/packages/p2p-media-loader-core/src/hybrid-loader.ts b/packages/p2p-media-loader-core/src/hybrid-loader.ts index 4bcf555e..09e0a8e0 100644 --- a/packages/p2p-media-loader-core/src/hybrid-loader.ts +++ b/packages/p2p-media-loader-core/src/hybrid-loader.ts @@ -544,6 +544,7 @@ export class HybridLoader { clearInterval(this.storageCleanUpIntervalId); clearInterval(this.randomHttpDownloadInterval); this.storageCleanUpIntervalId = undefined; + this.engineRequest?.abort(); this.requests.destroy(); this.p2pLoaders.destroy(); } diff --git a/packages/p2p-media-loader-core/src/p2p/peer.ts b/packages/p2p-media-loader-core/src/p2p/peer.ts index 8a2078e2..48e0f060 100644 --- a/packages/p2p-media-loader-core/src/p2p/peer.ts +++ b/packages/p2p-media-loader-core/src/p2p/peer.ts @@ -62,8 +62,11 @@ export class Peer { eventTarget.getEventDispatcher("onPeerConnect")({ peerId: this.id, }); - connection.on("close", this.onPeerConnectionClosed); + connection.on("error", this.onConnectionError); + connection.on("close", this.onPeerConnectionClosed); + connection.on("end", this.onPeerConnectionClosed); + connection.on("finish", this.onPeerConnectionClosed); } get downloadingSegment(): SegmentWithStream | undefined { @@ -306,6 +309,8 @@ export class Peer { if (error.code === "ERR_DATA_CHANNEL") { this.destroy(); + } else if (error.code === "ERR_CONNECTION_FAILURE") { + this.destroy(); } }; diff --git a/packages/p2p-media-loader-core/src/requests/request-container.ts b/packages/p2p-media-loader-core/src/requests/request-container.ts index 8c43395c..b7ca0a9a 100644 --- a/packages/p2p-media-loader-core/src/requests/request-container.ts +++ b/packages/p2p-media-loader-core/src/requests/request-container.ts @@ -72,6 +72,7 @@ export class RequestsContainer { destroy() { for (const request of this.requests.values()) { + if (request.status !== "loading") continue; request.abortFromProcessQueue(); } this.requests.clear(); diff --git a/packages/p2p-media-loader-core/src/types.ts b/packages/p2p-media-loader-core/src/types.ts index 73f41fba..1f2150c8 100644 --- a/packages/p2p-media-loader-core/src/types.ts +++ b/packages/p2p-media-loader-core/src/types.ts @@ -77,7 +77,8 @@ export type DynamicStreamProperties = | "httpErrorRetries" | "p2pErrorRetries" | "validateP2PSegment" - | "httpRequestSetup"; + | "httpRequestSetup" + | "isP2PDisabled"; /** * Represents a dynamically modifiable configuration, allowing updates to selected CoreConfig properties at runtime. @@ -138,9 +139,23 @@ export type CommonCoreConfig = { * Represents a set of configuration parameters that can be used to override or extend the * default configuration settings for a specific stream (main or secondary). * - * @example + * @example Configuration for basic video stream + * * ```typescript * const config: CoreConfig = { + * highDemandTimeWindow: 15, + * httpDownloadTimeWindow: 3000, + * p2pDownloadTimeWindow: 6000, + * swarmId: "custom swarm ID for video stream", + * cashedSegmentsCount: 1000, + * } + * ``` + * + * @example Configuration for advanced video stream + * + * ```typescript + * const config: CoreConfig = { + * // Configuration for both streams * highDemandTimeWindow: 20, * httpDownloadTimeWindow: 3000, * p2pDownloadTimeWindow: 6000, @@ -164,6 +179,16 @@ export type CoreConfig = Partial & /** Configuration options for the Core functionality, including network and processing parameters. */ export type StreamConfig = { + /** + * Indicates whether Peer-to-Peer (P2P) functionality is disabled for the stream. + * If set to true, P2P functionality is disabled for the stream. + * + * @default + * ```typescript + * isP2PDisabled: false + * ``` + */ + isP2PDisabled: boolean; /** * Defines the duration of the time window, in seconds, during which segments are pre-loaded to ensure smooth playback. * This window helps prioritize the fetching of media segments that are imminent to playback. diff --git a/packages/p2p-media-loader-hlsjs/src/fragment-loader.ts b/packages/p2p-media-loader-hlsjs/src/fragment-loader.ts index 7e31524a..a26762ed 100644 --- a/packages/p2p-media-loader-hlsjs/src/fragment-loader.ts +++ b/packages/p2p-media-loader-hlsjs/src/fragment-loader.ts @@ -56,9 +56,15 @@ export class FragmentLoaderBase implements Loader { start, end !== undefined ? end - 1 : undefined, ); + this.#segmentId = Utils.getSegmentLocalId(context.url, byteRange); + const isSegmentDownloadableByP2PCore = + this.#core.isSegmentLoadableByP2PCore(this.#segmentId); - if (!this.#core.hasSegment(this.#segmentId)) { + if ( + !this.#core.hasSegment(this.#segmentId) || + isSegmentDownloadableByP2PCore === false + ) { this.#defaultLoader = this.#createDefaultLoader(); this.#defaultLoader.stats = this.stats; this.#defaultLoader?.load(context, config, callbacks); diff --git a/packages/p2p-media-loader-shaka/src/loading-handler.ts b/packages/p2p-media-loader-shaka/src/loading-handler.ts index 38980514..962f3169 100644 --- a/packages/p2p-media-loader-shaka/src/loading-handler.ts +++ b/packages/p2p-media-loader-shaka/src/loading-handler.ts @@ -54,7 +54,13 @@ export class Loader { byteRangeString: string, ): LoadingHandlerResult { const segmentId = Utils.getSegmentLocalId(segmentUrl, byteRangeString); - if (!this.core.hasSegment(segmentId)) { + const isSegmentDownloadableByP2PCore = + this.core.isSegmentLoadableByP2PCore(segmentId); + + if ( + !this.core.hasSegment(segmentId) || + isSegmentDownloadableByP2PCore === false + ) { return this.defaultLoad() as LoadingHandlerResult; }