p2p-media-loader-core
Version:
P2P Media Loader core functionality
148 lines (134 loc) • 4.49 kB
text/typescript
import { P2PLoader } from "./loader.js";
import debug from "debug";
import {
CoreEventMap,
Stream,
StreamConfig,
StreamWithSegments,
SegmentStorage,
} from "../index.js";
import { RequestsContainer } from "../requests/request-container.js";
import * as LoggerUtils from "../utils/logger.js";
import { EventTarget } from "../utils/event-target.js";
import * as StreamUtils from "../utils/stream.js";
import { WebTorrentSocketPool } from "../webtorrent/webtorrent-socket-pool/index.js";
type P2PLoaderContainerItem = {
stream: Stream;
loader: P2PLoader;
destroyTimeoutId?: number;
loggerInfo: string;
};
export class P2PLoadersContainer {
readonly #loaders = new Map<string, P2PLoaderContainerItem>();
#currentLoaderItem: P2PLoaderContainerItem;
readonly #logger = debug("p2pml-core:p2p-loaders-container");
readonly #streamManifestUrl: string;
readonly #requests: RequestsContainer;
readonly #segmentStorage: SegmentStorage;
readonly #config: StreamConfig;
readonly #webTorrentSocketPool: WebTorrentSocketPool;
readonly #eventTarget: EventTarget<CoreEventMap>;
readonly #peerId: string;
readonly #onSegmentAnnouncement: () => void;
constructor(
streamManifestUrl: string,
stream: StreamWithSegments,
requests: RequestsContainer,
segmentStorage: SegmentStorage,
config: StreamConfig,
webTorrentSocketPool: WebTorrentSocketPool,
eventTarget: EventTarget<CoreEventMap>,
peerId: string,
onSegmentAnnouncement: () => void,
) {
this.#streamManifestUrl = streamManifestUrl;
this.#requests = requests;
this.#segmentStorage = segmentStorage;
this.#config = config;
this.#webTorrentSocketPool = webTorrentSocketPool;
this.#eventTarget = eventTarget;
this.#peerId = peerId;
this.#onSegmentAnnouncement = onSegmentAnnouncement;
this.#currentLoaderItem = this.#findOrCreateLoaderForStream(stream);
this.#logger(
`set current p2p loader: ${LoggerUtils.getStreamString(stream)}`,
);
}
#createLoader(stream: StreamWithSegments): P2PLoaderContainerItem {
if (this.#loaders.has(stream.runtimeId)) {
throw new Error("Loader for this stream already exists");
}
const loader = new P2PLoader(
this.#streamManifestUrl,
stream,
this.#requests,
this.#segmentStorage,
this.#config,
this.#webTorrentSocketPool,
this.#eventTarget,
this.#peerId,
() => {
if (this.#currentLoaderItem.loader === loader) {
this.#onSegmentAnnouncement();
}
},
);
const loggerInfo = LoggerUtils.getStreamString(stream);
this.#logger(`created new loader: ${loggerInfo}`);
return {
loader,
stream,
loggerInfo,
};
}
#findOrCreateLoaderForStream(stream: StreamWithSegments) {
const loaderItem = this.#loaders.get(stream.runtimeId);
if (loaderItem) {
clearTimeout(loaderItem.destroyTimeoutId);
loaderItem.destroyTimeoutId = undefined;
return loaderItem;
} else {
const loader = this.#createLoader(stream);
this.#loaders.set(stream.runtimeId, loader);
return loader;
}
}
changeCurrentLoader(stream: StreamWithSegments) {
const swarmId = this.#config.swarmId ?? this.#streamManifestUrl;
const streamSwarmId = StreamUtils.getStreamSwarmId(
swarmId,
this.#currentLoaderItem.stream,
);
const ids = this.#segmentStorage.getStoredSegmentIds(
swarmId,
streamSwarmId,
);
if (!ids.length) this.#destroyAndRemoveLoader(this.#currentLoaderItem);
else this.#setLoaderDestroyTimeout(this.#currentLoaderItem);
this.#currentLoaderItem = this.#findOrCreateLoaderForStream(stream);
this.#logger(
`change current p2p loader: ${LoggerUtils.getStreamString(stream)}`,
);
}
#setLoaderDestroyTimeout(item: P2PLoaderContainerItem) {
item.destroyTimeoutId = window.setTimeout(
() => this.#destroyAndRemoveLoader(item),
this.#config.p2pInactiveLoaderDestroyTimeoutMs,
);
}
#destroyAndRemoveLoader(item: P2PLoaderContainerItem) {
item.loader.destroy();
this.#loaders.delete(item.stream.runtimeId);
this.#logger(`destroy p2p loader: `, item.loggerInfo);
}
get currentLoader() {
return this.#currentLoaderItem.loader;
}
destroy() {
for (const { loader, destroyTimeoutId } of this.#loaders.values()) {
loader.destroy();
clearTimeout(destroyTimeoutId);
}
this.#loaders.clear();
}
}