UNPKG

p2p-media-loader-core

Version:
240 lines 8.92 kB
import debug from "debug"; import { isAndroid, isIPadOrIPhone, isAndroidWebview, getStorageItemId, } from "./utils.js"; const BYTES_PER_MiB = 1048576; export class SegmentMemoryStorage { constructor() { Object.defineProperty(this, "userAgent", { enumerable: true, configurable: true, writable: true, value: navigator.userAgent }); Object.defineProperty(this, "segmentMemoryStorageLimit", { enumerable: true, configurable: true, writable: true, value: 4 * 1024 }); Object.defineProperty(this, "currentStorageUsage", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "cache", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "logger", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "coreConfig", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "mainStreamConfig", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "secondaryStreamConfig", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "currentPlayback", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "lastRequestedSegment", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "segmentChangeCallback", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.logger = debug("p2pml-core:segment-memory-storage"); this.logger.color = "RebeccaPurple"; } // eslint-disable-next-line @typescript-eslint/require-await async initialize(coreConfig, mainStreamConfig, secondaryStreamConfig) { this.coreConfig = coreConfig; this.mainStreamConfig = mainStreamConfig; this.secondaryStreamConfig = secondaryStreamConfig; this.setMemoryStorageLimit(); this.logger("initialized"); } onPlaybackUpdated(position, rate) { this.currentPlayback = { position, rate }; } onSegmentRequested(swarmId, streamId, segmentId, startTime, endTime, streamType, isLiveStream) { this.lastRequestedSegment = { streamId, segmentId, startTime, endTime, swarmId, streamType, isLiveStream, }; } // eslint-disable-next-line @typescript-eslint/require-await async storeSegment(_swarmId, streamId, segmentId, data, startTime, endTime, streamType, isLiveStream) { this.clear(isLiveStream, data.byteLength); const storageId = getStorageItemId(streamId, segmentId); this.cache.set(storageId, { data, segmentId, streamId, startTime, endTime, streamType, }); this.increaseStorageUsage(data.byteLength); this.logger(`add segment: ${segmentId} to ${streamId}`); if (!this.segmentChangeCallback) { throw new Error("dispatchStorageUpdatedEvent is not set"); } this.segmentChangeCallback(streamId); } // eslint-disable-next-line @typescript-eslint/require-await async getSegmentData(_swarmId, streamId, segmentId) { const segmentStorageId = getStorageItemId(streamId, segmentId); const dataItem = this.cache.get(segmentStorageId); if (dataItem === undefined) return undefined; return dataItem.data; } getUsage() { if (!this.lastRequestedSegment || !this.currentPlayback) { return { totalCapacity: this.segmentMemoryStorageLimit, usedCapacity: this.currentStorageUsage, }; } const playbackPosition = this.currentPlayback.position; let calculatedUsedCapacity = 0; for (const { endTime, data } of this.cache.values()) { if (playbackPosition > endTime) continue; calculatedUsedCapacity += data.byteLength; } return { totalCapacity: this.segmentMemoryStorageLimit, usedCapacity: calculatedUsedCapacity / BYTES_PER_MiB, }; } hasSegment(_swarmId, streamId, externalId) { const segmentStorageId = getStorageItemId(streamId, externalId); const segment = this.cache.get(segmentStorageId); return segment !== undefined; } getStoredSegmentIds(_swarmId, streamId) { const externalIds = []; for (const { segmentId, streamId: streamCacheId } of this.cache.values()) { if (streamCacheId !== streamId) continue; externalIds.push(segmentId); } return externalIds; } clear(isLiveStream, newSegmentSize) { if (!this.currentPlayback || !this.mainStreamConfig || !this.secondaryStreamConfig || !this.coreConfig) { return; } const isMemoryLimitReached = this.isMemoryLimitReached(newSegmentSize); if (!isMemoryLimitReached && !isLiveStream) return; const affectedStreams = new Set(); const sortedCache = Array.from(this.cache.values()).sort((a, b) => a.startTime - b.startTime); for (const segmentData of sortedCache) { const { streamId, segmentId, data } = segmentData; const storageId = getStorageItemId(streamId, segmentId); const shouldRemove = this.shouldRemoveSegment(segmentData, isLiveStream, this.currentPlayback.position); if (!shouldRemove) continue; this.cache.delete(storageId); affectedStreams.add(streamId); this.decreaseStorageUsage(data.byteLength); this.logger(`Removed segment ${segmentId} from stream ${streamId}`); if (!this.isMemoryLimitReached(newSegmentSize) && !isLiveStream) break; } this.sendUpdatesToAffectedStreams(affectedStreams); } isMemoryLimitReached(segmentByteLength) { return (this.currentStorageUsage + segmentByteLength / BYTES_PER_MiB > this.segmentMemoryStorageLimit); } setSegmentChangeCallback(callback) { this.segmentChangeCallback = callback; } sendUpdatesToAffectedStreams(affectedStreams) { if (affectedStreams.size === 0) return; affectedStreams.forEach((stream) => { if (!this.segmentChangeCallback) { throw new Error("dispatchStorageUpdatedEvent is not set"); } this.segmentChangeCallback(stream); }); } shouldRemoveSegment(segmentData, isLiveStream, currentPlaybackPosition) { const { endTime, streamType } = segmentData; const highDemandTimeWindow = this.getStreamTimeWindow(streamType, "highDemandTimeWindow"); if (currentPlaybackPosition <= endTime) return false; if (isLiveStream) { return currentPlaybackPosition > highDemandTimeWindow + endTime; } return true; } increaseStorageUsage(segmentByteLength) { this.currentStorageUsage += segmentByteLength / BYTES_PER_MiB; } decreaseStorageUsage(segmentByteLength) { this.currentStorageUsage -= segmentByteLength / BYTES_PER_MiB; } setMemoryStorageLimit() { if (this.coreConfig?.segmentMemoryStorageLimit) { this.segmentMemoryStorageLimit = this.coreConfig.segmentMemoryStorageLimit; return; } if (isAndroidWebview(this.userAgent) || isIPadOrIPhone(this.userAgent)) { this.segmentMemoryStorageLimit = 1024; } else if (isAndroid(this.userAgent)) { this.segmentMemoryStorageLimit = 2 * 1024; } } getStreamTimeWindow(streamType, configKey) { const config = streamType === "main" ? this.mainStreamConfig : this.secondaryStreamConfig; return config?.[configKey] ?? 0; } destroy() { this.cache.clear(); } } //# sourceMappingURL=segment-memory-storage.js.map