p2p-media-loader-core
Version:
P2P Media Loader core functionality
192 lines • 7.84 kB
JavaScript
import { P2PTrackerClient } from "./tracker-client.js";
import * as StreamUtils from "../utils/stream.js";
import * as Utils from "../utils/utils.js";
export class P2PLoader {
constructor(streamManifestUrl, stream, requests, segmentStorage, config, eventTarget, onSegmentAnnouncement) {
Object.defineProperty(this, "streamManifestUrl", {
enumerable: true,
configurable: true,
writable: true,
value: streamManifestUrl
});
Object.defineProperty(this, "stream", {
enumerable: true,
configurable: true,
writable: true,
value: stream
});
Object.defineProperty(this, "requests", {
enumerable: true,
configurable: true,
writable: true,
value: requests
});
Object.defineProperty(this, "segmentStorage", {
enumerable: true,
configurable: true,
writable: true,
value: segmentStorage
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: config
});
Object.defineProperty(this, "eventTarget", {
enumerable: true,
configurable: true,
writable: true,
value: eventTarget
});
Object.defineProperty(this, "onSegmentAnnouncement", {
enumerable: true,
configurable: true,
writable: true,
value: onSegmentAnnouncement
});
Object.defineProperty(this, "trackerClient", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "isAnnounceMicrotaskCreated", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "onPeerConnected", {
enumerable: true,
configurable: true,
writable: true,
value: (peer) => {
if (this.config.isP2PUploadDisabled)
return;
const { httpLoading, loaded } = this.getSegmentsAnnouncement();
peer.sendSegmentsAnnouncementCommand(loaded, httpLoading);
}
});
Object.defineProperty(this, "broadcastAnnouncement", {
enumerable: true,
configurable: true,
writable: true,
value: (sendEmptyAnnouncement = false) => {
if (sendEmptyAnnouncement) {
this.sendSegmentsAnnouncement([], []);
return;
}
if (this.isAnnounceMicrotaskCreated || this.config.isP2PUploadDisabled) {
return;
}
const { loaded, httpLoading } = this.getSegmentsAnnouncement();
this.sendSegmentsAnnouncement(loaded, httpLoading);
}
});
Object.defineProperty(this, "sendSegmentsAnnouncement", {
enumerable: true,
configurable: true,
writable: true,
value: (loaded, httpLoading) => {
this.isAnnounceMicrotaskCreated = true;
queueMicrotask(() => {
for (const peer of this.trackerClient.peers()) {
peer.sendSegmentsAnnouncementCommand(loaded, httpLoading);
}
this.isAnnounceMicrotaskCreated = false;
});
}
});
Object.defineProperty(this, "onSegmentRequested", {
enumerable: true,
configurable: true,
writable: true,
value: async (peer, segmentExternalId, requestId, byteFrom) => {
const segment = StreamUtils.getSegmentFromStreamByExternalId(this.stream, segmentExternalId);
if (!segment)
return;
if (this.config.isP2PUploadDisabled) {
peer.sendSegmentAbsentCommand(segmentExternalId, requestId);
return;
}
const swarmId = this.config.swarmId ?? this.streamManifestUrl;
const streamSwarmId = StreamUtils.getStreamSwarmId(swarmId, this.stream);
const segmentData = await this.segmentStorage.getSegmentData(swarmId, streamSwarmId, segment.externalId);
if (!segmentData) {
peer.sendSegmentAbsentCommand(segmentExternalId, requestId);
return;
}
await peer.uploadSegmentData(segment, requestId, byteFrom !== undefined ? segmentData.slice(byteFrom) : segmentData);
}
});
const swarmId = this.config.swarmId ?? this.streamManifestUrl;
const streamSwarmId = StreamUtils.getStreamSwarmId(swarmId, this.stream);
this.trackerClient = new P2PTrackerClient(streamSwarmId, this.stream, {
onPeerConnected: this.onPeerConnected,
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onSegmentRequested: this.onSegmentRequested,
onSegmentsAnnouncement: this.onSegmentAnnouncement,
}, this.config, this.eventTarget);
this.eventTarget.addEventListener(`onStorageUpdated-${streamSwarmId}`, this.broadcastAnnouncement);
this.segmentStorage.setSegmentChangeCallback((streamId) => {
this.eventTarget.dispatchEvent(`onStorageUpdated-${streamId}`);
});
this.trackerClient.start();
}
downloadSegment(segment) {
const peersWithSegment = [];
for (const peer of this.trackerClient.peers()) {
if (!peer.downloadingSegment &&
peer.getSegmentStatus(segment) === "loaded") {
peersWithSegment.push(peer);
}
}
if (peersWithSegment.length === 0)
return;
const peer = Utils.getRandomItem(peersWithSegment);
const request = this.requests.getOrCreateRequest(segment);
peer.downloadSegment(request);
}
isSegmentLoadingOrLoadedBySomeone(segment) {
for (const peer of this.trackerClient.peers()) {
if (peer.getSegmentStatus(segment))
return true;
}
return false;
}
isSegmentLoadedBySomeone(segment) {
for (const peer of this.trackerClient.peers()) {
if (peer.getSegmentStatus(segment) === "loaded")
return true;
}
return false;
}
get connectedPeerCount() {
let count = 0;
const iterator = this.trackerClient.peers();
while (!iterator.next().done)
count++;
return count;
}
getSegmentsAnnouncement() {
const swarmId = this.config.swarmId ?? this.streamManifestUrl;
const streamSwarmId = StreamUtils.getStreamSwarmId(swarmId, this.stream);
const loaded = this.segmentStorage.getStoredSegmentIds(swarmId, streamSwarmId);
const httpLoading = [];
for (const request of this.requests.httpRequests()) {
const segment = this.stream.segments.get(request.segment.runtimeId);
if (!segment)
continue;
httpLoading.push(segment.externalId);
}
return { loaded, httpLoading };
}
destroy() {
const swarmId = this.config.swarmId ?? this.streamManifestUrl;
const streamSwarmId = StreamUtils.getStreamSwarmId(swarmId, this.stream);
this.eventTarget.removeEventListener(`onStorageUpdated-${streamSwarmId}`, this.broadcastAnnouncement);
this.trackerClient.destroy();
}
}
//# sourceMappingURL=loader.js.map