diff --git a/p2p-media-loader-core/.editorconfig b/p2p-media-loader-core/.editorconfig index 4b43aa8e..6c761897 100644 --- a/p2p-media-loader-core/.editorconfig +++ b/p2p-media-loader-core/.editorconfig @@ -7,6 +7,7 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +max_line_length = 120 [*.md] trim_trailing_whitespace = false [*.json] diff --git a/p2p-media-loader-core/.eslintrc b/p2p-media-loader-core/.eslintrc index b22e2242..0484f2e1 100644 --- a/p2p-media-loader-core/.eslintrc +++ b/p2p-media-loader-core/.eslintrc @@ -11,7 +11,8 @@ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking" + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "prettier" ], "env": { "browser": true diff --git a/p2p-media-loader-core/lib/bandwidth-approximator.ts b/p2p-media-loader-core/lib/bandwidth-approximator.ts index 1b44fb92..e0241375 100644 --- a/p2p-media-loader-core/lib/bandwidth-approximator.ts +++ b/p2p-media-loader-core/lib/bandwidth-approximator.ts @@ -37,7 +37,7 @@ export class BandwidthApproximator { const interval = Math.min(SMOOTH_INTERVAL, timeStamp); this.lastBandwidth.push(new NumberWithTime(this.currentBytesSum / interval, timeStamp)); - } + }; // in bytes per millisecond public getBandwidth = (timeStamp: number): number => { @@ -53,13 +53,13 @@ export class BandwidthApproximator { } return maxBandwidth; - } + }; public getSmoothInterval = (): number => { return SMOOTH_INTERVAL; - } + }; public getMeasureInterval = (): number => { return MEASURE_INTERVAL; - } + }; } diff --git a/p2p-media-loader-core/lib/http-media-manager.ts b/p2p-media-loader-core/lib/http-media-manager.ts index da53df16..21425136 100644 --- a/p2p-media-loader-core/lib/http-media-manager.ts +++ b/p2p-media-loader-core/lib/http-media-manager.ts @@ -20,21 +20,20 @@ import { STEEmitter } from "./stringly-typed-event-emitter"; import { Segment } from "./loader-interface"; import { SegmentValidatorCallback, XhrSetupCallback, SegmentUrlBuilder } from "./hybrid-loader"; -export class HttpMediaManager extends STEEmitter< - "segment-loaded" | "segment-error" | "bytes-downloaded" -> { - - private xhrRequests = new Map(); +export class HttpMediaManager extends STEEmitter<"segment-loaded" | "segment-error" | "bytes-downloaded"> { + private xhrRequests = new Map(); private failedSegments = new Map(); private debug = Debug("p2pml:http-media-manager"); - public constructor(readonly settings: { - httpFailedSegmentTimeout: number, - httpUseRanges: boolean, - segmentValidator?: SegmentValidatorCallback, - xhrSetup?: XhrSetupCallback - segmentUrlBuilder?: SegmentUrlBuilder - }) { + public constructor( + readonly settings: { + httpFailedSegmentTimeout: number; + httpUseRanges: boolean; + segmentValidator?: SegmentValidatorCallback; + xhrSetup?: XhrSetupCallback; + segmentUrlBuilder?: SegmentUrlBuilder; + } + ) { super(); } @@ -45,9 +44,7 @@ export class HttpMediaManager extends STEEmitter< this.cleanTimedOutFailedSegments(); - const segmentUrl = this.settings.segmentUrlBuilder - ? this.settings.segmentUrlBuilder(segment) - : segment.url; + const segmentUrl = this.settings.segmentUrlBuilder ? this.settings.segmentUrlBuilder(segment) : segment.url; this.debug("http segment download", segmentUrl); @@ -60,7 +57,7 @@ export class HttpMediaManager extends STEEmitter< if (segment.range) { xhr.setRequestHeader("Range", segment.range); downloadedPieces = undefined; // TODO: process downloadedPieces for segments with range headers too - } else if ((downloadedPieces !== undefined) && this.settings.httpUseRanges) { + } else if (downloadedPieces !== undefined && this.settings.httpUseRanges) { let bytesDownloaded = 0; for (const piece of downloadedPieces) { bytesDownloaded += piece.byteLength; @@ -79,9 +76,9 @@ export class HttpMediaManager extends STEEmitter< this.settings.xhrSetup(xhr, segmentUrl); } - this.xhrRequests.set(segment.id, {xhr, segment}); + this.xhrRequests.set(segment.id, { xhr, segment }); xhr.send(); - } + }; public abort = (segment: Segment): void => { const request = this.xhrRequests.get(segment.id); @@ -91,29 +88,29 @@ export class HttpMediaManager extends STEEmitter< this.xhrRequests.delete(segment.id); this.debug("http segment abort", segment.id); } - } + }; public isDownloading = (segment: Segment): boolean => { return this.xhrRequests.has(segment.id); - } + }; public isFailed = (segment: Segment): boolean => { const time = this.failedSegments.get(segment.id); return time !== undefined && time > this.now(); - } + }; - public getActiveDownloads = (): ReadonlyMap => { + public getActiveDownloads = (): ReadonlyMap => { return this.xhrRequests; - } + }; public getActiveDownloadsCount = (): number => { return this.xhrRequests.size; - } + }; public destroy = (): void => { - this.xhrRequests.forEach(request => request.xhr.abort()); + this.xhrRequests.forEach((request) => request.xhr.abort()); this.xhrRequests.clear(); - } + }; private setupXhrEvents = (xhr: XMLHttpRequest, segment: Segment, downloadedPieces?: ArrayBuffer[]) => { let prevBytesLoaded = 0; @@ -125,14 +122,14 @@ export class HttpMediaManager extends STEEmitter< }); xhr.addEventListener("load", async (event) => { - if ((xhr.status < 200) || (xhr.status >= 300)) { + if (xhr.status < 200 || xhr.status >= 300) { this.segmentFailure(segment, event, xhr); return; } let data = xhr.response as ArrayBuffer; - if ((downloadedPieces !== undefined) && (xhr.status === 206)) { + if (downloadedPieces !== undefined && xhr.status === 206) { let bytesDownloaded = 0; for (const piece of downloadedPieces) { bytesDownloaded += piece.byteLength; @@ -160,7 +157,7 @@ export class HttpMediaManager extends STEEmitter< xhr.addEventListener("timeout", (event: unknown) => { this.segmentFailure(segment, event, xhr); }); - } + }; private segmentDownloadFinished = async (segment: Segment, data: ArrayBuffer, xhr: XMLHttpRequest) => { segment.responseUrl = xhr.responseURL === null ? undefined : xhr.responseURL; @@ -177,7 +174,7 @@ export class HttpMediaManager extends STEEmitter< this.xhrRequests.delete(segment.id); this.emit("segment-loaded", segment, data); - } + }; private segmentFailure = (segment: Segment, error: unknown, xhr: XMLHttpRequest) => { segment.responseUrl = xhr.responseURL === null ? undefined : xhr.responseURL; @@ -185,7 +182,7 @@ export class HttpMediaManager extends STEEmitter< this.xhrRequests.delete(segment.id); this.failedSegments.set(segment.id, this.now() + this.settings.httpFailedSegmentTimeout); this.emit("segment-error", segment, error); - } + }; private cleanTimedOutFailedSegments = () => { const now = this.now(); @@ -197,9 +194,8 @@ export class HttpMediaManager extends STEEmitter< } }); - candidates.forEach(id => this.failedSegments.delete(id)); - } + candidates.forEach((id) => this.failedSegments.delete(id)); + }; private now = () => performance.now(); - } diff --git a/p2p-media-loader-core/lib/hybrid-loader.ts b/p2p-media-loader-core/lib/hybrid-loader.ts index 5979ee5d..d19d82a0 100644 --- a/p2p-media-loader-core/lib/hybrid-loader.ts +++ b/p2p-media-loader-core/lib/hybrid-loader.ts @@ -51,11 +51,10 @@ const defaultSettings: HybridLoaderSettings = { webRtcMaxMessageSize: 64 * 1024 - 1, trackerAnnounce: ["wss://tracker.novage.com.ua", "wss://tracker.openwebtorrent.com"], peerRequestsPerAnnounce: 10, - rtcConfig: (Peer as { config: RTCConfiguration }).config + rtcConfig: (Peer as { config: RTCConfiguration }).config, }; export class HybridLoader extends EventEmitter implements LoaderInterface { - private readonly debug = Debug("p2pml:hybrid-loader"); private readonly debugSegments = Debug("p2pml:hybrid-loader-segments"); private readonly httpManager: HttpMediaManager; @@ -69,8 +68,8 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { private masterSwarmId?: string; public static isSupported = (): boolean => { - return (window.RTCPeerConnection.prototype.createDataChannel !== undefined); - } + return window.RTCPeerConnection.prototype.createDataChannel !== undefined; + }; public constructor(settings: Partial = {}) { super(); @@ -89,9 +88,10 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { } } - this.segmentsStorage = (this.settings.segmentsStorage === undefined - ? new SegmentsMemoryStorage(this.settings) - : this.settings.segmentsStorage); + this.segmentsStorage = + this.settings.segmentsStorage === undefined + ? new SegmentsMemoryStorage(this.settings) + : this.settings.segmentsStorage; this.debug("loader settings", this.settings); @@ -113,8 +113,12 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); } }); - this.p2pManager.on("bytes-downloaded", (bytes: number, peerId: string) => this.onPieceBytesDownloaded("p2p", bytes, peerId)); - this.p2pManager.on("bytes-uploaded", (bytes: number, peerId: string) => this.onPieceBytesUploaded("p2p", bytes, peerId)); + this.p2pManager.on("bytes-downloaded", (bytes: number, peerId: string) => + this.onPieceBytesDownloaded("p2p", bytes, peerId) + ); + this.p2pManager.on("bytes-uploaded", (bytes: number, peerId: string) => + this.onPieceBytesUploaded("p2p", bytes, peerId) + ); this.p2pManager.on("peer-connected", this.onPeerConnect); this.p2pManager.on("peer-closed", this.onPeerClose); this.p2pManager.on("tracker-update", this.onTrackerUpdate); @@ -122,19 +126,31 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { private createHttpManager = () => { return new HttpMediaManager(this.settings); - } + }; private createP2PManager = () => { return new P2PMediaManager(this.segmentsStorage, this.settings); - } + }; public load = async (segments: Segment[], streamSwarmId: string): Promise => { - if (this.httpRandomDownloadInterval === undefined) { // Do once on first call - this.httpRandomDownloadInterval = setInterval(this.downloadRandomSegmentOverHttp, this.settings.httpDownloadProbabilityInterval); - - if (this.settings.httpDownloadInitialTimeout > 0 && this.settings.httpDownloadInitialTimeoutPerSegment > 0) { + if (this.httpRandomDownloadInterval === undefined) { + // Do once on first call + this.httpRandomDownloadInterval = setInterval( + this.downloadRandomSegmentOverHttp, + this.settings.httpDownloadProbabilityInterval + ); + + if ( + this.settings.httpDownloadInitialTimeout > 0 && + this.settings.httpDownloadInitialTimeoutPerSegment > 0 + ) { // Initialize initial HTTP download timeout (i.e. download initial segments over P2P) - this.debugSegments("enable initial HTTP download timeout", this.settings.httpDownloadInitialTimeout, "per segment", this.settings.httpDownloadInitialTimeoutPerSegment); + this.debugSegments( + "enable initial HTTP download timeout", + this.settings.httpDownloadInitialTimeout, + "per segment", + this.settings.httpDownloadInitialTimeoutPerSegment + ); this.httpDownloadInitialTimeoutTimestamp = this.now(); setTimeout(this.processInitialSegmentTimeout, this.settings.httpDownloadInitialTimeoutPerSegment + 100); } @@ -154,7 +170,7 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { // stop all http requests and p2p downloads for segments that are not in the new load for (const segment of this.segmentsQueue) { - if (!segments.find(f => f.url === segment.url)) { + if (!segments.find((f) => f.url === segment.url)) { this.debug("remove segment", segment.url); if (this.httpManager.isDownloading(segment)) { updateSegmentsMap = true; @@ -168,7 +184,7 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { if (this.debug.enabled) { for (const segment of segments) { - if (!this.segmentsQueue.find(f => f.url === segment.url)) { + if (!this.segmentsQueue.find((f) => f.url === segment.url)) { this.debug("add segment", segment.url); } } @@ -181,7 +197,7 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { } let storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); - updateSegmentsMap = (this.processSegmentsQueue(storageSegments) || updateSegmentsMap); + updateSegmentsMap = this.processSegmentsQueue(storageSegments) || updateSegmentsMap; if (await this.cleanSegmentsStorage()) { storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); @@ -191,23 +207,21 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { if (updateSegmentsMap && !this.settings.consumeOnly) { this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); } - } + }; public getSegment = async (id: string): Promise => { - return this.masterSwarmId === undefined - ? undefined - : this.segmentsStorage.getSegment(id, this.masterSwarmId); - } + return this.masterSwarmId === undefined ? undefined : this.segmentsStorage.getSegment(id, this.masterSwarmId); + }; public getSettings = (): HybridLoaderSettings => { return this.settings; - } + }; public getDetails = (): { peerId: string } => { return { - peerId: this.p2pManager.getPeerId() + peerId: this.p2pManager.getPeerId(), }; - } + }; public destroy = async (): Promise => { if (this.httpRandomDownloadInterval !== undefined) { @@ -222,7 +236,7 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { this.p2pManager.destroy(); this.masterSwarmId = undefined; await this.segmentsStorage.destroy(); - } + }; private processInitialSegmentTimeout = async () => { if (this.httpRandomDownloadInterval === undefined) { @@ -241,11 +255,13 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { // Set one more timeout for a next segment setTimeout(this.processInitialSegmentTimeout, this.settings.httpDownloadInitialTimeoutPerSegment); } - } + }; - private processSegmentsQueue = (storageSegments: Map) => { - this.debugSegments("process segments queue. priority", - this.segmentsQueue.length > 0 ? this.segmentsQueue[0].priority : 0); + private processSegmentsQueue = (storageSegments: Map) => { + this.debugSegments( + "process segments queue. priority", + this.segmentsQueue.length > 0 ? this.segmentsQueue[0].priority : 0 + ); if (this.masterSwarmId === undefined || this.segmentsQueue.length === 0) { return false; @@ -267,8 +283,11 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { } const httpTimeout = this.now() - this.httpDownloadInitialTimeoutTimestamp; - httpAllowed = (httpTimeout >= this.settings.httpDownloadInitialTimeout) - || ((firstNotDownloadePriority !== undefined) && (httpTimeout > this.settings.httpDownloadInitialTimeoutPerSegment) && (firstNotDownloadePriority <= 0)); + httpAllowed = + httpTimeout >= this.settings.httpDownloadInitialTimeout || + (firstNotDownloadePriority !== undefined && + httpTimeout > this.settings.httpDownloadInitialTimeoutPerSegment && + firstNotDownloadePriority <= 0); if (httpAllowed) { this.debugSegments("cancel initial HTTP download timeout - timed out"); @@ -283,7 +302,11 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { continue; } - if (segment.priority <= this.settings.requiredSegmentsPriority && httpAllowed && !this.httpManager.isFailed(segment)) { + if ( + segment.priority <= this.settings.requiredSegmentsPriority && + httpAllowed && + !this.httpManager.isFailed(segment) + ) { // Download required segments over HTTP if (this.httpManager.getActiveDownloadsCount() >= this.settings.simultaneousHttpDownloads) { // Not enough HTTP download resources. Abort one of the HTTP downloads. @@ -311,7 +334,8 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { continue; } - if (segment.priority <= this.settings.requiredSegmentsPriority) { // Download required segments over P2P + if (segment.priority <= this.settings.requiredSegmentsPriority) { + // Download required segments over P2P segmentsMap = segmentsMap ? segmentsMap : this.p2pManager.getOverallSegmentsMap(); if (segmentsMap.get(segment.id) !== MediaPeerSegmentStatus.Loaded) { @@ -340,8 +364,10 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { continue; } - if (this.p2pManager.getActiveDownloadsCount() < this.settings.simultaneousP2PDownloads && - segment.priority <= this.settings.p2pDownloadMaxPriority) { + if ( + this.p2pManager.getActiveDownloadsCount() < this.settings.simultaneousP2PDownloads && + segment.priority <= this.settings.p2pDownloadMaxPriority + ) { if (this.p2pManager.download(segment)) { this.debugSegments("P2P download", segment.priority, segment.url); } @@ -349,28 +375,32 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { } return updateSegmentsMap; - } + }; private downloadRandomSegmentOverHttp = async () => { - if (this.masterSwarmId === undefined || - this.httpRandomDownloadInterval === undefined || - this.httpDownloadInitialTimeoutTimestamp !== -Infinity || - this.httpManager.getActiveDownloadsCount() >= this.settings.simultaneousHttpDownloads || - (this.settings.httpDownloadProbabilitySkipIfNoPeers && this.p2pManager.getPeers().size === 0) || - this.settings.consumeOnly) { + if ( + this.masterSwarmId === undefined || + this.httpRandomDownloadInterval === undefined || + this.httpDownloadInitialTimeoutTimestamp !== -Infinity || + this.httpManager.getActiveDownloadsCount() >= this.settings.simultaneousHttpDownloads || + (this.settings.httpDownloadProbabilitySkipIfNoPeers && this.p2pManager.getPeers().size === 0) || + this.settings.consumeOnly + ) { return; } const storageSegments = await this.segmentsStorage.getSegmentsMap(this.masterSwarmId); const segmentsMap = this.p2pManager.getOverallSegmentsMap(); - const pendingQueue = this.segmentsQueue.filter(s => - !this.p2pManager.isDownloading(s) && - !this.httpManager.isDownloading(s) && - !segmentsMap.has(s.id) && - !this.httpManager.isFailed(s) && - (s.priority <= this.settings.httpDownloadMaxPriority) && - !storageSegments.has(s.id)); + const pendingQueue = this.segmentsQueue.filter( + (s) => + !this.p2pManager.isDownloading(s) && + !this.httpManager.isDownloading(s) && + !segmentsMap.has(s.id) && + !this.httpManager.isFailed(s) && + s.priority <= this.settings.httpDownloadMaxPriority && + !storageSegments.has(s.id) + ); if (pendingQueue.length === 0) { return; @@ -384,16 +414,16 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { this.debugSegments("HTTP download (random)", segment.priority, segment.url); this.httpManager.download(segment); this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); - } + }; private onPieceBytesDownloaded = (method: "http" | "p2p", bytes: number, peerId?: string) => { this.bandwidthApproximator.addBytes(bytes, this.now()); this.emit(Events.PieceBytesDownloaded, method, bytes, peerId); - } + }; private onPieceBytesUploaded = (method: "p2p", bytes: number, peerId?: string) => { this.emit(Events.PieceBytesUploaded, method, bytes, peerId); - } + }; private onSegmentLoaded = async (segment: Segment, data: ArrayBuffer, peerId?: string) => { this.debugSegments("segment loaded", segment.id, segment.url); @@ -414,7 +444,7 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { if (!this.settings.consumeOnly) { this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); } - } + }; private onSegmentError = async (segment: Segment, details: unknown, peerId?: string) => { this.debugSegments("segment error", segment.id, segment.url, peerId, details); @@ -425,14 +455,14 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments)); } } - } + }; private getStreamSwarmId = (segment: Segment) => { return segment.streamId === undefined ? segment.masterSwarmId : `${segment.masterSwarmId}+${segment.streamId}`; - } + }; - private createSegmentsMap = (storageSegments: Map) => { - const segmentsMap: {[key: string]: [string, number[]]} = {}; + private createSegmentsMap = (storageSegments: Map) => { + const segmentsMap: { [key: string]: [string, number[]] } = {}; const addSegmentToMap = (segment: Segment, status: MediaPeerSegmentStatus) => { const streamSwarmId = this.getStreamSwarmId(segment); @@ -444,7 +474,7 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { segmentsMap[streamSwarmId] = segmentsIdsAndStatuses; } const segmentsStatuses = segmentsIdsAndStatuses[1]; - segmentsIdsAndStatuses[0] += ((segmentsStatuses.length === 0) ? segmentId : `|${segmentId}`); + segmentsIdsAndStatuses[0] += segmentsStatuses.length === 0 ? segmentId : `|${segmentId}`; segmentsStatuses.push(status); }; @@ -457,23 +487,28 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { } return segmentsMap; - } + }; - private onPeerConnect = async (peer: {id: string}) => { + private onPeerConnect = async (peer: { id: string }) => { this.emit(Events.PeerConnect, peer); if (!this.settings.consumeOnly && this.masterSwarmId !== undefined) { - this.p2pManager.sendSegmentsMap(peer.id, this.createSegmentsMap(await this.segmentsStorage.getSegmentsMap(this.masterSwarmId))); + this.p2pManager.sendSegmentsMap( + peer.id, + this.createSegmentsMap(await this.segmentsStorage.getSegmentsMap(this.masterSwarmId)) + ); } - } + }; private onPeerClose = (peerId: string) => { this.emit(Events.PeerClose, peerId); - } + }; private onTrackerUpdate = async (data: { incomplete?: number }) => { - if (this.httpDownloadInitialTimeoutTimestamp !== -Infinity && - data.incomplete !== undefined && data.incomplete <= 1) { - + if ( + this.httpDownloadInitialTimeoutTimestamp !== -Infinity && + data.incomplete !== undefined && + data.incomplete <= 1 + ) { this.debugSegments("cancel initial HTTP download timeout - no peers"); this.httpDownloadInitialTimeoutTimestamp = -Infinity; @@ -486,26 +521,27 @@ export class HybridLoader extends EventEmitter implements LoaderInterface { } } } - } + }; private cleanSegmentsStorage = async (): Promise => { if (this.masterSwarmId === undefined) { return false; } - return this.segmentsStorage.clean(this.masterSwarmId, - (id: string) => this.segmentsQueue.find(queueSegment => queueSegment.id === id) !== undefined); - } + return this.segmentsStorage.clean( + this.masterSwarmId, + (id: string) => this.segmentsQueue.find((queueSegment) => queueSegment.id === id) !== undefined + ); + }; private now = () => { return performance.now(); - } - + }; } export interface SegmentsStorage { storeSegment: (segment: Segment) => Promise; - getSegmentsMap: (masterSwarmId: string) => Promise>; + getSegmentsMap: (masterSwarmId: string) => Promise>; getSegment: (id: string, masterSwarmId: string) => Promise; clean: (masterSwarmId: string, lockedSegmentsFilter?: (id: string) => boolean) => Promise; destroy: () => Promise; diff --git a/p2p-media-loader-core/lib/loader-interface.ts b/p2p-media-loader-core/lib/loader-interface.ts index 8f81baae..8ac0d34f 100644 --- a/p2p-media-loader-core/lib/loader-interface.ts +++ b/p2p-media-loader-core/lib/loader-interface.ts @@ -70,12 +70,11 @@ export enum Events { * Emitted when a segment piece has been uploaded. * Args: method (can be "p2p" only), bytes */ - PieceBytesUploaded = "piece_bytes_uploaded" + PieceBytesUploaded = "piece_bytes_uploaded", } export interface LoaderInterface { - on: - ((eventName: string, listener: (...params: unknown[]) => void) => this) & + on: ((eventName: string, listener: (...params: unknown[]) => void) => this) & ((eventName: Events.SegmentLoaded, listener: (segment: Segment) => void) => this) & ((eventName: Events.SegmentError, listener: (segment: Segment, error: unknown) => void) => this) & ((eventName: Events.SegmentAbort, listener: (segment: Segment) => void) => this); diff --git a/p2p-media-loader-core/lib/media-peer.ts b/p2p-media-loader-core/lib/media-peer.ts index 85917812..fa4fc016 100644 --- a/p2p-media-loader-core/lib/media-peer.ts +++ b/p2p-media-loader-core/lib/media-peer.ts @@ -28,24 +28,30 @@ enum MediaPeerCommands { SegmentAbsent, SegmentsMap, SegmentRequest, - CancelSegmentRequest + CancelSegmentRequest, } -type MediaPeerCommand = { - c: MediaPeerCommands.SegmentAbsent | MediaPeerCommands.SegmentRequest | MediaPeerCommands.CancelSegmentRequest; - i: string; -} | { - c: MediaPeerCommands.SegmentsMap; - m: {[key: string]: [string, number[]]}; -} | { - c: MediaPeerCommands.SegmentData; - i: string; - s: number; -}; +type MediaPeerCommand = + | { + c: + | MediaPeerCommands.SegmentAbsent + | MediaPeerCommands.SegmentRequest + | MediaPeerCommands.CancelSegmentRequest; + i: string; + } + | { + c: MediaPeerCommands.SegmentsMap; + m: { [key: string]: [string, number[]] }; + } + | { + c: MediaPeerCommands.SegmentData; + i: string; + s: number; + }; export enum MediaPeerSegmentStatus { Loaded, - LoadingByHttp + LoadingByHttp, } class DownloadingSegment { @@ -55,9 +61,16 @@ class DownloadingSegment { } export class MediaPeer extends STEEmitter< - "connect" | "close" | "data-updated" | - "segment-request" | "segment-absent" | "segment-loaded" | "segment-error" | "segment-timeout" | - "bytes-downloaded" | "bytes-uploaded" + | "connect" + | "close" + | "data-updated" + | "segment-request" + | "segment-absent" + | "segment-loaded" + | "segment-error" + | "segment-timeout" + | "bytes-downloaded" + | "bytes-uploaded" > { public id: string; public remoteAddress = ""; @@ -67,12 +80,14 @@ export class MediaPeer extends STEEmitter< private debug = Debug("p2pml:media-peer"); private timer: ReturnType | null = null; - // eslint-disable-next-line - constructor(readonly peer: any, - readonly settings: { - p2pSegmentDownloadTimeout: number, - webRtcMaxMessageSize: number - }) { + constructor( + // eslint-disable-next-line + readonly peer: any, + readonly settings: { + p2pSegmentDownloadTimeout: number; + webRtcMaxMessageSize: number; + } + ) { super(); this.peer.on("connect", this.onPeerConnect); @@ -87,17 +102,17 @@ export class MediaPeer extends STEEmitter< this.debug("peer connect", this.id, this); this.remoteAddress = this.peer.remoteAddress; this.emit("connect", this); - } + }; private onPeerClose = () => { this.debug("peer close", this.id, this); this.terminateSegmentRequest(); this.emit("close", this); - } + }; private onPeerError = (error: unknown) => { this.debug("peer error", this.id, error, this); - } + }; private receiveSegmentPiece = (data: ArrayBuffer): void => { if (!this.downloadingSegment) { @@ -128,7 +143,7 @@ export class MediaPeer extends STEEmitter< this.terminateSegmentRequest(); this.emit("segment-error", this, segmentId, "Too many bytes received for segment"); } - } + }; private getJsonCommand = (data: ArrayBuffer) => { const bytes = new Uint8Array(data); @@ -143,7 +158,7 @@ export class MediaPeer extends STEEmitter< } return null; - } + }; private onPeerData = (data: ArrayBuffer) => { const command = this.getJsonCommand(data); @@ -175,10 +190,12 @@ export class MediaPeer extends STEEmitter< break; case MediaPeerCommands.SegmentData: - if (this.downloadingSegmentId - && this.downloadingSegmentId === command.i - && typeof command.s === "number" - && command.s >= 0) { + if ( + this.downloadingSegmentId && + this.downloadingSegmentId === command.i && + typeof command.s === "number" && + command.s >= 0 + ) { this.downloadingSegment = new DownloadingSegment(command.i, command.s); this.cancelResponseTimeoutTimer(); } @@ -199,7 +216,7 @@ export class MediaPeer extends STEEmitter< default: break; } - } + }; private createSegmentsMap = (segments: unknown) => { if (!(segments instanceof Object)) { @@ -210,10 +227,12 @@ export class MediaPeer extends STEEmitter< for (const streamSwarmId of Object.keys(segments)) { const swarmData = (segments as Record)[streamSwarmId]; - if (!(swarmData instanceof Array) || - (swarmData.length !== 2) || - (typeof swarmData[0] !== "string") || - !(swarmData[1] instanceof Array)) { + if ( + !(swarmData instanceof Array) || + swarmData.length !== 2 || + typeof swarmData[0] !== "string" || + !(swarmData[1] instanceof Array) + ) { return new Map(); } @@ -235,41 +254,42 @@ export class MediaPeer extends STEEmitter< } return segmentsMap; - } + }; private sendCommand = (command: MediaPeerCommand): void => { this.debug("peer send command", this.id, command, this); this.peer.write(JSON.stringify(command)); - } + }; public destroy = (): void => { this.debug("peer destroy", this.id, this); this.terminateSegmentRequest(); this.peer.destroy(); - } + }; public getDownloadingSegmentId = (): string | null => { return this.downloadingSegmentId; - } + }; public getSegmentsMap = (): Map => { return this.segmentsMap; - } + }; - public sendSegmentsMap = (segmentsMap: {[key: string]: [string, number[]]}): void => { - this.sendCommand({c: MediaPeerCommands.SegmentsMap, m: segmentsMap}); - } + public sendSegmentsMap = (segmentsMap: { [key: string]: [string, number[]] }): void => { + this.sendCommand({ c: MediaPeerCommands.SegmentsMap, m: segmentsMap }); + }; public sendSegmentData = (segmentId: string, data: ArrayBuffer): void => { this.sendCommand({ c: MediaPeerCommands.SegmentData, i: segmentId, - s: data.byteLength + s: data.byteLength, }); let bytesLeft = data.byteLength; while (bytesLeft > 0) { - const bytesToSend = (bytesLeft >= this.settings.webRtcMaxMessageSize ? this.settings.webRtcMaxMessageSize : bytesLeft); + const bytesToSend = + bytesLeft >= this.settings.webRtcMaxMessageSize ? this.settings.webRtcMaxMessageSize : bytesLeft; const buffer = Buffer.from(data, data.byteLength - bytesLeft, bytesToSend); this.peer.write(buffer); @@ -277,21 +297,21 @@ export class MediaPeer extends STEEmitter< } this.emit("bytes-uploaded", this, data.byteLength); - } + }; public sendSegmentAbsent = (segmentId: string): void => { - this.sendCommand({c: MediaPeerCommands.SegmentAbsent, i: segmentId}); - } + this.sendCommand({ c: MediaPeerCommands.SegmentAbsent, i: segmentId }); + }; public requestSegment = (segmentId: string): void => { if (this.downloadingSegmentId) { throw new Error("A segment is already downloading: " + this.downloadingSegmentId); } - this.sendCommand({c: MediaPeerCommands.SegmentRequest, i: segmentId}); + this.sendCommand({ c: MediaPeerCommands.SegmentRequest, i: segmentId }); this.downloadingSegmentId = segmentId; this.runResponseTimeoutTimer(); - } + }; public cancelSegmentRequest = (): ArrayBuffer[] | undefined => { let downloadingSegment: ArrayBuffer[] | undefined; @@ -300,11 +320,11 @@ export class MediaPeer extends STEEmitter< const segmentId = this.downloadingSegmentId; downloadingSegment = this.downloadingSegment ? this.downloadingSegment.pieces : undefined; this.terminateSegmentRequest(); - this.sendCommand({c: MediaPeerCommands.CancelSegmentRequest, i: segmentId}); + this.sendCommand({ c: MediaPeerCommands.CancelSegmentRequest, i: segmentId }); } return downloadingSegment; - } + }; private runResponseTimeoutTimer = (): void => { this.timer = setTimeout(() => { @@ -316,18 +336,18 @@ export class MediaPeer extends STEEmitter< this.cancelSegmentRequest(); this.emit("segment-timeout", this, segmentId); // TODO: send peer not responding event }, this.settings.p2pSegmentDownloadTimeout); - } + }; private cancelResponseTimeoutTimer = (): void => { if (this.timer) { clearTimeout(this.timer); this.timer = null; } - } + }; private terminateSegmentRequest = () => { this.downloadingSegmentId = null; this.downloadingSegment = null; this.cancelResponseTimeoutTimer(); - } + }; } diff --git a/p2p-media-loader-core/lib/p2p-media-manager.ts b/p2p-media-loader-core/lib/p2p-media-manager.ts index 3d9c7cab..47584efb 100644 --- a/p2p-media-loader-core/lib/p2p-media-manager.ts +++ b/p2p-media-loader-core/lib/p2p-media-manager.ts @@ -30,14 +30,11 @@ import { version } from "./index"; import { SegmentsStorage, SegmentValidatorCallback } from "./hybrid-loader"; const PEER_PROTOCOL_VERSION = 2; -const PEER_ID_VERSION_STRING = version.replace(/\d*./g, v => `0${parseInt(v, 10) % 100}`.slice(-2)).slice(0, 4); +const PEER_ID_VERSION_STRING = version.replace(/\d*./g, (v) => `0${parseInt(v, 10) % 100}`.slice(-2)).slice(0, 4); const PEER_ID_VERSION_PREFIX = `-WW${PEER_ID_VERSION_STRING}-`; // Using WebTorrent client ID in order to not be banned by websocket trackers class PeerSegmentRequest { - constructor( - readonly peerId: string, - readonly segment: Segment - ) {} + constructor(readonly peerId: string, readonly segment: Segment) {} } function generatePeerId(): ArrayBuffer { @@ -54,9 +51,14 @@ function generatePeerId(): ArrayBuffer { } export class P2PMediaManager extends STEEmitter< - "peer-connected" | "peer-closed" | "peer-data-updated" | - "segment-loaded" | "segment-error" | "bytes-downloaded" | - "bytes-uploaded" | "tracker-update" + | "peer-connected" + | "peer-closed" + | "peer-data-updated" + | "segment-loaded" + | "segment-error" + | "bytes-downloaded" + | "bytes-uploaded" + | "tracker-update" > { // eslint-disable-next-line @typescript-eslint/no-explicit-any private trackerClient: any = null; @@ -67,21 +69,22 @@ export class P2PMediaManager extends STEEmitter< private readonly peerId: ArrayBuffer; private debug = Debug("p2pml:p2p-media-manager"); private pendingTrackerClient: { - isDestroyed: boolean + isDestroyed: boolean; } | null = null; private masterSwarmId?: string; public constructor( - private segmentsStorage: SegmentsStorage, - private settings: { - useP2P: boolean, - trackerAnnounce: string[], - p2pSegmentDownloadTimeout: number, - segmentValidator?: SegmentValidatorCallback, - webRtcMaxMessageSize: number, - rtcConfig?: RTCConfiguration, - peerRequestsPerAnnounce: number - }) { + private segmentsStorage: SegmentsStorage, + private settings: { + useP2P: boolean; + trackerAnnounce: string[]; + p2pSegmentDownloadTimeout: number; + segmentValidator?: SegmentValidatorCallback; + webRtcMaxMessageSize: number; + rtcConfig?: RTCConfiguration; + peerRequestsPerAnnounce: number; + } + ) { super(); this.peerId = settings.useP2P ? generatePeerId() : new ArrayBuffer(0); @@ -93,11 +96,11 @@ export class P2PMediaManager extends STEEmitter< public getPeers = (): Map => { return this.peers; - } + }; public getPeerId = (): string => { return Buffer.from(this.peerId).toString("hex"); - } + }; public setStreamSwarmId = (streamSwarmId: string, masterSwarmId: string): void => { if (this.streamSwarmId === streamSwarmId) { @@ -111,7 +114,7 @@ export class P2PMediaManager extends STEEmitter< this.debug("stream swarm ID", this.streamSwarmId); this.pendingTrackerClient = { - isDestroyed: false + isDestroyed: false, }; const pendingTrackerClient = this.pendingTrackerClient; @@ -130,7 +133,7 @@ export class P2PMediaManager extends STEEmitter< this.trackerClient.destroy(); this.trackerClient = null; } - } + }; private createClient = (infoHash: ArrayBuffer): void => { if (!this.settings.useP2P) { @@ -145,7 +148,7 @@ export class P2PMediaManager extends STEEmitter< port: 6881, // a dummy value allows running in Node.js environment getAnnounceOpts: () => { return { numwant: this.settings.peerRequestsPerAnnounce }; - } + }, }; let oldTrackerClient = this.trackerClient; @@ -162,20 +165,20 @@ export class P2PMediaManager extends STEEmitter< oldTrackerClient.destroy(); oldTrackerClient = null; } - } + }; private onTrackerError = (error: unknown) => { this.debug("tracker error", error); - } + }; private onTrackerWarning = (warning: unknown) => { this.debug("tracker warning", warning); - } + }; private onTrackerUpdate = (data: unknown): void => { this.debug("tracker update", data); this.emit("tracker-update", data); - } + }; // eslint-disable-next-line @typescript-eslint/no-explicit-any private onTrackerPeer = (trackerPeer: any): void => { @@ -208,7 +211,7 @@ export class P2PMediaManager extends STEEmitter< } peerCandidatesById.push(peer); - } + }; public download = (segment: Segment): boolean => { if (this.isDownloading(segment)) { @@ -218,8 +221,10 @@ export class P2PMediaManager extends STEEmitter< const candidates: MediaPeer[] = []; for (const peer of this.peers.values()) { - if ((peer.getDownloadingSegmentId() === null) && - (peer.getSegmentsMap().get(segment.id) === MediaPeerSegmentStatus.Loaded)) { + if ( + peer.getDownloadingSegmentId() === null && + peer.getSegmentsMap().get(segment.id) === MediaPeerSegmentStatus.Loaded + ) { candidates.push(peer); } } @@ -232,7 +237,7 @@ export class P2PMediaManager extends STEEmitter< peer.requestSegment(segment.id); this.peerSegmentRequests.set(segment.id, new PeerSegmentRequest(peer.id, segment)); return true; - } + }; public abort = (segment: Segment): ArrayBuffer[] | undefined => { let downloadingSegment: ArrayBuffer[] | undefined; @@ -245,15 +250,15 @@ export class P2PMediaManager extends STEEmitter< this.peerSegmentRequests.delete(segment.id); } return downloadingSegment; - } + }; public isDownloading = (segment: Segment): boolean => { return this.peerSegmentRequests.has(segment.id); - } + }; public getActiveDownloadsCount = (): number => { return this.peerSegmentRequests.size; - } + }; public destroy = (swarmChange = false): void => { this.streamSwarmId = null; @@ -277,7 +282,7 @@ export class P2PMediaManager extends STEEmitter< this.pendingTrackerClient = null; } - this.peers.forEach(peer => peer.destroy()); + this.peers.forEach((peer) => peer.destroy()); this.peers.clear(); this.peerSegmentRequests.clear(); @@ -288,18 +293,18 @@ export class P2PMediaManager extends STEEmitter< } } this.peerCandidates.clear(); - } + }; - public sendSegmentsMapToAll = (segmentsMap: {[key: string]: [string, number[]]}): void => { - this.peers.forEach(peer => peer.sendSegmentsMap(segmentsMap)); - } + public sendSegmentsMapToAll = (segmentsMap: { [key: string]: [string, number[]] }): void => { + this.peers.forEach((peer) => peer.sendSegmentsMap(segmentsMap)); + }; - public sendSegmentsMap = (peerId: string, segmentsMap: {[key: string]: [string, number[]]}): void => { + public sendSegmentsMap = (peerId: string, segmentsMap: { [key: string]: [string, number[]] }): void => { const peer = this.peers.get(peerId); if (peer) { peer.sendSegmentsMap(segmentsMap); } - } + }; public getOverallSegmentsMap = (): Map => { const overallSegmentsMap = new Map(); @@ -315,15 +320,15 @@ export class P2PMediaManager extends STEEmitter< } return overallSegmentsMap; - } + }; private onPieceBytesDownloaded = (peer: MediaPeer, bytes: number) => { this.emit("bytes-downloaded", bytes, peer.id); - } + }; private onPieceBytesUploaded = (peer: MediaPeer, bytes: number) => { this.emit("bytes-uploaded", bytes, peer.id); - } + }; private onPeerConnect = (peer: MediaPeer) => { const connectedPeer = this.peers.get(peer.id); @@ -349,8 +354,8 @@ export class P2PMediaManager extends STEEmitter< this.peerCandidates.delete(peer.id); } - this.emit("peer-connected", {id: peer.id, remoteAddress: peer.remoteAddress}); - } + this.emit("peer-connected", { id: peer.id, remoteAddress: peer.remoteAddress }); + }; private onPeerClose = (peer: MediaPeer) => { if (this.peers.get(peer.id) !== peer) { @@ -382,11 +387,11 @@ export class P2PMediaManager extends STEEmitter< this.peers.delete(peer.id); this.emit("peer-data-updated"); this.emit("peer-closed", peer.id); - } + }; private onPeerDataUpdated = () => { this.emit("peer-data-updated"); - } + }; private onSegmentRequest = async (peer: MediaPeer, segmentId: string) => { if (this.masterSwarmId === undefined) { @@ -399,7 +404,7 @@ export class P2PMediaManager extends STEEmitter< } else { peer.sendSegmentAbsent(segmentId); } - } + }; private onSegmentLoaded = async (peer: MediaPeer, segmentId: string, data: ArrayBuffer) => { const peerSegmentRequest = this.peerSegmentRequests.get(segmentId); @@ -423,12 +428,12 @@ export class P2PMediaManager extends STEEmitter< this.peerSegmentRequests.delete(segmentId); this.emit("segment-loaded", segment, data, peer.id); - } + }; private onSegmentAbsent = (peer: MediaPeer, segmentId: string) => { this.peerSegmentRequests.delete(segmentId); this.emit("peer-data-updated"); - } + }; private onSegmentError = (peer: MediaPeer, segmentId: string, description: string) => { const peerSegmentRequest = this.peerSegmentRequests.get(segmentId); @@ -436,7 +441,7 @@ export class P2PMediaManager extends STEEmitter< this.peerSegmentRequests.delete(segmentId); this.emit("segment-error", peerSegmentRequest.segment, description, peer.id); } - } + }; private onSegmentTimeout = (peer: MediaPeer, segmentId: string) => { const peerSegmentRequest = this.peerSegmentRequests.get(segmentId); @@ -447,5 +452,5 @@ export class P2PMediaManager extends STEEmitter< this.emit("peer-data-updated"); } } - } + }; } diff --git a/p2p-media-loader-core/lib/segments-memory-storage.ts b/p2p-media-loader-core/lib/segments-memory-storage.ts index ef2dbfcf..582f4bb7 100644 --- a/p2p-media-loader-core/lib/segments-memory-storage.ts +++ b/p2p-media-loader-core/lib/segments-memory-storage.ts @@ -18,20 +18,22 @@ import { Segment } from "./loader-interface"; import { SegmentsStorage } from "./hybrid-loader"; export class SegmentsMemoryStorage implements SegmentsStorage { - private cache = new Map(); + private cache = new Map(); - constructor(private settings: { - cachedSegmentExpiration: number, - cachedSegmentsCount: number - }) { } + constructor( + private settings: { + cachedSegmentExpiration: number; + cachedSegmentsCount: number; + } + ) {} public storeSegment = async (segment: Segment): Promise => { - this.cache.set(segment.id, {segment, lastAccessed: performance.now()}); - } + this.cache.set(segment.id, { segment, lastAccessed: performance.now() }); + }; - public getSegmentsMap = async (): Promise> => { + public getSegmentsMap = async (): Promise> => { return this.cache; - } + }; public getSegment = async (id: string): Promise => { const cacheItem = this.cache.get(id); @@ -42,15 +44,15 @@ export class SegmentsMemoryStorage implements SegmentsStorage { cacheItem.lastAccessed = performance.now(); return cacheItem.segment; - } + }; public hasSegment = async (id: string): Promise => { return this.cache.has(id); - } + }; public clean = async (masterSwarmId: string, lockedSegmentsFilter?: (id: string) => boolean): Promise => { const segmentsToDelete: string[] = []; - const remainingSegments: {segment: Segment, lastAccessed: number}[] = []; + const remainingSegments: { segment: Segment; lastAccessed: number }[] = []; // Delete old segments const now = performance.now(); @@ -69,7 +71,7 @@ export class SegmentsMemoryStorage implements SegmentsStorage { remainingSegments.sort((a, b) => a.lastAccessed - b.lastAccessed); for (const cachedSegment of remainingSegments) { - if ((lockedSegmentsFilter === undefined) || !lockedSegmentsFilter(cachedSegment.segment.id)) { + if (lockedSegmentsFilter === undefined || !lockedSegmentsFilter(cachedSegment.segment.id)) { segmentsToDelete.push(cachedSegment.segment.id); countOverhead--; if (countOverhead === 0) { @@ -79,11 +81,11 @@ export class SegmentsMemoryStorage implements SegmentsStorage { } } - segmentsToDelete.forEach(id => this.cache.delete(id)); + segmentsToDelete.forEach((id) => this.cache.delete(id)); return segmentsToDelete.length > 0; - } + }; public destroy = async (): Promise => { this.cache.clear(); - } + }; } diff --git a/p2p-media-loader-core/lib/stringly-typed-event-emitter.ts b/p2p-media-loader-core/lib/stringly-typed-event-emitter.ts index e30b432b..bb9d907a 100644 --- a/p2p-media-loader-core/lib/stringly-typed-event-emitter.ts +++ b/p2p-media-loader-core/lib/stringly-typed-event-emitter.ts @@ -18,7 +18,7 @@ import { EventEmitter } from "events"; -export class STEEmitter extends EventEmitter { +export class STEEmitter extends EventEmitter { public on = (event: T, listener: (...args: any[]) => void): this => super.on(event, listener); public emit = (event: T, ...args: any[]): boolean => super.emit(event, ...args); } diff --git a/p2p-media-loader-core/package-lock.json b/p2p-media-loader-core/package-lock.json index 00591ce7..f21dfec9 100644 --- a/p2p-media-loader-core/package-lock.json +++ b/p2p-media-loader-core/package-lock.json @@ -146,9 +146,9 @@ "dev": true }, "@types/eslint": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.7.tgz", - "integrity": "sha512-EHXbc1z2GoQRqHaAT7+grxlTJ3WE2YNeD6jlpPoRc83cCoThRY+NUWjCUZaYmk51OICkPXn2hhphcWcWXgNW0Q==", + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.8.tgz", + "integrity": "sha512-RTKvBsfz0T8CKOGZMfuluDNyMFHnu5lvNr4hWEsQeHXH6FcmIDIozOyWMh36nLGMwVd5UFNXC2xztA8lln22MQ==", "dev": true, "requires": { "@types/estree": "*", @@ -1030,9 +1030,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001204", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001204.tgz", - "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==", + "version": "1.0.30001205", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz", + "integrity": "sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og==", "dev": true }, "chalk": { @@ -1518,9 +1518,9 @@ } }, "electron-to-chromium": { - "version": "1.3.703", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.703.tgz", - "integrity": "sha512-SVBVhNB+4zPL+rvtWLw7PZQkw/Eqj1HQZs22xtcqW36+xoifzEOEEDEpkxSMfB6RFeSIOcG00w6z5mSqLr1Y6w==", + "version": "1.3.706", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.706.tgz", + "integrity": "sha512-IcXgNXeW6+ObrvHnQtjhokjdOPI/DQ5j0f9M6gUy82kc9GNTMxq/mTkxWlPBSpqO1mAomR1uPDsssKDMj1V4Cw==", "dev": true }, "elliptic": { @@ -1704,6 +1704,12 @@ } } }, + "eslint-config-prettier": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", + "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", + "dev": true + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -2725,18 +2731,18 @@ } }, "mime-db": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", - "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", "dev": true }, "mime-types": { - "version": "2.1.29", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", - "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", + "version": "2.1.30", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", + "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", "dev": true, "requires": { - "mime-db": "1.46.0" + "mime-db": "1.47.0" } }, "mimic-fn": { @@ -3255,6 +3261,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -3920,9 +3932,9 @@ }, "dependencies": { "ajv": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.1.tgz", - "integrity": "sha512-46ZA4TalFcLLqX1dEU3dhdY38wAtDydJ4e7QQTVekLUTzXkb1LfqU6VOBXC/a9wiv4T094WURqJH6ZitF92Kqw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.5.tgz", + "integrity": "sha512-RkiLa/AeJx7+9OvniQ/qeWu0w74A8DiPPBclQ6ji3ZQkv5KamO+QGpqmi7O4JIw3rHGUXZ6CoP9tsAkn3gyazg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -4278,9 +4290,9 @@ } }, "webpack": { - "version": "5.28.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.28.0.tgz", - "integrity": "sha512-1xllYVmA4dIvRjHzwELgW4KjIU1fW4PEuEnjsylz7k7H5HgPOctIq7W1jrt3sKH9yG5d72//XWzsHhfoWvsQVg==", + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.30.0.tgz", + "integrity": "sha512-Zr9NIri5yzpfmaMea2lSMV1UygbW0zQsSlGLMgKUm63ACXg6alhd1u4v5UBSBjzYKXJN6BNMGVM7w165e7NxYA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", diff --git a/p2p-media-loader-core/package.json b/p2p-media-loader-core/package.json index 474c6aad..1bd37209 100644 --- a/p2p-media-loader-core/package.json +++ b/p2p-media-loader-core/package.json @@ -55,8 +55,10 @@ "browserify": "^17.0.0", "copyfiles": "^2.4.1", "eslint": "^7.23.0", + "eslint-config-prettier": "^8.1.0", "mkdirp": "^1.0.4", "mocha": "^8.3.2", + "prettier": "^2.2.1", "terser": "^5.6.1", "ts-loader": "^8.1.0", "ts-mockito": "^2.6.1", diff --git a/p2p-media-loader-demo/.editorconfig b/p2p-media-loader-demo/.editorconfig index 4b43aa8e..6c761897 100644 --- a/p2p-media-loader-demo/.editorconfig +++ b/p2p-media-loader-demo/.editorconfig @@ -7,6 +7,7 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +max_line_length = 120 [*.md] trim_trailing_whitespace = false [*.json] diff --git a/p2p-media-loader-demo/index.html b/p2p-media-loader-demo/index.html index 8715e12a..926bd8dd 100644 --- a/p2p-media-loader-demo/index.html +++ b/p2p-media-loader-demo/index.html @@ -16,1398 +16,1466 @@ limitations under the License. --> - - - - - - - P2P Media Loader - + + + + + + + P2P Media Loader + + + + + + + + + + + + + - - - -
-
-

P2P Media Loader Demo

- Git: https://github.com/novage/p2p-media-loader -
-
- -
-
- WebRTC Data Channels API is not supported by your browser. P2P disabled.
- Read more at Is WebRTC ready yet?. -
-
-
- Your browser doesn't support hls.js engine. P2P disabled.
- Read more at Media Source Extensions. -
-
-
- Your browser doesn't support Shaka Player engine. P2P disabled.
- Read more at Media Source Extensions. -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - -
-
- - -
- - -
-
-

Trackers:

-
-
-
-
- - -
- + window.demo = new DemoApp(); + window.demo.init(); + + + diff --git a/p2p-media-loader-demo/package-lock.json b/p2p-media-loader-demo/package-lock.json index 64276760..65c8409e 100644 --- a/p2p-media-loader-demo/package-lock.json +++ b/p2p-media-loader-demo/package-lock.json @@ -951,6 +951,12 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, + "eslint-config-prettier": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", + "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", + "dev": true + }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -1663,6 +1669,12 @@ "sha.js": "^2.4.8" } }, + "prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", diff --git a/p2p-media-loader-demo/package.json b/p2p-media-loader-demo/package.json index 15df52c5..14dec1b0 100644 --- a/p2p-media-loader-demo/package.json +++ b/p2p-media-loader-demo/package.json @@ -39,6 +39,8 @@ "browserify": "^17.0.0", "browserify-versionify": "^1.0.6", "copyfiles": "^2.4.1", + "eslint-config-prettier": "^8.1.0", + "prettier": "^2.2.1", "terser": "^5.6.1" } } diff --git a/p2p-media-loader-hlsjs/.editorconfig b/p2p-media-loader-hlsjs/.editorconfig index 4b43aa8e..6c761897 100644 --- a/p2p-media-loader-hlsjs/.editorconfig +++ b/p2p-media-loader-hlsjs/.editorconfig @@ -7,6 +7,7 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +max_line_length = 120 [*.md] trim_trailing_whitespace = false [*.json] diff --git a/p2p-media-loader-hlsjs/.eslintrc b/p2p-media-loader-hlsjs/.eslintrc index b22e2242..0484f2e1 100644 --- a/p2p-media-loader-hlsjs/.eslintrc +++ b/p2p-media-loader-hlsjs/.eslintrc @@ -11,7 +11,8 @@ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking" + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "prettier" ], "env": { "browser": true diff --git a/p2p-media-loader-hlsjs/demo/clappr.html b/p2p-media-loader-hlsjs/demo/clappr.html index 8d3c37dd..846e18c8 100644 --- a/p2p-media-loader-hlsjs/demo/clappr.html +++ b/p2p-media-loader-hlsjs/demo/clappr.html @@ -15,56 +15,50 @@ limitations under the License. --> - - - - - Clappr player with hls.js engine and P2P demo - - - - - - - - - - - -
- - - - + + + + Clappr player with hls.js engine and P2P demo + + + + + + + + + +
+ + + diff --git a/p2p-media-loader-hlsjs/demo/dplayer.html b/p2p-media-loader-hlsjs/demo/dplayer.html index 0024b86a..9f3db164 100644 --- a/p2p-media-loader-hlsjs/demo/dplayer.html +++ b/p2p-media-loader-hlsjs/demo/dplayer.html @@ -15,61 +15,57 @@ limitations under the License. --> - + + - + DPlayer with hls.js engine and P2P demo - DPlayer with hls.js engine and P2P demo + + - - + + + - - - + + + +
- + - - + hls.loadSource(video.src); + hls.attachMedia(video); + }, + }, + }, + }); + } else { + document.write("Not supported :("); + } + + diff --git a/p2p-media-loader-hlsjs/demo/flowplayer.html b/p2p-media-loader-hlsjs/demo/flowplayer.html index 30df745c..d2b1cbfb 100644 --- a/p2p-media-loader-hlsjs/demo/flowplayer.html +++ b/p2p-media-loader-hlsjs/demo/flowplayer.html @@ -15,61 +15,58 @@ limitations under the License. --> - - - - - Flowplayer with hls.js engine and P2P demo - - - - - - - - - - - - - - -
- - - - + + + + Flowplayer with hls.js engine and P2P demo + + + + + + + + + + + + +
+ + + diff --git a/p2p-media-loader-hlsjs/demo/hlsjs.html b/p2p-media-loader-hlsjs/demo/hlsjs.html index 7a64d3d3..e8984884 100644 --- a/p2p-media-loader-hlsjs/demo/hlsjs.html +++ b/p2p-media-loader-hlsjs/demo/hlsjs.html @@ -15,52 +15,46 @@ limitations under the License. --> - - - - - Hls.js player with P2P demo - - - - - - - - - - - - - - - - + + + + Hls.js player with P2P demo + + + + + + + + + + + + + diff --git a/p2p-media-loader-hlsjs/demo/jwplayer.html b/p2p-media-loader-hlsjs/demo/jwplayer.html index 02be0f82..217aa144 100644 --- a/p2p-media-loader-hlsjs/demo/jwplayer.html +++ b/p2p-media-loader-hlsjs/demo/jwplayer.html @@ -15,56 +15,51 @@ limitations under the License. --> - - - - - JWPlayer with hls.js engine and P2P demo - - - - - - - - - - - - -
-
-
- - - - + + + + JWPlayer with hls.js engine and P2P demo + + + + + + + + + + + +
+
+
+ + + diff --git a/p2p-media-loader-hlsjs/demo/mediaelementjs.html b/p2p-media-loader-hlsjs/demo/mediaelementjs.html index 3b5741fd..a19f7639 100644 --- a/p2p-media-loader-hlsjs/demo/mediaelementjs.html +++ b/p2p-media-loader-hlsjs/demo/mediaelementjs.html @@ -15,64 +15,61 @@ limitations under the License. --> - - - - - MediaElement player with hls.js engine and P2P demo - - - - - - - - - - - - -
- -
- - - - + + + + MediaElement player with hls.js engine and P2P demo + + + + + + + + + + +
+ +
+ + + diff --git a/p2p-media-loader-hlsjs/demo/offlineplayback.html b/p2p-media-loader-hlsjs/demo/offlineplayback.html index 3f1f9119..015a6821 100644 --- a/p2p-media-loader-hlsjs/demo/offlineplayback.html +++ b/p2p-media-loader-hlsjs/demo/offlineplayback.html @@ -15,191 +15,200 @@ limitations under the License. --> - + + - + Offline playback with hls.js engine and P2P demo - Offline playback with hls.js engine and P2P demo + + - - + - - - - - - - - - - - + (async () => { + const db = await initIndexedDb(); + const assetsStorage = new IdbAssetsStorage(db); + const segmentsStorage = new IdbSegmentsStorage(db); + + if (Hls.isSupported() && p2pml.hlsjs.Engine.isSupported()) { + var engine = new p2pml.hlsjs.Engine({ + segments: { + assetsStorage: assetsStorage, + }, + loader: { + segmentsStorage: segmentsStorage, + }, + }); + + var hls = new Hls({ + liveSyncDurationCount: 7, // To have at least 7 segments in queue + loader: engine.createLoaderClass(), + }); + + p2pml.hlsjs.initHlsJsPlayer(hls); + + hls.on(Hls.Events.MANIFEST_PARSED, () => { + // disable ABR + hls.autoLevelEnabled = false; + hls.loadLevel = 0; + }); + + hls.on(Hls.Events.MEDIA_ATTACHED, () => { + hls.loadSource("https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8"); + }); + + hls.attachMedia(document.getElementById("video")); + } else { + document.write("Not supported :("); + } + })(); + + diff --git a/p2p-media-loader-hlsjs/demo/plyr.html b/p2p-media-loader-hlsjs/demo/plyr.html index eaa952fa..f6fc73e0 100644 --- a/p2p-media-loader-hlsjs/demo/plyr.html +++ b/p2p-media-loader-hlsjs/demo/plyr.html @@ -15,58 +15,52 @@ limitations under the License. --> - - - - - Plyr player with hls.js engine and P2P demo - - - - - - - - - - - - - -
- -
- - - - + + + + Plyr player with hls.js engine and P2P demo + + + + + + + + + + + +
+ +
+ + + diff --git a/p2p-media-loader-hlsjs/demo/videojs-contrib-hlsjs.html b/p2p-media-loader-hlsjs/demo/videojs-contrib-hlsjs.html index a23e255d..2e241062 100644 --- a/p2p-media-loader-hlsjs/demo/videojs-contrib-hlsjs.html +++ b/p2p-media-loader-hlsjs/demo/videojs-contrib-hlsjs.html @@ -15,63 +15,57 @@ limitations under the License. --> - - - - - Video.js player with hls.js engine and P2P demo - - - - - - - - - - - - - - - - - - - + + + + Video.js player with hls.js engine and P2P demo + + + + + + + + + + + + + + + + diff --git a/p2p-media-loader-hlsjs/demo/videojs-hlsjs-plugin.html b/p2p-media-loader-hlsjs/demo/videojs-hlsjs-plugin.html index 6f0a52d5..a386de49 100644 --- a/p2p-media-loader-hlsjs/demo/videojs-hlsjs-plugin.html +++ b/p2p-media-loader-hlsjs/demo/videojs-hlsjs-plugin.html @@ -15,59 +15,53 @@ limitations under the License. --> - - - - - Video.js player with hls.js engine and P2P demo - - - - - - - - - - - - - - - - - - - + + + + Video.js player with hls.js engine and P2P demo + + + + + + + + + + + + + + + + diff --git a/p2p-media-loader-hlsjs/lib/declarations.d.ts b/p2p-media-loader-hlsjs/lib/declarations.d.ts index 603edad7..52deaba2 100644 --- a/p2p-media-loader-hlsjs/lib/declarations.d.ts +++ b/p2p-media-loader-hlsjs/lib/declarations.d.ts @@ -17,8 +17,8 @@ declare module "m3u8-parser" { export class Parser { constructor(); - push (m3u8: string): void; - end (): void; + push(m3u8: string): void; + end(): void; manifest: Manifest; } @@ -30,12 +30,12 @@ declare module "m3u8-parser" { export type Segment = { uri: string; - byteRange?: { length: number; offset: number; }; - } + byteRange?: { length: number; offset: number }; + }; export type Playlist = { uri: string; - } + }; } // FIXME: fixes hls.js internal .js module import diff --git a/p2p-media-loader-hlsjs/lib/engine.ts b/p2p-media-loader-hlsjs/lib/engine.ts index 670b2564..2fbf1828 100644 --- a/p2p-media-loader-hlsjs/lib/engine.ts +++ b/p2p-media-loader-hlsjs/lib/engine.ts @@ -15,17 +15,8 @@ */ import { EventEmitter } from "events"; -import { - Events, - LoaderInterface, - HybridLoader, - HybridLoaderSettings, -} from "p2p-media-loader-core"; -import { - SegmentManager, - ByteRange, - SegmentManagerSettings, -} from "./segment-manager"; +import { Events, LoaderInterface, HybridLoader, HybridLoaderSettings } from "p2p-media-loader-core"; +import { SegmentManager, ByteRange, SegmentManagerSettings } from "./segment-manager"; import { HlsJsLoader } from "./hlsjs-loader"; import type { LoaderCallbacks, LoaderConfiguration, LoaderContext } from "hls.js/src/types/loader"; @@ -46,18 +37,11 @@ export class Engine extends EventEmitter { super(); this.loader = new HybridLoader(settings.loader); - this.segmentManager = new SegmentManager( - this.loader, - settings.segments - ); + this.segmentManager = new SegmentManager(this.loader, settings.segments); Object.keys(Events) .map((eventKey) => Events[eventKey as keyof typeof Events]) - .forEach((event) => - this.loader.on(event, (...args: unknown[]) => - this.emit(event, ...args) - ) - ); + .forEach((event) => this.loader.on(event, (...args: unknown[]) => this.emit(event, ...args))); } public createLoaderClass(): new () => unknown { @@ -117,12 +101,7 @@ export class Engine extends EventEmitter { }; } - public setPlayingSegment( - url: string, - byteRange: ByteRange, - start: number, - duration: number - ): void { + public setPlayingSegment(url: string, byteRange: ByteRange, start: number, duration: number): void { this.segmentManager.setPlayingSegment(url, byteRange, start, duration); } @@ -142,10 +121,6 @@ export interface Asset { export interface AssetsStorage { storeAsset(asset: Asset): Promise; - getAsset( - requestUri: string, - requestRange: string | undefined, - masterSwarmId: string - ): Promise; + getAsset(requestUri: string, requestRange: string | undefined, masterSwarmId: string): Promise; destroy(): Promise; } diff --git a/p2p-media-loader-hlsjs/lib/hlsjs-loader.ts b/p2p-media-loader-hlsjs/lib/hlsjs-loader.ts index 86a9580a..16595150 100644 --- a/p2p-media-loader-hlsjs/lib/hlsjs-loader.ts +++ b/p2p-media-loader-hlsjs/lib/hlsjs-loader.ts @@ -27,20 +27,26 @@ export class HlsJsLoader { this.segmentManager = segmentManager; } - public async load(context: LoaderContext, _config: LoaderConfiguration, callbacks: LoaderCallbacks): Promise { - if ((context as unknown as { type: unknown }).type) { + public async load( + context: LoaderContext, + _config: LoaderConfiguration, + callbacks: LoaderCallbacks + ): Promise { + if (((context as unknown) as { type: unknown }).type) { try { const result = await this.segmentManager.loadPlaylist(context.url); this.successPlaylist(result, context, callbacks); } catch (e) { this.error(e, context, callbacks); } - } else if ((context as unknown as { frag: unknown }).frag) { + } else if (((context as unknown) as { frag: unknown }).frag) { try { - const result = await this.segmentManager.loadSegment(context.url, - (context.rangeStart === undefined) || (context.rangeEnd === undefined) + const result = await this.segmentManager.loadSegment( + context.url, + context.rangeStart === undefined || context.rangeEnd === undefined ? undefined - : { offset: context.rangeStart, length: context.rangeEnd - context.rangeStart }); + : { offset: context.rangeStart, length: context.rangeEnd - context.rangeStart } + ); const { content } = result; if (content !== undefined) { setTimeout(() => this.successSegment(content, result.downloadBandwidth, context, callbacks), 0); @@ -54,13 +60,19 @@ export class HlsJsLoader { } public abort(context: LoaderContext): void { - this.segmentManager.abortSegment(context.url, - (context.rangeStart === undefined) || (context.rangeEnd === undefined) + this.segmentManager.abortSegment( + context.url, + context.rangeStart === undefined || context.rangeEnd === undefined ? undefined - : { offset: context.rangeStart, length: context.rangeEnd - context.rangeStart }); + : { offset: context.rangeStart, length: context.rangeEnd - context.rangeStart } + ); } - private successPlaylist(xhr: {response: string, responseURL: string}, context: LoaderContext, callbacks: LoaderCallbacks): void { + private successPlaylist( + xhr: { response: string; responseURL: string }, + context: LoaderContext, + callbacks: LoaderCallbacks + ): void { const now = performance.now(); const stats = { @@ -72,15 +84,29 @@ export class HlsJsLoader { total: xhr.response.length, }; - callbacks.onSuccess({ - url: xhr.responseURL, - data: xhr.response - }, stats, context, undefined); + callbacks.onSuccess( + { + url: xhr.responseURL, + data: xhr.response, + }, + stats, + context, + undefined + ); } - private successSegment(content: ArrayBuffer, downloadBandwidth: number | undefined, context: LoaderContext, callbacks: LoaderCallbacks): void { + private successSegment( + content: ArrayBuffer, + downloadBandwidth: number | undefined, + context: LoaderContext, + callbacks: LoaderCallbacks + ): void { const now = performance.now(); - const downloadTime = content.byteLength / (((downloadBandwidth === undefined) || (downloadBandwidth <= 0)) ? DEFAULT_DOWNLOAD_BANDWIDTH : downloadBandwidth); + const downloadTime = + content.byteLength / + (downloadBandwidth === undefined || downloadBandwidth <= 0 + ? DEFAULT_DOWNLOAD_BANDWIDTH + : downloadBandwidth); const stats = { trequest: now - DEFAULT_DOWNLOAD_LATENCY - downloadTime, @@ -88,16 +114,25 @@ export class HlsJsLoader { tload: now - 1, tparsed: now, loaded: content.byteLength, - total: content.byteLength - } + total: content.byteLength, + }; - callbacks.onSuccess({ - url: context.url, - data: content - }, stats, context, undefined); + callbacks.onSuccess( + { + url: context.url, + data: content, + }, + stats, + context, + undefined + ); } - private error(error: { code: number; text: string; }, context: LoaderContext, callbacks: LoaderCallbacks): void { + private error( + error: { code: number; text: string }, + context: LoaderContext, + callbacks: LoaderCallbacks + ): void { callbacks.onError(error, context, undefined); } } diff --git a/p2p-media-loader-hlsjs/lib/index.ts b/p2p-media-loader-hlsjs/lib/index.ts index b7747e4f..145aee07 100644 --- a/p2p-media-loader-hlsjs/lib/index.ts +++ b/p2p-media-loader-hlsjs/lib/index.ts @@ -54,7 +54,12 @@ export function initFlowplayerHlsJsPlayer(player: any): void { export function initVideoJsContribHlsJsPlayer(player: any): void { player.ready(() => { const options = player.tech_.options_; - if (options && options.hlsjsConfig && options.hlsjsConfig.loader && typeof options.hlsjsConfig.loader.getEngine === "function") { + if ( + options && + options.hlsjsConfig && + options.hlsjsConfig.loader && + typeof options.hlsjsConfig.loader.getEngine === "function" + ) { initHlsJsEvents(player.tech_, options.hlsjsConfig.loader.getEngine()); } }); @@ -78,11 +83,12 @@ export function initMediaElementJsPlayer(mediaElement: any): void { if (hls && hls.config && hls.config.loader && typeof hls.config.loader.getEngine === "function") { const engine: Engine = hls.config.loader.getEngine(); - if (event.data && (event.data.length > 1)) { + if (event.data && event.data.length > 1) { const frag = event.data[1].frag; - const byteRange = (frag.byteRange.length !== 2) - ? undefined - : { offset: frag.byteRange[0], length: frag.byteRange[1] - frag.byteRange[0] }; + const byteRange = + frag.byteRange.length !== 2 + ? undefined + : { offset: frag.byteRange[0], length: frag.byteRange[1] - frag.byteRange[0] }; engine.setPlayingSegment(frag.url, byteRange, frag.start, frag.duration); } } @@ -97,7 +103,7 @@ export function initMediaElementJsPlayer(mediaElement: any): void { mediaElement.addEventListener("hlsError", (event: any) => { const hls = mediaElement.hlsPlayer; if (hls && hls.config && hls.config.loader && typeof hls.config.loader.getEngine === "function") { - if ((event.data !== undefined) && (event.data.details === "bufferStalledError")) { + if (event.data !== undefined && event.data.details === "bufferStalledError") { const engine: Engine = hls.config.loader.getEngine(); engine.setPlayingSegmentByCurrentTime(hls.media.currentTime); } @@ -118,9 +124,10 @@ export function initJwPlayer(player: any, hlsjsConfig: any): void { function initHlsJsEvents(player: any, engine: Engine): void { player.on("hlsFragChanged", (_event: string, data: any) => { const frag = data.frag; - const byteRange = (frag.byteRange.length !== 2) - ? undefined - : { offset: frag.byteRange[0], length: frag.byteRange[1] - frag.byteRange[0] }; + const byteRange = + frag.byteRange.length !== 2 + ? undefined + : { offset: frag.byteRange[0], length: frag.byteRange[1] - frag.byteRange[0] }; engine.setPlayingSegment(frag.url, byteRange, frag.start, frag.duration); }); player.on("hlsDestroying", async () => { @@ -130,8 +137,7 @@ function initHlsJsEvents(player: any, engine: Engine): void { if (errorData.details === "bufferStalledError") { const htmlMediaElement = (player.media === undefined ? player.el_ // videojs-contrib-hlsjs - : player.media // all others - ) as HTMLMediaElement | undefined; + : player.media) as HTMLMediaElement | undefined; // all others if (htmlMediaElement) { engine.setPlayingSegmentByCurrentTime(htmlMediaElement.currentTime); } diff --git a/p2p-media-loader-hlsjs/lib/segment-manager.ts b/p2p-media-loader-hlsjs/lib/segment-manager.ts index 731d0f9e..9ae7c217 100644 --- a/p2p-media-loader-hlsjs/lib/segment-manager.ts +++ b/p2p-media-loader-hlsjs/lib/segment-manager.ts @@ -24,7 +24,7 @@ const defaultSettings: SegmentManagerSettings = { assetsStorage: undefined, }; -export type ByteRange = { length: number, offset: number } | undefined; +export type ByteRange = { length: number; offset: number } | undefined; export class SegmentManager { private readonly loader: LoaderInterface; @@ -32,13 +32,13 @@ export class SegmentManager { private readonly variantPlaylists = new Map(); private segmentRequest: SegmentRequest | null = null; private playQueue: { - segmentSequence: number, - segmentUrl: string, - segmentByteRange: ByteRange, + segmentSequence: number; + segmentUrl: string; + segmentByteRange: ByteRange; playPosition?: { - start: number, - duration: number - } + start: number; + duration: number; + }; }[] = []; private readonly settings: SegmentManagerSettings; @@ -66,7 +66,7 @@ export class SegmentManager { this.masterPlaylist = playlist; for (const [key, variantPlaylist] of this.variantPlaylists) { - const {streamSwarmId, found, index} = this.getStreamSwarmId(variantPlaylist.requestUrl); + const { streamSwarmId, found, index } = this.getStreamSwarmId(variantPlaylist.requestUrl); if (!found) { this.variantPlaylists.delete(key); } else { @@ -75,20 +75,21 @@ export class SegmentManager { } } } else { - const {streamSwarmId, found, index} = this.getStreamSwarmId(requestUrl); + const { streamSwarmId, found, index } = this.getStreamSwarmId(requestUrl); - if (found || (this.masterPlaylist === null)) { // do not add audio and subtitles to variants + if (found || this.masterPlaylist === null) { + // do not add audio and subtitles to variants playlist.streamSwarmId = streamSwarmId; - playlist.streamId = (this.masterPlaylist === null ? undefined : "V" + index.toString()); + playlist.streamId = this.masterPlaylist === null ? undefined : "V" + index.toString(); this.variantPlaylists.set(requestUrl, playlist); this.updateSegments(); } } } - public async loadPlaylist(url: string): Promise<{response: string, responseURL: string}> { + public async loadPlaylist(url: string): Promise<{ response: string; responseURL: string }> { const assetsStorage = this.settings.assetsStorage; - let xhr: {response: string, responseURL: string} | undefined; + let xhr: { response: string; responseURL: string } | undefined; if (assetsStorage !== undefined) { let masterSwarmId: string | undefined; @@ -121,7 +122,10 @@ export class SegmentManager { return xhr; } - public async loadSegment(url: string, byteRange: ByteRange): Promise<{content: ArrayBuffer | undefined, downloadBandwidth?: number}> { + public async loadSegment( + url: string, + byteRange: ByteRange + ): Promise<{ content: ArrayBuffer | undefined; downloadBandwidth?: number }> { const segmentLocation = this.getSegmentLocation(url, byteRange); const byteRangeString = byteRangeToString(byteRange); @@ -138,14 +142,16 @@ export class SegmentManager { if (masterSwarmId === undefined && this.variantPlaylists.size === 1) { const result = this.variantPlaylists.values().next(); - if (!result.done) { // always true + if (!result.done) { + // always true masterSwarmId = result.value.requestUrl.split("?")[0]; } } if (masterManifestUri === undefined && this.variantPlaylists.size === 1) { const result = this.variantPlaylists.values().next(); - if (!result.done) { // always true + if (!result.done) { + // always true masterManifestUri = result.value.requestUrl; } } @@ -177,8 +183,9 @@ export class SegmentManager { return { content, downloadBandwidth: 0 }; } - const segmentSequence = (segmentLocation.playlist.manifest.mediaSequence ? segmentLocation.playlist.manifest.mediaSequence : 0) - + segmentLocation.segmentIndex; + const segmentSequence = + (segmentLocation.playlist.manifest.mediaSequence ? segmentLocation.playlist.manifest.mediaSequence : 0) + + segmentLocation.segmentIndex; if (this.playQueue.length > 0) { const previousSegment = this.playQueue[this.playQueue.length - 1]; @@ -192,21 +199,30 @@ export class SegmentManager { this.segmentRequest.onError("Cancel segment request: simultaneous segment requests are not supported"); } - const promise = new Promise<{content: ArrayBuffer | undefined, downloadBandwidth?: number}>((resolve, reject) => { - this.segmentRequest = new SegmentRequest(url, byteRange, segmentSequence, segmentLocation.playlist.requestUrl, - (content: ArrayBuffer | undefined, downloadBandwidth?: number) => resolve({content, downloadBandwidth}), - error => reject(error)); - }); + const promise = new Promise<{ content: ArrayBuffer | undefined; downloadBandwidth?: number }>( + (resolve, reject) => { + this.segmentRequest = new SegmentRequest( + url, + byteRange, + segmentSequence, + segmentLocation.playlist.requestUrl, + (content: ArrayBuffer | undefined, downloadBandwidth?: number) => + resolve({ content, downloadBandwidth }), + (error) => reject(error) + ); + } + ); - this.playQueue.push({segmentUrl: url, segmentByteRange: byteRange, segmentSequence: segmentSequence}); + this.playQueue.push({ segmentUrl: url, segmentByteRange: byteRange, segmentSequence: segmentSequence }); void this.loadSegments(segmentLocation.playlist, segmentLocation.segmentIndex, true); return promise; } public setPlayingSegment(url: string, byteRange: ByteRange, start: number, duration: number): void { - const urlIndex = this.playQueue.findIndex(segment => - (segment.segmentUrl === url) && compareByteRanges(segment.segmentByteRange, byteRange)); + const urlIndex = this.playQueue.findIndex( + (segment) => segment.segmentUrl === url && compareByteRanges(segment.segmentByteRange, byteRange) + ); if (urlIndex >= 0) { this.playQueue = this.playQueue.slice(urlIndex); @@ -233,8 +249,11 @@ export class SegmentManager { } public abortSegment(url: string, byteRange: ByteRange): void { - if (this.segmentRequest && (this.segmentRequest.segmentUrl === url) && - compareByteRanges(this.segmentRequest.segmentByteRange, byteRange)) { + if ( + this.segmentRequest && + this.segmentRequest.segmentUrl === url && + compareByteRanges(this.segmentRequest.segmentByteRange, byteRange) + ) { this.segmentRequest.onSuccess(undefined, 0); this.segmentRequest = null; } @@ -262,38 +281,53 @@ export class SegmentManager { return; } - const segmentLocation = this.getSegmentLocation(this.segmentRequest.segmentUrl, this.segmentRequest.segmentByteRange); + const segmentLocation = this.getSegmentLocation( + this.segmentRequest.segmentUrl, + this.segmentRequest.segmentByteRange + ); if (segmentLocation) { void this.loadSegments(segmentLocation.playlist, segmentLocation.segmentIndex, false); } } private onSegmentLoaded = (segment: Segment) => { - if (this.segmentRequest && (this.segmentRequest.segmentUrl === segment.url) && - (byteRangeToString(this.segmentRequest.segmentByteRange) === segment.range)) { + if ( + this.segmentRequest && + this.segmentRequest.segmentUrl === segment.url && + byteRangeToString(this.segmentRequest.segmentByteRange) === segment.range + ) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.segmentRequest.onSuccess(segment.data!.slice(0), segment.downloadBandwidth); this.segmentRequest = null; } - } + }; private onSegmentError = (segment: Segment, error: unknown) => { - if (this.segmentRequest && (this.segmentRequest.segmentUrl === segment.url) && - (byteRangeToString(this.segmentRequest.segmentByteRange) === segment.range)) { + if ( + this.segmentRequest && + this.segmentRequest.segmentUrl === segment.url && + byteRangeToString(this.segmentRequest.segmentByteRange) === segment.range + ) { this.segmentRequest.onError(error); this.segmentRequest = null; } - } + }; private onSegmentAbort = (segment: Segment) => { - if (this.segmentRequest && (this.segmentRequest.segmentUrl === segment.url) && - (byteRangeToString(this.segmentRequest.segmentByteRange) === segment.range)) { + if ( + this.segmentRequest && + this.segmentRequest.segmentUrl === segment.url && + byteRangeToString(this.segmentRequest.segmentByteRange) === segment.range + ) { this.segmentRequest.onError("Loading aborted: internal abort"); this.segmentRequest = null; } - } + }; - private getSegmentLocation(url: string, byteRange: ByteRange): { playlist: Playlist, segmentIndex: number } | undefined { + private getSegmentLocation( + url: string, + byteRange: ByteRange + ): { playlist: Playlist; segmentIndex: number } | undefined { for (const playlist of this.variantPlaylists.values()) { const segmentIndex = playlist.getSegmentIndex(url, byteRange); if (segmentIndex >= 0) { @@ -314,7 +348,11 @@ export class SegmentManager { const masterSwarmId = this.getMasterSwarmId(); - for (let i = segmentIndex; i < playlistSegments.length && segments.length < this.settings.forwardSegmentCount; ++i) { + for ( + let i = segmentIndex; + i < playlistSegments.length && segments.length < this.settings.forwardSegmentCount; + ++i + ) { const segment = playlist.manifest.segments[i]; const url = playlist.getSegmentAbsoluteUrl(segment.uri); @@ -339,7 +377,8 @@ export class SegmentManager { if (loadSegmentId) { const segment = await this.loader.getSegment(loadSegmentId); - if (segment) { // Segment already loaded by loader + if (segment) { + // Segment already loaded by loader this.onSegmentLoaded(segment); } } @@ -350,24 +389,26 @@ export class SegmentManager { } private getMasterSwarmId() { - const settingsSwarmId = (this.settings.swarmId && (this.settings.swarmId.length !== 0)) ? this.settings.swarmId : undefined; + const settingsSwarmId = + this.settings.swarmId && this.settings.swarmId.length !== 0 ? this.settings.swarmId : undefined; if (settingsSwarmId !== undefined) { return settingsSwarmId; } - return (this.masterPlaylist !== null) - ? this.masterPlaylist.requestUrl.split("?")[0] - : undefined; + return this.masterPlaylist !== null ? this.masterPlaylist.requestUrl.split("?")[0] : undefined; } - private getStreamSwarmId(playlistUrl: string): {streamSwarmId: string, found: boolean, index: number} { + private getStreamSwarmId(playlistUrl: string): { streamSwarmId: string; found: boolean; index: number } { const masterSwarmId = this.getMasterSwarmId(); if (this.masterPlaylist && this.masterPlaylist.manifest.playlists && masterSwarmId) { for (let i = 0; i < this.masterPlaylist.manifest.playlists.length; ++i) { - const url = new URL(this.masterPlaylist.manifest.playlists[i].uri, this.masterPlaylist.responseUrl).toString(); + const url = new URL( + this.masterPlaylist.manifest.playlists[i].uri, + this.masterPlaylist.responseUrl + ).toString(); if (url === playlistUrl) { - return {streamSwarmId: `${masterSwarmId}+V${i}`, found: true, index: i}; + return { streamSwarmId: `${masterSwarmId}+V${i}`, found: true, index: i }; } } } @@ -375,11 +416,15 @@ export class SegmentManager { return { streamSwarmId: masterSwarmId ?? playlistUrl.split("?")[0], found: false, - index: -1 + index: -1, }; } - private async loadContent(url: string, responseType: XMLHttpRequestResponseType, range?: string): Promise { + private async loadContent( + url: string, + responseType: XMLHttpRequestResponseType, + range?: string + ): Promise { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("GET", url, true); @@ -419,7 +464,7 @@ class Playlist { const segment = this.manifest.segments[i]; const segmentUrl = this.getSegmentAbsoluteUrl(segment.uri); - if ((url === segmentUrl) && compareByteRanges(segment.byteRange, byteRange)) { + if (url === segmentUrl && compareByteRanges(segment.byteRange, byteRange)) { return i; } } @@ -462,9 +507,7 @@ export interface SegmentManagerSettings { } function compareByteRanges(b1: ByteRange, b2: ByteRange) { - return (b1 === undefined) - ? (b2 === undefined) - : ((b2 !== undefined) && (b1.length === b2.length) && (b1.offset === b2.offset)); + return b1 === undefined ? b2 === undefined : b2 !== undefined && b1.length === b2.length && b1.offset === b2.offset; } function byteRangeToString(byteRange: ByteRange): string | undefined { diff --git a/p2p-media-loader-hlsjs/package-lock.json b/p2p-media-loader-hlsjs/package-lock.json index ef8db5e8..b6445042 100644 --- a/p2p-media-loader-hlsjs/package-lock.json +++ b/p2p-media-loader-hlsjs/package-lock.json @@ -153,9 +153,9 @@ "dev": true }, "@types/eslint": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.7.tgz", - "integrity": "sha512-EHXbc1z2GoQRqHaAT7+grxlTJ3WE2YNeD6jlpPoRc83cCoThRY+NUWjCUZaYmk51OICkPXn2hhphcWcWXgNW0Q==", + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.8.tgz", + "integrity": "sha512-RTKvBsfz0T8CKOGZMfuluDNyMFHnu5lvNr4hWEsQeHXH6FcmIDIozOyWMh36nLGMwVd5UFNXC2xztA8lln22MQ==", "dev": true, "requires": { "@types/estree": "*", @@ -1053,9 +1053,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001204", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001204.tgz", - "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==", + "version": "1.0.30001205", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz", + "integrity": "sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og==", "dev": true }, "chalk": { @@ -1555,9 +1555,9 @@ } }, "electron-to-chromium": { - "version": "1.3.703", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.703.tgz", - "integrity": "sha512-SVBVhNB+4zPL+rvtWLw7PZQkw/Eqj1HQZs22xtcqW36+xoifzEOEEDEpkxSMfB6RFeSIOcG00w6z5mSqLr1Y6w==", + "version": "1.3.706", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.706.tgz", + "integrity": "sha512-IcXgNXeW6+ObrvHnQtjhokjdOPI/DQ5j0f9M6gUy82kc9GNTMxq/mTkxWlPBSpqO1mAomR1uPDsssKDMj1V4Cw==", "dev": true }, "elliptic": { @@ -1741,6 +1741,12 @@ } } }, + "eslint-config-prettier": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", + "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", + "dev": true + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -2809,18 +2815,18 @@ } }, "mime-db": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", - "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", "dev": true }, "mime-types": { - "version": "2.1.29", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", - "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", + "version": "2.1.30", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", + "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", "dev": true, "requires": { - "mime-db": "1.46.0" + "mime-db": "1.47.0" } }, "mimic-fn": { @@ -3348,6 +3354,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -4080,9 +4092,9 @@ }, "dependencies": { "ajv": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.1.tgz", - "integrity": "sha512-46ZA4TalFcLLqX1dEU3dhdY38wAtDydJ4e7QQTVekLUTzXkb1LfqU6VOBXC/a9wiv4T094WURqJH6ZitF92Kqw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.5.tgz", + "integrity": "sha512-RkiLa/AeJx7+9OvniQ/qeWu0w74A8DiPPBclQ6ji3ZQkv5KamO+QGpqmi7O4JIw3rHGUXZ6CoP9tsAkn3gyazg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -4443,9 +4455,9 @@ } }, "webpack": { - "version": "5.28.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.28.0.tgz", - "integrity": "sha512-1xllYVmA4dIvRjHzwELgW4KjIU1fW4PEuEnjsylz7k7H5HgPOctIq7W1jrt3sKH9yG5d72//XWzsHhfoWvsQVg==", + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.30.0.tgz", + "integrity": "sha512-Zr9NIri5yzpfmaMea2lSMV1UygbW0zQsSlGLMgKUm63ACXg6alhd1u4v5UBSBjzYKXJN6BNMGVM7w165e7NxYA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", diff --git a/p2p-media-loader-hlsjs/package.json b/p2p-media-loader-hlsjs/package.json index 1996b5ce..27c0c7c6 100644 --- a/p2p-media-loader-hlsjs/package.json +++ b/p2p-media-loader-hlsjs/package.json @@ -48,9 +48,11 @@ "@typescript-eslint/parser": "^4.20.0", "browserify": "^17.0.0", "eslint": "^7.23.0", + "eslint-config-prettier": "^8.1.0", "hls.js": "^0.14.17", "mkdirp": "^1.0.4", "mocha": "^8.3.2", + "prettier": "^2.2.1", "sinon": "^10.0.0", "terser": "^5.6.1", "ts-loader": "^8.1.0", diff --git a/p2p-media-loader-shaka/.editorconfig b/p2p-media-loader-shaka/.editorconfig index 4b43aa8e..6c761897 100644 --- a/p2p-media-loader-shaka/.editorconfig +++ b/p2p-media-loader-shaka/.editorconfig @@ -7,6 +7,7 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +max_line_length = 120 [*.md] trim_trailing_whitespace = false [*.json] diff --git a/p2p-media-loader-shaka/.eslintrc b/p2p-media-loader-shaka/.eslintrc index b22e2242..0484f2e1 100644 --- a/p2p-media-loader-shaka/.eslintrc +++ b/p2p-media-loader-shaka/.eslintrc @@ -11,7 +11,8 @@ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking" + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "prettier" ], "env": { "browser": true diff --git a/p2p-media-loader-shaka/demo/clappr.html b/p2p-media-loader-shaka/demo/clappr.html index 746e8747..f58e51f0 100644 --- a/p2p-media-loader-shaka/demo/clappr.html +++ b/p2p-media-loader-shaka/demo/clappr.html @@ -15,55 +15,49 @@ limitations under the License. --> - - - - - Clappr player with Shaka Player engine and P2P demo (MPEG-DASH only) - - - - - - - - - - - - - - -
- - - - + + + + Clappr player with Shaka Player engine and P2P demo (MPEG-DASH only) + + + + + + + + + + + + +
+ + + diff --git a/p2p-media-loader-shaka/demo/dplayer.html b/p2p-media-loader-shaka/demo/dplayer.html index 64003f95..3f4d7735 100644 --- a/p2p-media-loader-shaka/demo/dplayer.html +++ b/p2p-media-loader-shaka/demo/dplayer.html @@ -15,66 +15,66 @@ limitations under the License. --> - - - - - DPlayer with Shaka Player engine and P2P demo (HLS or MPEG-DASH) - - - - - - - - - - - - - - - -
- - - - + + + + DPlayer with Shaka Player engine and P2P demo (HLS or MPEG-DASH) + + + + + + + + + + + + + +
+ + + diff --git a/p2p-media-loader-shaka/demo/offlineplayback.html b/p2p-media-loader-shaka/demo/offlineplayback.html index 5694b3f1..917c2698 100644 --- a/p2p-media-loader-shaka/demo/offlineplayback.html +++ b/p2p-media-loader-shaka/demo/offlineplayback.html @@ -15,176 +15,197 @@ limitations under the License. --> - - - - - Offline playback with Shaka Player engine and P2P demo - - - - - - - - - - - - - + + + + + + + + + - + (async () => { + const db = await initIndexedDb(); + const assetsStorage = new IdbAssetsStorage(db); + const segmentsStorage = new IdbSegmentsStorage(db); + + shaka.polyfill.installAll(); + + if (shaka.Player.isBrowserSupported() && p2pml.shaka.Engine.isSupported()) { + var settings = { + segments: { + assetsStorage: assetsStorage, + }, + loader: { + segmentsStorage: segmentsStorage, + }, + }; + var engine = new p2pml.shaka.Engine(settings); + + var video = document.getElementById("video"); + var player = new shaka.Player(video); + var onError = function (error) { + console.error("Error code", error.code, "object", error); + }; + player.addEventListener("error", function (event) { + onError(event.detail); + }); + + engine.initShakaPlayer(player); + + player.configure({ abr: { enabled: false } }); // disable ABR + player.load("https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd").catch(onError); + } else { + document.write("Not supported :("); + } + })(); + + diff --git a/p2p-media-loader-shaka/demo/plyr.html b/p2p-media-loader-shaka/demo/plyr.html index 58864177..7ba8582d 100644 --- a/p2p-media-loader-shaka/demo/plyr.html +++ b/p2p-media-loader-shaka/demo/plyr.html @@ -15,53 +15,47 @@ limitations under the License. --> - - - - - Plyr player with Shaka Player engine and P2P demo - - - - - - - - - - - - - - -
- -
- - - - + + + + Plyr player with Shaka Player engine and P2P demo + + + + + + + + + + + + +
+ +
+ + + diff --git a/p2p-media-loader-shaka/demo/shakaplayer.html b/p2p-media-loader-shaka/demo/shakaplayer.html index 8b8c2ed2..77ab8527 100644 --- a/p2p-media-loader-shaka/demo/shakaplayer.html +++ b/p2p-media-loader-shaka/demo/shakaplayer.html @@ -15,46 +15,53 @@ limitations under the License. --> - - - - - Shaka Player with P2P demo (HLS or MPEG-DASH) - - - - - - - - - - - - - - - + + + + Shaka Player with P2P demo (HLS or MPEG-DASH) + + + + + + + + + + + + diff --git a/p2p-media-loader-shaka/lib/declarations.d.ts b/p2p-media-loader-shaka/lib/declarations.d.ts index a9635c61..95caf817 100644 --- a/p2p-media-loader-shaka/lib/declarations.d.ts +++ b/p2p-media-loader-shaka/lib/declarations.d.ts @@ -27,14 +27,14 @@ namespace shaka { static install(): void; } - class Fullscreen extends polyfill_install { } - class IndexedDB extends polyfill_install { } - class InputEvent extends polyfill_install { } - class MathRound extends polyfill_install { } - class MediaSource extends polyfill_install { } - class VideoPlaybackQuality extends polyfill_install { } - class VideoPlayPromise extends polyfill_install { } - class VTTCue extends polyfill_install { } + class Fullscreen extends polyfill_install {} + class IndexedDB extends polyfill_install {} + class InputEvent extends polyfill_install {} + class MathRound extends polyfill_install {} + class MediaSource extends polyfill_install {} + class VideoPlaybackQuality extends polyfill_install {} + class VideoPlayPromise extends polyfill_install {} + class VTTCue extends polyfill_install {} class PatchedMediaKeysMs extends polyfill_install { static setMediaKeys(mediaKeys: MediaKeys): void; @@ -62,27 +62,27 @@ namespace shaka { } interface EventMap { - "abrstatuschanged": Player.AbrStatusChangedEvent, - "adaptation": Player.AdaptationEvent, - "buffering": Player.BufferingEvent, - "drmsessionupdate": Player.DrmSessionUpdateEvent, - "emsg": Player.EmsgEvent, - "error": Player.ErrorEvent, - "expirationupdated": Player.ExpirationUpdatedEvent, - "largegap": Player.LargeGapEvent, - "loading": Player.LoadingEvent, - "manifestparsed": Player.ManifestParsedEvent, - "onstatechange": Player.StateChangeEvent, - "onstateidle": Player.StateIdleEvent, - "streaming": Player.StreamingEvent, - "textchanged": Player.TextChangedEvent, - "texttrackvisibility": Player.TextTrackVisibilityEvent, - "timelineregionadded": Player.TimelineRegionAddedEvent, - "timelineregionenter": Player.TimelineRegionEnterEvent, - "timelineregionexit": Player.TimelineRegionExitEvent, - "trackschanged": Player.TracksChangedEvent, - "unloading": Player.UnloadingEvent, - "variantchanged": Player.VariantChangedEvent, + abrstatuschanged: Player.AbrStatusChangedEvent; + adaptation: Player.AdaptationEvent; + buffering: Player.BufferingEvent; + drmsessionupdate: Player.DrmSessionUpdateEvent; + emsg: Player.EmsgEvent; + error: Player.ErrorEvent; + expirationupdated: Player.ExpirationUpdatedEvent; + largegap: Player.LargeGapEvent; + loading: Player.LoadingEvent; + manifestparsed: Player.ManifestParsedEvent; + onstatechange: Player.StateChangeEvent; + onstateidle: Player.StateIdleEvent; + streaming: Player.StreamingEvent; + textchanged: Player.TextChangedEvent; + texttrackvisibility: Player.TextTrackVisibilityEvent; + timelineregionadded: Player.TimelineRegionAddedEvent; + timelineregionenter: Player.TimelineRegionEnterEvent; + timelineregionexit: Player.TimelineRegionExitEvent; + trackschanged: Player.TracksChangedEvent; + unloading: Player.UnloadingEvent; + variantchanged: Player.VariantChangedEvent; } class Player extends util.FakeEventTarget implements util.IDestroyable { @@ -139,7 +139,8 @@ namespace shaka { addEventListener( type: K, listener: (event: EventMap[K]) => boolean | void | undefined, - options?: AddEventListenerOptions): void; + options?: AddEventListenerOptions + ): void; // eslint-disable-next-line no-dupe-class-members addEventListener( @@ -975,13 +976,24 @@ namespace shaka { destroy(): Promise; } - - declare const HttpXHRPlugin: { - static parse: (uri: string, request: extern.Request, requestType: net.NetworkingEngine.RequestType, progressUpdated?: shaka.extern.ProgressUpdated) => util.AbortableOperation. - } | { - (uri: string, request: extern.Request, requestType: net.NetworkingEngine.RequestType, progressUpdated?: shaka.extern.ProgressUpdated): util.AbortableOperation.; - static parse: undefined; - }; + declare const HttpXHRPlugin: + | { + static parse: ( + uri: string, + request: extern.Request, + requestType: net.NetworkingEngine.RequestType, + progressUpdated?: shaka.extern.ProgressUpdated + ) => util.AbortableOperation; + } + | { + ( + uri: string, + request: extern.Request, + requestType: net.NetworkingEngine.RequestType, + progressUpdated?: shaka.extern.ProgressUpdated + ): util.AbortableOperation; + static parse: undefined; + }; } namespace media { @@ -1244,11 +1256,7 @@ namespace shaka { namespace StringUtils { function fromBytesAutoDetect(data: BufferSource | null): string; function fromUTF8(data: BufferSource | null): string; - function fromUTF16( - data: BufferSource | null, - littleEndian?: boolean, - opt_noThrow?: boolean - ): string; + function fromUTF16(data: BufferSource | null, littleEndian?: boolean, opt_noThrow?: boolean): string; function toUTF8(str: string): ArrayBuffer; } @@ -1257,7 +1265,6 @@ namespace shaka { } class FakeEventTarget { - /** * A work-alike for EventTarget. Only DOM elements may be true * EventTargets, but this can be used as a base class to @@ -1315,12 +1322,7 @@ namespace shaka { } class Error { - constructor( - severity: Error.Severity, - category: Error.Category, - code: Error.Code, - ...var_args: unknown - ); + constructor(severity: Error.Severity, category: Error.Category, code: Error.Code, ...var_args: unknown); data: Array; category: util.Error.Category; @@ -1461,19 +1463,9 @@ namespace shaka { } namespace extern { - type ProgressUpdated = ( - duration: number, - downloadedBytes: number, - remainingBytes: number - ) => void; - type RequestFilter = ( - type: net.NetworkingEngine.RequestType, - request: Request - ) => void | Promise; - type ResponseFilter = ( - type: net.NetworkingEngine.RequestType, - response: Response - ) => void | Promise; + type ProgressUpdated = (duration: number, downloadedBytes: number, remainingBytes: number) => void; + type RequestFilter = (type: net.NetworkingEngine.RequestType, request: Request) => void | Promise; + type ResponseFilter = (type: net.NetworkingEngine.RequestType, response: Response) => void | Promise; type SchemePlugin = ( uri: string, request: Request, @@ -1954,7 +1946,7 @@ namespace shaka { update(): void; } - interface TimelineRegionInfo { } + interface TimelineRegionInfo {} } namespace hls { diff --git a/p2p-media-loader-shaka/lib/engine.ts b/p2p-media-loader-shaka/lib/engine.ts index 1add206a..35751f34 100644 --- a/p2p-media-loader-shaka/lib/engine.ts +++ b/p2p-media-loader-shaka/lib/engine.ts @@ -25,7 +25,6 @@ export interface ShakaEngineSettings { } export class Engine extends EventEmitter { - public static isSupported(): boolean { return HybridLoader.isSupported(); } @@ -40,8 +39,8 @@ export class Engine extends EventEmitter { this.segmentManager = new SegmentManager(this.loader, settings.segments); Object.keys(Events) - .map(eventKey => Events[eventKey as keyof typeof Events]) - .forEach(event => this.loader.on(event, (...args: unknown[]) => this.emit(event, ...args))); + .map((eventKey) => Events[eventKey as keyof typeof Events]) + .forEach((event) => this.loader.on(event, (...args: unknown[]) => this.emit(event, ...args))); } public async destroy(): Promise { @@ -54,20 +53,19 @@ export class Engine extends EventEmitter { } { return { segments: this.segmentManager.getSettings(), - loader: this.loader.getSettings() + loader: this.loader.getSettings(), }; } - public getDetails(): { loader: unknown; } { + public getDetails(): { loader: unknown } { return { - loader: this.loader.getDetails() + loader: this.loader.getDetails(), }; } public initShakaPlayer(player: shaka.Player): void { integration.initShakaPlayer(player, this.segmentManager); } - } export interface Asset { diff --git a/p2p-media-loader-shaka/lib/integration.ts b/p2p-media-loader-shaka/lib/integration.ts index f9dd64e0..3409db9d 100644 --- a/p2p-media-loader-shaka/lib/integration.ts +++ b/p2p-media-loader-shaka/lib/integration.ts @@ -16,13 +16,18 @@ import Debug from "debug"; import { SegmentManager } from "./segment-manager"; -import { HookedShakaManifest, HookedShakaNetworkingEngine, ShakaDashManifestParserProxy, ShakaHlsManifestParserProxy } from "./manifest-parser-proxy"; +import { + HookedShakaManifest, + HookedShakaNetworkingEngine, + ShakaDashManifestParserProxy, + ShakaHlsManifestParserProxy, +} from "./manifest-parser-proxy"; import { getSchemedUri, getMasterSwarmId } from "./utils"; import { ParserSegment } from "./parser-segment"; const debug = Debug("p2pml:shaka:index"); -type HookedRequest = shaka.extern.Request & { p2pml?: { player: shaka.Player, segmentManager: SegmentManager } }; +type HookedRequest = shaka.extern.Request & { p2pml?: { player: shaka.Player; segmentManager: SegmentManager } }; export function initShakaPlayer(player: shaka.Player, segmentManager: SegmentManager): void { registerParserProxies(); @@ -40,7 +45,7 @@ export function initShakaPlayer(player: shaka.Player, segmentManager: SegmentMan lastPlayheadTimeReported = 0; - const manifest = player.getManifest() as (HookedShakaManifest | null); + const manifest = player.getManifest() as HookedShakaManifest | null; if (manifest && manifest.p2pml) { manifest.p2pml.parser.reset(); } @@ -60,9 +65,11 @@ export function initShakaPlayer(player: shaka.Player, segmentManager: SegmentMan }); debug("register request filter"); - player.getNetworkingEngine().registerRequestFilter((requestType: shaka.net.NetworkingEngine.RequestType, request: shaka.extern.Request) => { - (request as HookedRequest).p2pml = { player, segmentManager }; - }); + player + .getNetworkingEngine() + .registerRequestFilter((requestType: shaka.net.NetworkingEngine.RequestType, request: shaka.extern.Request) => { + (request as HookedRequest).p2pml = { player, segmentManager }; + }); } function registerParserProxies() { @@ -80,7 +87,12 @@ function initializeNetworkingEngine() { shaka.net.NetworkingEngine.registerScheme("https", processNetworkRequest); } -function processNetworkRequest(uri: string, request: HookedRequest, requestType: shaka.net.NetworkingEngine.RequestType, progressUpdated?: shaka.extern.ProgressUpdated): shaka.util.AbortableOperation { +function processNetworkRequest( + uri: string, + request: HookedRequest, + requestType: shaka.net.NetworkingEngine.RequestType, + progressUpdated?: shaka.extern.ProgressUpdated +): shaka.util.AbortableOperation { const xhrPlugin = shaka.net.HttpXHRPlugin.parse ? shaka.net.HttpXHRPlugin.parse : shaka.net.HttpXHRPlugin; const { p2pml } = request; @@ -108,11 +120,16 @@ function processNetworkRequest(uri: string, request: HookedRequest, requestType: segment = manifest?.p2pml?.parser?.find(uri, request.headers?.Range); } - if (segment !== undefined && segment.streamType === "video") { // load segment using P2P loader + if (segment !== undefined && segment.streamType === "video") { + // load segment using P2P loader debug("request", "load", segment.identity); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const promise = segmentManager.load(segment, getSchemedUri((player.getAssetUri ? player.getAssetUri() : player.getManifestUri())!), getPlayheadTime(player)); + const promise = segmentManager.load( + segment, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + getSchemedUri((player.getAssetUri ? player.getAssetUri() : player.getManifestUri())!), + getPlayheadTime(player) + ); const abort = async () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -121,7 +138,8 @@ function processNetworkRequest(uri: string, request: HookedRequest, requestType: }; return new shaka.util.AbortableOperation(promise, abort); - } else if (assetsStorage && masterSwarmId && masterManifestUri) { // load or store the asset using assets storage + } else if (assetsStorage && masterSwarmId && masterManifestUri) { + // load or store the asset using assets storage const responsePromise = (async () => { const asset = await assetsStorage.getAsset(uri, request.headers?.Range, masterSwarmId); if (asset !== undefined) { @@ -130,7 +148,7 @@ function processNetworkRequest(uri: string, request: HookedRequest, requestType: uri: asset.responseUri, originalUri: asset.requestUri, fromCache: true, - headers: {} + headers: {}, }; } else { const response = await xhrPlugin(uri, request, requestType, progressUpdated).promise; @@ -140,14 +158,15 @@ function processNetworkRequest(uri: string, request: HookedRequest, requestType: requestUri: uri, requestRange: request.headers?.Range, responseUri: response.uri, - data: response.data + data: response.data, }); return response; } })(); return new shaka.util.AbortableOperation(responsePromise, async () => undefined); - } else { // load asset using default plugin + } else { + // load asset using default plugin return xhrPlugin(uri, request, requestType, progressUpdated); } } diff --git a/p2p-media-loader-shaka/lib/manifest-parser-proxy.ts b/p2p-media-loader-shaka/lib/manifest-parser-proxy.ts index ac9aae7c..6227d68e 100644 --- a/p2p-media-loader-shaka/lib/manifest-parser-proxy.ts +++ b/p2p-media-loader-shaka/lib/manifest-parser-proxy.ts @@ -19,13 +19,12 @@ import { ParserSegment, ParserSegmentCache } from "./parser-segment"; export type HookedShakaStream = shaka.extern.Stream & { getSegmentReferenceOriginal: shaka.extern.GetSegmentReferenceFunction; createSegmentIndexOriginal: shaka.extern.CreateSegmentIndexFunction; - getPosition: () => number + getPosition: () => number; }; export type HookedShakaManifest = shaka.extern.Manifest & { p2pml?: { parser: ShakaManifestParserProxy } }; export type HookedShakaNetworkingEngine = shaka.net.NetworkingEngine & { p2pml?: { masterManifestUri: string } }; export class ShakaManifestParserProxy implements shaka.extern.ManifestParser { - private readonly cache: ParserSegmentCache = new ParserSegmentCache(200); private readonly originalManifestParser: shaka.extern.ManifestParser; private manifest?: HookedShakaManifest; @@ -42,9 +41,14 @@ export class ShakaManifestParserProxy implements shaka.extern.ManifestParser { return this.originalManifestParser instanceof shaka.dash.DashParser; } - public async start(uri: string, playerInterface: shaka.extern.ManifestParser.PlayerInterface): Promise { + public async start( + uri: string, + playerInterface: shaka.extern.ManifestParser.PlayerInterface + ): Promise { // Tell P2P Media Loader's networking engine code about currently loading manifest - const networkingEngine = playerInterface.networkingEngine as shaka.net.NetworkingEngine & { p2pml?: { masterManifestUri: string } }; + const networkingEngine = playerInterface.networkingEngine as shaka.net.NetworkingEngine & { + p2pml?: { masterManifestUri: string }; + }; networkingEngine.p2pml = { masterManifestUri: uri }; this.manifest = await this.originalManifestParser.start(uri, playerInterface); @@ -53,7 +57,7 @@ export class ShakaManifestParserProxy implements shaka.extern.ManifestParser { const processedStreams = []; for (const variant of period.variants) { - if ((variant.video !== null) && (processedStreams.indexOf(variant.video) === -1)) { + if (variant.video !== null && processedStreams.indexOf(variant.video) === -1) { if (variant.video.getSegmentReference as shaka.extern.GetSegmentReferenceFunction | undefined) { this.hookGetSegmentReference(variant.video as HookedShakaStream); } else { @@ -62,7 +66,7 @@ export class ShakaManifestParserProxy implements shaka.extern.ManifestParser { processedStreams.push(variant.video); } - if ((variant.audio !== null) && (processedStreams.indexOf(variant.audio) === -1)) { + if (variant.audio !== null && processedStreams.indexOf(variant.audio) === -1) { if (variant.audio.getSegmentReference as shaka.extern.GetSegmentReferenceFunction | undefined) { this.hookGetSegmentReference(variant.audio as HookedShakaStream); } else { @@ -124,7 +128,8 @@ export class ShakaManifestParserProxy implements shaka.extern.ManifestParser { // eslint-disable-next-line @typescript-eslint/unbound-method const getOriginal = stream.segmentIndex.get; - stream.getSegmentReferenceOriginal = (segmentNumber: number) => getOriginal.call(stream.segmentIndex, segmentNumber); + stream.getSegmentReferenceOriginal = (segmentNumber: number) => + getOriginal.call(stream.segmentIndex, segmentNumber); stream.segmentIndex.get = (segmentNumber: number) => { const reference = stream.getSegmentReferenceOriginal(segmentNumber); @@ -151,7 +156,7 @@ export class ShakaManifestParserProxy implements shaka.extern.ManifestParser { } } return -1; - } + }; } export class ShakaDashManifestParserProxy extends ShakaManifestParserProxy { diff --git a/p2p-media-loader-shaka/lib/parser-segment.ts b/p2p-media-loader-shaka/lib/parser-segment.ts index 8f050d16..f46fe33b 100644 --- a/p2p-media-loader-shaka/lib/parser-segment.ts +++ b/p2p-media-loader-shaka/lib/parser-segment.ts @@ -18,8 +18,10 @@ import { getSchemedUri } from "./utils"; import { HookedShakaStream } from "./manifest-parser-proxy"; export class ParserSegment { - - public static create(stream: HookedShakaStream, segmentReference: shaka.media.SegmentReference | null): ParserSegment | undefined { + public static create( + stream: HookedShakaStream, + segmentReference: shaka.media.SegmentReference | null + ): ParserSegment | undefined { if (!segmentReference) { return undefined; } @@ -34,21 +36,15 @@ export class ParserSegment { const startByte = segmentReference.getStartByte(); const endByte = segmentReference.getEndByte(); - const range = startByte || endByte - ? `bytes=${startByte || ""}-${endByte || ""}` - : undefined; + const range = startByte || endByte ? `bytes=${startByte || ""}-${endByte || ""}` : undefined; const streamTypeCode = stream.type.substring(0, 1).toUpperCase(); const streamPosition = stream.getPosition(); const streamIsHls = streamPosition >= 0; - const streamIdentity = streamIsHls - ? `${streamTypeCode}${streamPosition}` - : `${streamTypeCode}${stream.id}`; + const streamIdentity = streamIsHls ? `${streamTypeCode}${streamPosition}` : `${streamTypeCode}${stream.id}`; - const identity = streamIsHls - ? `${segmentReference.getPosition()}` - : `${Number(start).toFixed(3)}`; + const identity = streamIsHls ? `${segmentReference.getPosition()}` : `${Number(start).toFixed(3)}`; return new ParserSegment( stream.id, @@ -59,9 +55,9 @@ export class ParserSegment { segmentReference.getPosition(), start, end, - getSchemedUri(uris[ 0 ]), + getSchemedUri(uris[0]), range, - () => ParserSegment.create(stream, stream.getSegmentReferenceOriginal(segmentReference.getPosition() - 1)), + () => ParserSegment.create(stream, stream.getSegmentReferenceOriginal(segmentReference.getPosition() - 1)) ); } @@ -78,11 +74,9 @@ export class ParserSegment { readonly range: string | undefined, readonly next: () => ParserSegment | undefined ) {} - } // end of ParserSegment export class ParserSegmentCache { - private readonly segments: ParserSegment[] = []; private readonly maxSegments: number; @@ -91,7 +85,7 @@ export class ParserSegmentCache { } public find(uri: string, range?: string): ParserSegment | undefined { - return this.segments.find(i => i.uri === uri && i.range === range); + return this.segments.find((i) => i.uri === uri && i.range === range); } public add(stream: HookedShakaStream, segmentReference: shaka.media.SegmentReference | null): void { @@ -107,5 +101,4 @@ export class ParserSegmentCache { public clear(): void { this.segments.splice(0); } - } // end of ParserSegmentCache diff --git a/p2p-media-loader-shaka/lib/segment-manager.ts b/p2p-media-loader-shaka/lib/segment-manager.ts index 25ed084a..66b6ac5b 100644 --- a/p2p-media-loader-shaka/lib/segment-manager.ts +++ b/p2p-media-loader-shaka/lib/segment-manager.ts @@ -28,7 +28,6 @@ const defaultSettings: SegmentManagerSettings = { }; export class SegmentManager { - private readonly debug = Debug("p2pml:shaka:sm"); private readonly loader: LoaderInterface; private readonly requests = new Map(); @@ -71,7 +70,11 @@ export class SegmentManager { return this.settings; } - public async load(parserSegment: ParserSegment, manifestUri: string, playheadTime: number): Promise { + public async load( + parserSegment: ParserSegment, + manifestUri: string, + playheadTime: number + ): Promise { this.manifestUri = manifestUri; this.playheadTime = playheadTime; @@ -101,7 +104,7 @@ export class SegmentManager { } private refreshLoad(): LoaderSegment { - const lastRequestedSegment = this.segmentHistory[ this.segmentHistory.length - 1 ]; + const lastRequestedSegment = this.segmentHistory[this.segmentHistory.length - 1]; const safePlayheadTime = this.playheadTime > 0.1 ? this.playheadTime : lastRequestedSegment.start; const sequence: ParserSegment[] = this.segmentHistory.reduce((a: ParserSegment[], i) => { if (i.start >= safePlayheadTime) { @@ -117,7 +120,7 @@ export class SegmentManager { const lastRequestedSegmentIndex = sequence.length - 1; do { - const next = sequence[ sequence.length - 1 ].next(); + const next = sequence[sequence.length - 1].next(); if (next) { sequence.push(next); } else { @@ -139,7 +142,7 @@ export class SegmentManager { })); this.loader.load(loaderSegments, `${masterSwarmId}+${lastRequestedSegment.streamIdentity}`); - return loaderSegments[ lastRequestedSegmentIndex ]; + return loaderSegments[lastRequestedSegmentIndex]; } private pushSegmentHistory(segment: ParserSegment) { @@ -148,7 +151,10 @@ export class SegmentManager { this.segmentHistory.splice(0, this.settings.maxHistorySegments * 0.2); } - if (this.segmentHistory.length > 0 && this.segmentHistory[ this.segmentHistory.length - 1 ].start > segment.start) { + if ( + this.segmentHistory.length > 0 && + this.segmentHistory[this.segmentHistory.length - 1].start > segment.start + ) { this.debug("segment history reset due to playhead seek back"); this.segmentHistory.splice(0); } @@ -159,7 +165,12 @@ export class SegmentManager { private reportSuccess(request: Request, loaderSegment: LoaderSegment) { let timeMs: number | undefined; - if (loaderSegment.downloadBandwidth !== undefined && loaderSegment.downloadBandwidth > 0 && loaderSegment.data && loaderSegment.data.byteLength > 0) { + if ( + loaderSegment.downloadBandwidth !== undefined && + loaderSegment.downloadBandwidth > 0 && + loaderSegment.data && + loaderSegment.data.byteLength > 0 + ) { timeMs = Math.trunc(loaderSegment.data.byteLength / loaderSegment.downloadBandwidth); } @@ -190,7 +201,7 @@ export class SegmentManager { this.debug("request delete", segment.id); this.requests.delete(segment.id); } - } + }; private onSegmentError = (segment: LoaderSegment, error: unknown) => { if (this.requests.has(segment.id)) { @@ -199,7 +210,7 @@ export class SegmentManager { this.debug("request delete from error", segment.id); this.requests.delete(segment.id); } - } + }; private onSegmentAbort = (segment: LoaderSegment) => { if (this.requests.has(segment.id)) { @@ -208,8 +219,7 @@ export class SegmentManager { this.debug("request delete from abort", segment.id); this.requests.delete(segment.id); } - } - + }; } class Request { diff --git a/p2p-media-loader-shaka/lib/utils.ts b/p2p-media-loader-shaka/lib/utils.ts index a8394a7a..e90cff2f 100644 --- a/p2p-media-loader-shaka/lib/utils.ts +++ b/p2p-media-loader-shaka/lib/utils.ts @@ -18,7 +18,6 @@ export function getSchemedUri(uri: string): string { return uri.startsWith("//") ? window.location.protocol + uri : uri; } -export function getMasterSwarmId(masterManifestUri: string, settings: {swarmId?: string}) : string { - return (settings.swarmId && (settings.swarmId.length !== 0)) ? - settings.swarmId : masterManifestUri.split("?")[0]; +export function getMasterSwarmId(masterManifestUri: string, settings: { swarmId?: string }): string { + return settings.swarmId && settings.swarmId.length !== 0 ? settings.swarmId : masterManifestUri.split("?")[0]; } diff --git a/p2p-media-loader-shaka/package-lock.json b/p2p-media-loader-shaka/package-lock.json index 30eb32c2..83ef9b29 100644 --- a/p2p-media-loader-shaka/package-lock.json +++ b/p2p-media-loader-shaka/package-lock.json @@ -116,9 +116,9 @@ "dev": true }, "@types/eslint": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.7.tgz", - "integrity": "sha512-EHXbc1z2GoQRqHaAT7+grxlTJ3WE2YNeD6jlpPoRc83cCoThRY+NUWjCUZaYmk51OICkPXn2hhphcWcWXgNW0Q==", + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.8.tgz", + "integrity": "sha512-RTKvBsfz0T8CKOGZMfuluDNyMFHnu5lvNr4hWEsQeHXH6FcmIDIozOyWMh36nLGMwVd5UFNXC2xztA8lln22MQ==", "dev": true, "requires": { "@types/estree": "*", @@ -945,9 +945,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001204", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001204.tgz", - "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==", + "version": "1.0.30001205", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz", + "integrity": "sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og==", "dev": true }, "chalk": { @@ -1397,9 +1397,9 @@ } }, "electron-to-chromium": { - "version": "1.3.703", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.703.tgz", - "integrity": "sha512-SVBVhNB+4zPL+rvtWLw7PZQkw/Eqj1HQZs22xtcqW36+xoifzEOEEDEpkxSMfB6RFeSIOcG00w6z5mSqLr1Y6w==", + "version": "1.3.706", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.706.tgz", + "integrity": "sha512-IcXgNXeW6+ObrvHnQtjhokjdOPI/DQ5j0f9M6gUy82kc9GNTMxq/mTkxWlPBSpqO1mAomR1uPDsssKDMj1V4Cw==", "dev": true }, "elliptic": { @@ -1583,6 +1583,12 @@ } } }, + "eslint-config-prettier": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", + "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", + "dev": true + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -2543,18 +2549,18 @@ } }, "mime-db": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", - "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", "dev": true }, "mime-types": { - "version": "2.1.29", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", - "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", + "version": "2.1.30", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", + "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", "dev": true, "requires": { - "mime-db": "1.46.0" + "mime-db": "1.47.0" } }, "mimic-fn": { @@ -2927,6 +2933,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -3603,9 +3615,9 @@ }, "dependencies": { "ajv": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.1.tgz", - "integrity": "sha512-46ZA4TalFcLLqX1dEU3dhdY38wAtDydJ4e7QQTVekLUTzXkb1LfqU6VOBXC/a9wiv4T094WURqJH6ZitF92Kqw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.5.tgz", + "integrity": "sha512-RkiLa/AeJx7+9OvniQ/qeWu0w74A8DiPPBclQ6ji3ZQkv5KamO+QGpqmi7O4JIw3rHGUXZ6CoP9tsAkn3gyazg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -3924,9 +3936,9 @@ } }, "webpack": { - "version": "5.28.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.28.0.tgz", - "integrity": "sha512-1xllYVmA4dIvRjHzwELgW4KjIU1fW4PEuEnjsylz7k7H5HgPOctIq7W1jrt3sKH9yG5d72//XWzsHhfoWvsQVg==", + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.30.0.tgz", + "integrity": "sha512-Zr9NIri5yzpfmaMea2lSMV1UygbW0zQsSlGLMgKUm63ACXg6alhd1u4v5UBSBjzYKXJN6BNMGVM7w165e7NxYA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", diff --git a/p2p-media-loader-shaka/package.json b/p2p-media-loader-shaka/package.json index 788634a2..6b15ea3c 100644 --- a/p2p-media-loader-shaka/package.json +++ b/p2p-media-loader-shaka/package.json @@ -46,7 +46,9 @@ "@typescript-eslint/parser": "^4.20.0", "browserify": "^17.0.0", "eslint": "^7.23.0", + "eslint-config-prettier": "^8.1.0", "mkdirp": "^1.0.4", + "prettier": "^2.2.1", "terser": "^5.6.1", "ts-loader": "^8.1.0", "typescript": "^4.2.3",