p2p-media-loader-core
Version:
P2P Media Loader core functionality
240 lines • 8.92 kB
JavaScript
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