UNPKG

p2p-media-loader-core

Version:
443 lines 15.9 kB
import debug from "debug"; import { RequestError, } from "../types.js"; import * as Utils from "../utils/utils.js"; function mapSegmentWithStreamToSegment(segment) { return { runtimeId: segment.runtimeId, externalId: segment.externalId, url: segment.url, byteRange: segment.byteRange, startTime: segment.startTime, endTime: segment.endTime, }; } export class Request { constructor(segment, requestProcessQueueCallback, bandwidthCalculators, playback, playbackConfig, eventTarget) { Object.defineProperty(this, "segment", { enumerable: true, configurable: true, writable: true, value: segment }); Object.defineProperty(this, "requestProcessQueueCallback", { enumerable: true, configurable: true, writable: true, value: requestProcessQueueCallback }); Object.defineProperty(this, "bandwidthCalculators", { enumerable: true, configurable: true, writable: true, value: bandwidthCalculators }); Object.defineProperty(this, "playback", { enumerable: true, configurable: true, writable: true, value: playback }); Object.defineProperty(this, "playbackConfig", { enumerable: true, configurable: true, writable: true, value: playbackConfig }); Object.defineProperty(this, "currentAttempt", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_failedAttempts", { enumerable: true, configurable: true, writable: true, value: new FailedRequestAttempts() }); Object.defineProperty(this, "finalData", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "bytes", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "_loadedBytes", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_totalBytes", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_status", { enumerable: true, configurable: true, writable: true, value: "not-started" }); Object.defineProperty(this, "progress", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "notReceivingBytesTimeout", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_abortRequestCallback", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_logger", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_isHandledByProcessQueue", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "onSegmentError", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "onSegmentAbort", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "onSegmentStart", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "onSegmentLoaded", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "abortOnTimeout", { enumerable: true, configurable: true, writable: true, value: () => { this.throwErrorIfNotLoadingStatus(); if (!this.currentAttempt) return; this.setStatus("failed"); const error = new RequestError("bytes-receiving-timeout"); this._abortRequestCallback?.(error); this.logger(`${this.downloadSource} ${this.segment.externalId} failed ${error.type}`); this._failedAttempts.add({ ...this.currentAttempt, error, }); this.onSegmentError({ segment: mapSegmentWithStreamToSegment(this.segment), error, downloadSource: this.currentAttempt.downloadSource, peerId: this.currentAttempt.downloadSource === "p2p" ? this.currentAttempt.peerId : undefined, streamType: this.segment.stream.type, }); this.notReceivingBytesTimeout.clear(); this.manageBandwidthCalculatorsState("stop"); this.requestProcessQueueCallback(); } }); Object.defineProperty(this, "abortOnError", { enumerable: true, configurable: true, writable: true, value: (error) => { this.throwErrorIfNotLoadingStatus(); if (!this.currentAttempt) return; this.setStatus("failed"); this.logger(`${this.downloadSource} ${this.segment.externalId} failed ${error.type}`); this._failedAttempts.add({ ...this.currentAttempt, error, }); this.onSegmentError({ segment: mapSegmentWithStreamToSegment(this.segment), error, downloadSource: this.currentAttempt.downloadSource, peerId: this.currentAttempt.downloadSource === "p2p" ? this.currentAttempt.peerId : undefined, streamType: this.segment.stream.type, }); this.notReceivingBytesTimeout.clear(); this.manageBandwidthCalculatorsState("stop"); this.requestProcessQueueCallback(); } }); Object.defineProperty(this, "completeOnSuccess", { enumerable: true, configurable: true, writable: true, value: () => { this.throwErrorIfNotLoadingStatus(); if (!this.currentAttempt) return; this.manageBandwidthCalculatorsState("stop"); this.notReceivingBytesTimeout.clear(); this.setStatus("succeed"); this._totalBytes = this._loadedBytes; this.onSegmentLoaded({ segmentUrl: this.segment.url, bytesLength: this.data.byteLength, downloadSource: this.currentAttempt.downloadSource, peerId: this.currentAttempt.downloadSource === "p2p" ? this.currentAttempt.peerId : undefined, streamType: this.segment.stream.type, }); this.logger(`${this.currentAttempt.downloadSource} ${this.segment.externalId} succeed`); this.requestProcessQueueCallback(); } }); Object.defineProperty(this, "addLoadedChunk", { enumerable: true, configurable: true, writable: true, value: (chunk) => { this.throwErrorIfNotLoadingStatus(); if (!this.currentAttempt || !this.progress) return; this.notReceivingBytesTimeout.restart(); const { byteLength } = chunk; const { all: allBC, http: httpBC } = this.bandwidthCalculators; allBC.addBytes(byteLength); if (this.currentAttempt.downloadSource === "http") { httpBC.addBytes(byteLength); } this.bytes.push(chunk); this.progress.lastLoadedChunkTimestamp = performance.now(); this.progress.loadedBytes += byteLength; this._loadedBytes += byteLength; } }); Object.defineProperty(this, "firstBytesReceived", { enumerable: true, configurable: true, writable: true, value: () => { this.throwErrorIfNotLoadingStatus(); this.notReceivingBytesTimeout.restart(); } }); this.onSegmentError = eventTarget.getEventDispatcher("onSegmentError"); this.onSegmentAbort = eventTarget.getEventDispatcher("onSegmentAbort"); this.onSegmentStart = eventTarget.getEventDispatcher("onSegmentStart"); this.onSegmentLoaded = eventTarget.getEventDispatcher("onSegmentLoaded"); const { byteRange } = this.segment; if (byteRange) { const { end, start } = byteRange; this._totalBytes = end - start + 1; } this.notReceivingBytesTimeout = new Timeout(this.abortOnTimeout); const { type } = this.segment.stream; this._logger = debug(`p2pml-core:request-${type}`); } clearLoadedBytes() { this._loadedBytes = 0; this.bytes = []; this._totalBytes = undefined; this.finalData = undefined; } get status() { return this._status; } setStatus(status) { this._status = status; this._isHandledByProcessQueue = false; } get downloadSource() { return this.currentAttempt?.downloadSource; } get loadedBytes() { return this._loadedBytes; } get totalBytes() { return this._totalBytes; } get data() { if (!this.finalData) this.finalData = Utils.joinChunks(this.bytes); return this.finalData; } get failedAttempts() { return this._failedAttempts; } get isHandledByProcessQueue() { return this._isHandledByProcessQueue; } markHandledByProcessQueue() { this._isHandledByProcessQueue = true; } setTotalBytes(value) { if (this._totalBytes !== undefined) { throw new Error("Request total bytes value is already set"); } this._totalBytes = value; } start(requestData, controls) { if (this._status === "succeed") { throw new Error(`Request ${this.segment.externalId} has been already succeed.`); } if (this._status === "loading") { throw new Error(`Request ${this.segment.externalId} has been already started.`); } this.setStatus("loading"); this.currentAttempt = { ...requestData }; this.progress = { startFromByte: this._loadedBytes, loadedBytes: 0, startTimestamp: performance.now(), }; this.manageBandwidthCalculatorsState("start"); const { notReceivingBytesTimeoutMs, abort } = controls; this._abortRequestCallback = abort; if (notReceivingBytesTimeoutMs !== undefined) { this.notReceivingBytesTimeout.start(notReceivingBytesTimeoutMs); } this.logger(`${requestData.downloadSource} ${this.segment.externalId} started`); this.onSegmentStart({ segment: mapSegmentWithStreamToSegment(this.segment), downloadSource: requestData.downloadSource, peerId: requestData.downloadSource === "p2p" ? requestData.peerId : undefined, }); return { firstBytesReceived: this.firstBytesReceived, addLoadedChunk: this.addLoadedChunk, completeOnSuccess: this.completeOnSuccess, abortOnError: this.abortOnError, }; } abortFromProcessQueue() { this.throwErrorIfNotLoadingStatus(); this.setStatus("aborted"); this.logger(`${this.currentAttempt?.downloadSource} ${this.segment.externalId} aborted`); this._abortRequestCallback?.(new RequestError("abort")); this.onSegmentAbort({ segment: mapSegmentWithStreamToSegment(this.segment), downloadSource: this.currentAttempt?.downloadSource, peerId: this.currentAttempt?.downloadSource === "p2p" ? this.currentAttempt.peerId : undefined, streamType: this.segment.stream.type, }); this._abortRequestCallback = undefined; this.manageBandwidthCalculatorsState("stop"); this.notReceivingBytesTimeout.clear(); } throwErrorIfNotLoadingStatus() { if (this._status !== "loading") { throw new Error(`Request has been already ${this.status}.`); } } logger(message) { this._logger.color = this.currentAttempt?.downloadSource === "http" ? "green" : "red"; this._logger(message); this._logger.color = ""; } manageBandwidthCalculatorsState(state) { const { all, http } = this.bandwidthCalculators; const method = state === "start" ? "startLoading" : "stopLoading"; if (this.currentAttempt?.downloadSource === "http") http[method](); all[method](); } } class FailedRequestAttempts { constructor() { Object.defineProperty(this, "attempts", { enumerable: true, configurable: true, writable: true, value: [] }); } add(attempt) { this.attempts.push(attempt); } get httpAttemptsCount() { return this.attempts.reduce((sum, attempt) => (attempt.downloadSource === "http" ? sum + 1 : sum), 0); } get lastAttempt() { return this.attempts[this.attempts.length - 1]; } clear() { this.attempts = []; } } export class Timeout { constructor(action) { Object.defineProperty(this, "action", { enumerable: true, configurable: true, writable: true, value: action }); Object.defineProperty(this, "timeoutId", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "ms", { enumerable: true, configurable: true, writable: true, value: void 0 }); } start(ms) { if (this.timeoutId) { throw new Error("Timeout is already started."); } this.ms = ms; this.timeoutId = window.setTimeout(this.action, this.ms); } restart(ms) { if (this.timeoutId) clearTimeout(this.timeoutId); if (ms) this.ms = ms; if (!this.ms) return; this.timeoutId = window.setTimeout(this.action, this.ms); } clear() { clearTimeout(this.timeoutId); this.timeoutId = undefined; } } //# sourceMappingURL=request.js.map