Socket
Socket
Sign inDemoInstall

p2p-media-loader-core

Package Overview
Dependencies
Maintainers
2
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

p2p-media-loader-core - npm Package Compare versions

Comparing version 0.5.0 to 0.6.0

dist/bandwidth-approximator.d.ts

2

dist/browser-init.js

@@ -21,2 +21,2 @@ /**

window.p2pml.core = require("p2p-media-loader-core");
window.p2pml.core = require("./index");

@@ -16,7 +16,9 @@ /**

*/
import STEEmitter from "./stringly-typed-event-emitter";
import { Segment, SegmentValidatorCallback, XhrSetupCallback, SegmentUrlBuilder } from "./loader-interface";
import { STEEmitter } from "./stringly-typed-event-emitter";
import { Segment } from "./loader-interface";
import { SegmentValidatorCallback, XhrSetupCallback, SegmentUrlBuilder } from "./hybrid-loader";
export declare class HttpMediaManager extends STEEmitter<"segment-loaded" | "segment-error" | "bytes-downloaded"> {
readonly settings: {
httpFailedSegmentTimeout: number;
httpUseRanges: boolean;
segmentValidator?: SegmentValidatorCallback;

@@ -31,2 +33,3 @@ xhrSetup?: XhrSetupCallback;

httpFailedSegmentTimeout: number;
httpUseRanges: boolean;
segmentValidator?: SegmentValidatorCallback;

@@ -36,7 +39,9 @@ xhrSetup?: XhrSetupCallback;

});
download(segment: Segment): void;
download(segment: Segment, downloadedPieces?: ArrayBuffer[]): void;
abort(segment: Segment): void;
isDownloading(segment: Segment): boolean;
isFailed(segment: Segment): boolean;
getActiveDownloadsKeys(): string[];
getActiveDownloads(): ReadonlyMap<string, {
segment: Segment;
}>;
getActiveDownloadsCount(): number;

@@ -43,0 +48,0 @@ destroy(): void;

@@ -29,3 +29,3 @@ "use strict";

const loader_interface_1 = require("./loader-interface");
class HttpMediaManager extends stringly_typed_event_emitter_1.default {
class HttpMediaManager extends stringly_typed_event_emitter_1.STEEmitter {
constructor(settings) {

@@ -39,3 +39,3 @@ super();

}
download(segment) {
download(segment, downloadedPieces) {
if (this.isDownloading(segment)) {

@@ -54,14 +54,26 @@ return;

xhr.setRequestHeader("Range", segment.range);
downloadedPieces = undefined; // TODO: process downloadedPieces for segments with range headers too
}
this.setupXhrEvents(xhr, segment);
else if ((downloadedPieces !== undefined) && this.settings.httpUseRanges) {
let bytesDownloaded = 0;
for (const piece of downloadedPieces) {
bytesDownloaded += piece.byteLength;
}
xhr.setRequestHeader("Range", `bytes=${bytesDownloaded}-`);
this.debug("continue download from", bytesDownloaded);
}
else {
downloadedPieces = undefined;
}
this.setupXhrEvents(xhr, segment, downloadedPieces);
if (this.settings.xhrSetup) {
this.settings.xhrSetup(xhr, segmentUrl);
}
this.xhrRequests.set(segment.id, xhr);
this.xhrRequests.set(segment.id, { xhr, segment });
xhr.send();
}
abort(segment) {
const xhr = this.xhrRequests.get(segment.id);
if (xhr) {
xhr.abort();
const request = this.xhrRequests.get(segment.id);
if (request) {
request.xhr.abort();
this.xhrRequests.delete(segment.id);

@@ -78,4 +90,4 @@ this.debug("http segment abort", segment.id);

}
getActiveDownloadsKeys() {
return [...this.xhrRequests.keys()];
getActiveDownloads() {
return this.xhrRequests;
}

@@ -86,6 +98,6 @@ getActiveDownloadsCount() {

destroy() {
this.xhrRequests.forEach(xhr => xhr.abort());
this.xhrRequests.forEach(request => request.xhr.abort());
this.xhrRequests.clear();
}
setupXhrEvents(xhr, segment) {
setupXhrEvents(xhr, segment, downloadedPieces) {
let prevBytesLoaded = 0;

@@ -98,8 +110,22 @@ xhr.addEventListener("progress", (event) => {

xhr.addEventListener("load", (event) => __awaiter(this, void 0, void 0, function* () {
if (event.target.status >= 200 && 300 > event.target.status) {
yield this.segmentDownloadFinished(segment, event.target.response);
}
else {
if ((event.target.status < 200) || (event.target.status >= 300)) {
this.segmentFailure(segment, event);
return;
}
let data = event.target.response;
if ((downloadedPieces !== undefined) && (event.target.status === 206)) {
let bytesDownloaded = 0;
for (const piece of downloadedPieces) {
bytesDownloaded += piece.byteLength;
}
const segmentData = new Uint8Array(bytesDownloaded + data.byteLength);
let offset = 0;
for (const piece of downloadedPieces) {
segmentData.set(new Uint8Array(piece), offset);
offset += piece.byteLength;
}
segmentData.set(new Uint8Array(data), offset);
data = segmentData.buffer;
}
yield this.segmentDownloadFinished(segment, data);
}));

@@ -109,2 +135,5 @@ xhr.addEventListener("error", (event) => {

});
xhr.addEventListener("timeout", (event) => {
this.segmentFailure(segment, event);
});
}

@@ -115,3 +144,3 @@ segmentDownloadFinished(segment, data) {

try {
yield this.settings.segmentValidator(new loader_interface_1.Segment(segment.id, segment.url, segment.range, segment.priority, data), "http");
yield this.settings.segmentValidator(new loader_interface_1.Segment(segment.id, segment.url, segment.masterSwarmId, segment.masterManifestUri, segment.streamId, segment.sequence, segment.range, segment.priority, data), "http");
}

@@ -118,0 +147,0 @@ catch (error) {

@@ -16,5 +16,5 @@ /**

*/
import { LoaderInterface, Segment, SegmentValidatorCallback, XhrSetupCallback, SegmentUrlBuilder } from "./loader-interface";
import { LoaderInterface, Segment } from "./loader-interface";
import { EventEmitter } from "events";
export default class HybridLoader extends EventEmitter implements LoaderInterface {
export declare class HybridLoader extends EventEmitter implements LoaderInterface {
private readonly debug;

@@ -24,20 +24,20 @@ private readonly debugSegments;

private readonly p2pManager;
private readonly segments;
private segmentsStorage;
private segmentsQueue;
private readonly speedApproximator;
private readonly bandwidthApproximator;
private readonly settings;
private httpRandomDownloadInterval;
private httpDownloadInitialTimeoutTimestamp;
private initialDownloadedViaP2PSegmentsCount;
private masterSwarmId?;
static isSupported(): boolean;
constructor(settings?: any);
constructor(settings?: Partial<HybridLoaderSettings>);
private createHttpManager;
private createP2PManager;
load(segments: Segment[], swarmId: string): void;
getSegment(id: string): Segment | undefined;
getSettings(): Settings;
load(segments: Segment[], streamSwarmId: string): Promise<void>;
getSegment(id: string): Promise<Segment | undefined>;
getSettings(): HybridLoaderSettings;
getDetails(): {
peerId: string;
};
destroy(): void;
destroy(): Promise<void>;
private processInitialSegmentTimeout;

@@ -50,3 +50,3 @@ private processSegmentsQueue;

private onSegmentError;
private emitSegmentLoaded;
private getStreamSwarmId;
private createSegmentsMap;

@@ -56,6 +56,18 @@ private onPeerConnect;

private onTrackerUpdate;
private collectGarbage;
private cleanSegmentsStorage;
private now;
}
interface Settings {
export interface SegmentsStorage {
storeSegment(segment: Segment): Promise<void>;
getSegmentsMap(masterSwarmId: string): Promise<Map<string, {
segment: Segment;
}>>;
getSegment(id: string, masterSwarmId: string): Promise<Segment | undefined>;
clean(masterSwarmId: string, lockedSementsfilter?: (id: string) => boolean): Promise<boolean>;
destroy(): Promise<void>;
}
export declare type SegmentValidatorCallback = (segment: Segment, method: "http" | "p2p", peerId?: string) => Promise<void>;
export declare type XhrSetupCallback = (xhr: XMLHttpRequest, url: string) => void;
export declare type SegmentUrlBuilder = (segment: Segment) => string;
export interface HybridLoaderSettings {
/**

@@ -112,2 +124,7 @@ * Segment lifetime in cache. The segment is deleted from the cache if the last access time is greater than this value (in milliseconds).

/**
* Use HTTP ranges requests where it is possible.
* Allows to continue (and not start over) aborted P2P downloads over HTTP.
*/
httpUseRanges: boolean;
/**
* If initial HTTP download timeout is enabled (see httpDownloadInitialTimeout)

@@ -139,2 +156,6 @@ * this parameter sets additional timeout for a single sequential segment download

/**
* Number of requested peers in each announce for each tracker. Maximum is 10.
*/
peerRequestsPerAnnounce: number;
/**
* An RTCConfiguration dictionary providing options to configure WebRTC connections.

@@ -155,3 +176,7 @@ */

segmentUrlBuilder?: SegmentUrlBuilder;
/**
* A storage for the downloaded segments.
* By default the segments are stored in JavaScript memory.
*/
segmentsStorage?: SegmentsStorage;
}
export {};

@@ -17,2 +17,10 @@ "use strict";

*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });

@@ -25,4 +33,4 @@ const Debug = require("debug");

const media_peer_1 = require("./media-peer");
const segment_internal_1 = require("./segment-internal");
const speed_approximator_1 = require("./speed-approximator");
const bandwidth_approximator_1 = require("./bandwidth-approximator");
const segments_memory_storage_1 = require("./segments-memory-storage");
const getBrowserRTC = require("get-browser-rtc");

@@ -37,4 +45,4 @@ const Peer = require("simple-peer");

simultaneousHttpDownloads: 2,
httpDownloadProbability: 0.06,
httpDownloadProbabilityInterval: 500,
httpDownloadProbability: 0.1,
httpDownloadProbabilityInterval: 1000,
httpDownloadProbabilitySkipIfNoPeers: false,

@@ -45,2 +53,3 @@ httpFailedSegmentTimeout: 10000,

httpDownloadInitialTimeoutPerSegment: 4000,
httpUseRanges: false,
simultaneousP2PDownloads: 3,

@@ -50,3 +59,4 @@ p2pDownloadMaxPriority: 20,

webRtcMaxMessageSize: 64 * 1024 - 1,
trackerAnnounce: ["wss://tracker.novage.com.ua", "wss://tracker.btorrent.xyz", "wss://tracker.openwebtorrent.com", "wss://tracker.fastcast.nz"],
trackerAnnounce: ["wss://tracker.novage.com.ua", "wss://tracker.openwebtorrent.com"],
peerRequestsPerAnnounce: 10,
rtcConfig: Peer.config

@@ -59,13 +69,14 @@ };

this.debugSegments = Debug("p2pml:hybrid-loader-segments");
this.segments = new Map();
this.segmentsQueue = [];
this.speedApproximator = new speed_approximator_1.SpeedApproximator();
this.bandwidthApproximator = new bandwidth_approximator_1.BandwidthApproximator();
this.httpDownloadInitialTimeoutTimestamp = -Infinity;
this.initialDownloadedViaP2PSegmentsCount = 0;
this.processInitialSegmentTimeout = () => {
this.processInitialSegmentTimeout = () => __awaiter(this, void 0, void 0, function* () {
if (this.httpRandomDownloadInterval === undefined) {
return; // Instance destroyed
}
if (this.processSegmentsQueue() && !this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap());
if (this.masterSwarmId !== undefined) {
const storageSegments = yield this.segmentsStorage.getSegmentsMap(this.masterSwarmId);
if (this.processSegmentsQueue(storageSegments) && !this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments));
}
}

@@ -76,8 +87,7 @@ if (this.httpDownloadInitialTimeoutTimestamp !== -Infinity) {

}
};
this.downloadRandomSegmentOverHttp = () => {
if (this.httpRandomDownloadInterval === undefined) {
return; // Instance destroyed
}
if (this.httpDownloadInitialTimeoutTimestamp !== -Infinity ||
});
this.downloadRandomSegmentOverHttp = () => __awaiter(this, void 0, void 0, function* () {
if (this.masterSwarmId === undefined ||
this.httpRandomDownloadInterval === undefined ||
this.httpDownloadInitialTimeoutTimestamp !== -Infinity ||
this.httpManager.getActiveDownloadsCount() >= this.settings.simultaneousHttpDownloads ||

@@ -88,9 +98,10 @@ (this.settings.httpDownloadProbabilitySkipIfNoPeers && this.p2pManager.getPeers().size === 0) ||

}
const storageSegments = yield this.segmentsStorage.getSegmentsMap(this.masterSwarmId);
const segmentsMap = this.p2pManager.getOvrallSegmentsMap();
const pendingQueue = this.segmentsQueue.filter(segment => !this.segments.has(segment.id) &&
!this.p2pManager.isDownloading(segment) &&
!this.httpManager.isDownloading(segment) &&
!segmentsMap.has(segment.id) &&
!this.httpManager.isFailed(segment) &&
(segment.priority <= this.settings.httpDownloadMaxPriority));
const pendingQueue = this.segmentsQueue.filter(s => !this.p2pManager.isDownloading(s) &&
!this.httpManager.isDownloading(s) &&
!segmentsMap.has(s.id) &&
!this.httpManager.isFailed(s) &&
(s.priority <= this.settings.httpDownloadMaxPriority) &&
!storageSegments.has(s.id));
if (pendingQueue.length == 0) {

@@ -105,53 +116,47 @@ return;

this.httpManager.download(segment);
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap());
};
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments));
});
this.onPieceBytesDownloaded = (method, bytes, peerId) => {
this.speedApproximator.addBytes(bytes, this.now());
this.bandwidthApproximator.addBytes(bytes, this.now());
this.emit(loader_interface_1.Events.PieceBytesDownloaded, method, bytes, peerId);
};
this.onPieceBytesUploaded = (method, bytes, peerId) => {
this.speedApproximator.addBytes(bytes, this.now());
this.emit(loader_interface_1.Events.PieceBytesUploaded, method, bytes, peerId);
};
this.onSegmentLoaded = (segment, data, peerId) => {
this.onSegmentLoaded = (segment, data, peerId) => __awaiter(this, void 0, void 0, function* () {
this.debugSegments("segment loaded", segment.id, segment.url);
const segmentInternal = new segment_internal_1.SegmentInternal(segment.id, segment.url, segment.range, segment.priority, data, this.speedApproximator.getSpeed(this.now()));
this.segments.set(segment.id, segmentInternal);
this.emitSegmentLoaded(segmentInternal, peerId);
if (this.httpDownloadInitialTimeoutTimestamp !== -Infinity) {
// If initial HTTP download timeout enabled then
// count sequential P2P segment downloads
let loadedSegmentFound = false;
for (const queueSegment of this.segmentsQueue) {
if (queueSegment.id === segment.id) {
loadedSegmentFound = true;
}
else if (!this.segments.has(queueSegment.id)) {
break;
}
if (loadedSegmentFound) {
this.initialDownloadedViaP2PSegmentsCount++;
}
}
if (this.masterSwarmId === undefined) {
return;
}
this.processSegmentsQueue();
segment.data = data;
segment.downloadBandwidth = this.bandwidthApproximator.getBandwidth(this.now());
yield this.segmentsStorage.storeSegment(segment);
this.emit(loader_interface_1.Events.SegmentLoaded, segment, peerId);
let storageSegments;
storageSegments = (storageSegments === undefined ? yield this.segmentsStorage.getSegmentsMap(this.masterSwarmId) : storageSegments);
this.processSegmentsQueue(storageSegments);
if (!this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap());
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments));
}
};
this.onSegmentError = (segment, details, peerId) => {
});
this.onSegmentError = (segment, details, peerId) => __awaiter(this, void 0, void 0, function* () {
this.debugSegments("segment error", segment.id, segment.url, peerId, details);
this.emit(loader_interface_1.Events.SegmentError, segment, details, peerId);
this.processSegmentsQueue();
};
this.onPeerConnect = (peer) => {
if (!this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMap(peer.id, this.createSegmentsMap());
if (this.masterSwarmId !== undefined) {
const storageSegments = yield this.segmentsStorage.getSegmentsMap(this.masterSwarmId);
if (this.processSegmentsQueue(storageSegments) && !this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments));
}
}
});
this.onPeerConnect = (peer) => __awaiter(this, void 0, void 0, function* () {
this.emit(loader_interface_1.Events.PeerConnect, peer);
};
if (!this.settings.consumeOnly && this.masterSwarmId !== undefined) {
this.p2pManager.sendSegmentsMap(peer.id, this.createSegmentsMap(yield this.segmentsStorage.getSegmentsMap(this.masterSwarmId)));
}
});
this.onPeerClose = (peerId) => {
this.emit(loader_interface_1.Events.PeerClose, peerId);
};
this.onTrackerUpdate = (data) => {
this.onTrackerUpdate = (data) => __awaiter(this, void 0, void 0, function* () {
if (this.httpDownloadInitialTimeoutTimestamp !== -Infinity &&

@@ -161,7 +166,10 @@ data.incomplete !== undefined && data.incomplete <= 1) {

this.httpDownloadInitialTimeoutTimestamp = -Infinity;
if (this.processSegmentsQueue() && !this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap());
if (this.masterSwarmId !== undefined) {
const storageSegments = yield this.segmentsStorage.getSegmentsMap(this.masterSwarmId);
if (this.processSegmentsQueue(storageSegments) && !this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments));
}
}
}
};
});
this.settings = Object.assign({}, defaultSettings, settings);

@@ -177,2 +185,5 @@ if (settings.bufferedSegmentsCount) {

}
this.segmentsStorage = (this.settings.segmentsStorage === undefined
? new segments_memory_storage_1.SegmentsMemoryStorage(this.settings)
: this.settings.segmentsStorage);
this.debug("loader settings", this.settings);

@@ -186,7 +197,11 @@ this.httpManager = this.createHttpManager();

this.p2pManager.on("segment-error", this.onSegmentError);
this.p2pManager.on("peer-data-updated", () => {
if (this.processSegmentsQueue() && !this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap());
this.p2pManager.on("peer-data-updated", () => __awaiter(this, void 0, void 0, function* () {
if (this.masterSwarmId === undefined) {
return;
}
});
const storageSegments = yield this.segmentsStorage.getSegmentsMap(this.masterSwarmId);
if (this.processSegmentsQueue(storageSegments) && !this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments));
}
}));
this.p2pManager.on("bytes-downloaded", (bytes, peerId) => this.onPieceBytesDownloaded("p2p", bytes, peerId));

@@ -206,50 +221,65 @@ this.p2pManager.on("bytes-uploaded", (bytes, peerId) => this.onPieceBytesUploaded("p2p", bytes, peerId));

createP2PManager() {
return new p2p_media_manager_1.P2PMediaManager(this.segments, this.settings);
return new p2p_media_manager_1.P2PMediaManager(this.segmentsStorage, this.settings);
}
load(segments, swarmId) {
if (this.httpRandomDownloadInterval === undefined) { // Do once on first call
this.httpRandomDownloadInterval = setInterval(this.downloadRandomSegmentOverHttp, this.settings.httpDownloadProbabilityInterval);
if (this.settings.httpDownloadInitialTimeout > 0 && this.settings.httpDownloadInitialTimeoutPerSegment > 0) {
// Initialize initial HTTP download timeout (i.e. download initial segments over P2P)
this.debugSegments("enable initial HTTP download timeout", this.settings.httpDownloadInitialTimeout, "per segment", this.settings.httpDownloadInitialTimeoutPerSegment);
this.httpDownloadInitialTimeoutTimestamp = this.now();
setTimeout(this.processInitialSegmentTimeout, this.settings.httpDownloadInitialTimeoutPerSegment + 100);
load(segments, streamSwarmId) {
return __awaiter(this, void 0, void 0, function* () {
if (this.httpRandomDownloadInterval === undefined) { // Do once on first call
this.httpRandomDownloadInterval = setInterval(this.downloadRandomSegmentOverHttp, this.settings.httpDownloadProbabilityInterval);
if (this.settings.httpDownloadInitialTimeout > 0 && this.settings.httpDownloadInitialTimeoutPerSegment > 0) {
// Initialize initial HTTP download timeout (i.e. download initial segments over P2P)
this.debugSegments("enable initial HTTP download timeout", this.settings.httpDownloadInitialTimeout, "per segment", this.settings.httpDownloadInitialTimeoutPerSegment);
this.httpDownloadInitialTimeoutTimestamp = this.now();
setTimeout(this.processInitialSegmentTimeout, this.settings.httpDownloadInitialTimeoutPerSegment + 100);
}
}
}
this.p2pManager.setSwarmId(swarmId);
this.debug("load segments");
let updateSegmentsMap = false;
// stop all http requests and p2p downloads for segments that are not in the new load
for (const segment of this.segmentsQueue) {
if (!segments.find(f => f.url == segment.url)) {
this.debug("remove segment", segment.url);
if (this.httpManager.isDownloading(segment)) {
updateSegmentsMap = true;
this.httpManager.abort(segment);
if (segments.length > 0) {
this.masterSwarmId = segments[0].masterSwarmId;
}
if (this.masterSwarmId !== undefined) {
this.p2pManager.setStreamSwarmId(streamSwarmId, this.masterSwarmId);
}
this.debug("load segments");
let updateSegmentsMap = false;
// stop all http requests and p2p downloads for segments that are not in the new load
for (const segment of this.segmentsQueue) {
if (!segments.find(f => f.url == segment.url)) {
this.debug("remove segment", segment.url);
if (this.httpManager.isDownloading(segment)) {
updateSegmentsMap = true;
this.httpManager.abort(segment);
}
else {
this.p2pManager.abort(segment);
}
this.emit(loader_interface_1.Events.SegmentAbort, segment);
}
else {
this.p2pManager.abort(segment);
}
if (this.debug.enabled) {
for (const segment of segments) {
if (!this.segmentsQueue.find(f => f.url == segment.url)) {
this.debug("add segment", segment.url);
}
}
this.emit(loader_interface_1.Events.SegmentAbort, segment);
}
}
for (const segment of segments) {
if (!this.segmentsQueue.find(f => f.url == segment.url)) {
this.debug("add segment", segment.url);
this.segmentsQueue = segments;
if (this.masterSwarmId === undefined) {
return;
}
}
this.segmentsQueue = segments;
updateSegmentsMap = this.processSegmentsQueue() || updateSegmentsMap;
updateSegmentsMap = this.collectGarbage() || updateSegmentsMap;
if (updateSegmentsMap && !this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap());
}
let storageSegments = yield this.segmentsStorage.getSegmentsMap(this.masterSwarmId);
updateSegmentsMap = (this.processSegmentsQueue(storageSegments) || updateSegmentsMap);
if (yield this.cleanSegmentsStorage()) {
storageSegments = yield this.segmentsStorage.getSegmentsMap(this.masterSwarmId);
updateSegmentsMap = true;
}
if (updateSegmentsMap && !this.settings.consumeOnly) {
this.p2pManager.sendSegmentsMapToAll(this.createSegmentsMap(storageSegments));
}
});
}
getSegment(id) {
const segment = this.segments.get(id);
return segment
? segment.data
? new loader_interface_1.Segment(segment.id, segment.url, segment.range, segment.priority, segment.data, segment.downloadSpeed)
: undefined
: undefined;
return __awaiter(this, void 0, void 0, function* () {
return this.masterSwarmId === undefined
? undefined
: this.segmentsStorage.getSegment(id, this.masterSwarmId);
});
}

@@ -265,15 +295,20 @@ getSettings() {

destroy() {
if (this.httpRandomDownloadInterval !== undefined) {
clearInterval(this.httpRandomDownloadInterval);
this.httpRandomDownloadInterval = undefined;
}
this.initialDownloadedViaP2PSegmentsCount = 0;
this.httpDownloadInitialTimeoutTimestamp = -Infinity;
this.segmentsQueue = [];
this.httpManager.destroy();
this.p2pManager.destroy();
this.segments.clear();
return __awaiter(this, void 0, void 0, function* () {
if (this.httpRandomDownloadInterval !== undefined) {
clearInterval(this.httpRandomDownloadInterval);
this.httpRandomDownloadInterval = undefined;
}
this.httpDownloadInitialTimeoutTimestamp = -Infinity;
this.segmentsQueue = [];
this.httpManager.destroy();
this.p2pManager.destroy();
this.masterSwarmId = undefined;
yield this.segmentsStorage.destroy();
});
}
processSegmentsQueue() {
processSegmentsQueue(storageSegments) {
this.debugSegments("process segments queue. priority", this.segmentsQueue.length > 0 ? this.segmentsQueue[0].priority : 0);
if (this.masterSwarmId === undefined || this.segmentsQueue.length === 0) {
return false;
}
let updateSegmentsMap = false;

@@ -283,6 +318,12 @@ let segmentsMap;

if (this.httpDownloadInitialTimeoutTimestamp !== -Infinity) {
let firstNotDownloadePriority;
for (const segment of this.segmentsQueue) {
if (!storageSegments.has(segment.id)) {
firstNotDownloadePriority = segment.priority;
break;
}
}
const httpTimeout = this.now() - this.httpDownloadInitialTimeoutTimestamp;
httpAllowed =
(httpTimeout >= (this.initialDownloadedViaP2PSegmentsCount + 1) * this.settings.httpDownloadInitialTimeoutPerSegment) ||
(httpTimeout >= this.settings.httpDownloadInitialTimeout);
httpAllowed = (httpTimeout >= this.settings.httpDownloadInitialTimeout)
|| ((firstNotDownloadePriority !== undefined) && (httpTimeout > this.settings.httpDownloadInitialTimeoutPerSegment) && (firstNotDownloadePriority <= 0));
if (httpAllowed) {

@@ -295,3 +336,3 @@ this.debugSegments("cancel initial HTTP download timeout - timed out");

const segment = this.segmentsQueue[index];
if (this.segments.has(segment.id) || this.httpManager.isDownloading(segment)) {
if (storageSegments.has(segment.id) || this.httpManager.isDownloading(segment)) {
continue;

@@ -314,4 +355,4 @@ }

// Abort P2P download of the required segment if any and force HTTP download
this.p2pManager.abort(segment);
this.httpManager.download(segment);
const downloadedPieces = this.p2pManager.abort(segment);
this.httpManager.download(segment, downloadedPieces);
this.debugSegments("HTTP download (priority)", segment.priority, segment.url);

@@ -358,60 +399,34 @@ updateSegmentsMap = true;

}
emitSegmentLoaded(segmentInternal, peerId) {
segmentInternal.lastAccessed = this.now();
const segment = new loader_interface_1.Segment(segmentInternal.id, segmentInternal.url, segmentInternal.range, segmentInternal.priority, segmentInternal.data, segmentInternal.downloadSpeed);
this.emit(loader_interface_1.Events.SegmentLoaded, segment, peerId);
getStreamSwarmId(segment) {
return segment.streamId === undefined ? segment.masterSwarmId : `${segment.masterSwarmId}+${segment.streamId}`;
}
createSegmentsMap() {
const segmentsMap = new Map();
function addSegmentToMap(swarmWithSegmentId, status) {
// For now we rely on common format of segment ID = swarm ID + segment ID
// TODO: in next major relese segment should contain swarm ID and segment ID in the swarm fields.
const separatorIndex = swarmWithSegmentId.lastIndexOf("+");
const swarmId = swarmWithSegmentId.substring(0, separatorIndex);
const segmentId = swarmWithSegmentId.substring(separatorIndex + 1);
let segmentsStatuses = segmentsMap.get(swarmId);
if (!segmentsStatuses) {
segmentsStatuses = [[], []];
segmentsMap.set(swarmId, segmentsStatuses);
createSegmentsMap(storageSegments) {
const segmentsMap = {};
const addSegmentToMap = (segment, status) => {
const streamSwarmId = this.getStreamSwarmId(segment);
const segmentId = segment.sequence;
let segmentsIdsAndStatuses = segmentsMap[streamSwarmId];
if (segmentsIdsAndStatuses === undefined) {
segmentsIdsAndStatuses = ["", []];
segmentsMap[streamSwarmId] = segmentsIdsAndStatuses;
}
segmentsStatuses[0].push(segmentId);
segmentsStatuses[1].push(status);
const segmentsStatuses = segmentsIdsAndStatuses[1];
segmentsIdsAndStatuses[0] += ((segmentsStatuses.length == 0) ? segmentId : `|${segmentId}`);
segmentsStatuses.push(status);
};
for (const storageSegment of storageSegments.values()) {
addSegmentToMap(storageSegment.segment, media_peer_1.MediaPeerSegmentStatus.Loaded);
}
for (const segmentId of this.segments.keys()) {
addSegmentToMap(segmentId, media_peer_1.MediaPeerSegmentStatus.Loaded);
for (const download of this.httpManager.getActiveDownloads().values()) {
addSegmentToMap(download.segment, media_peer_1.MediaPeerSegmentStatus.LoadingByHttp);
}
for (const segmentId of this.httpManager.getActiveDownloadsKeys()) {
addSegmentToMap(segmentId, media_peer_1.MediaPeerSegmentStatus.LoadingByHttp);
}
return segmentsMap;
}
collectGarbage() {
const segmentsToDelete = [];
const remainingSegments = [];
// Delete old segments
const now = this.now();
for (const segment of this.segments.values()) {
if (now - segment.lastAccessed > this.settings.cachedSegmentExpiration) {
segmentsToDelete.push(segment.id);
cleanSegmentsStorage() {
return __awaiter(this, void 0, void 0, function* () {
if (this.masterSwarmId === undefined) {
return false;
}
else {
remainingSegments.push(segment);
}
}
// Delete segments over cached count
let countOverhead = remainingSegments.length - this.settings.cachedSegmentsCount;
if (countOverhead > 0) {
remainingSegments.sort((a, b) => a.lastAccessed - b.lastAccessed);
for (const segment of remainingSegments) {
if (!this.segmentsQueue.find(queueSegment => queueSegment.id == segment.id)) {
segmentsToDelete.push(segment.id);
countOverhead--;
if (countOverhead == 0) {
break;
}
}
}
}
segmentsToDelete.forEach(id => this.segments.delete(id));
return segmentsToDelete.length > 0;
return this.segmentsStorage.clean(this.masterSwarmId, (id) => this.segmentsQueue.find(queueSegment => queueSegment.id === id) !== undefined);
});
}

@@ -421,3 +436,3 @@ now() {

}
} // end of HybridLoader
exports.default = HybridLoader;
}
exports.HybridLoader = HybridLoader;

@@ -17,4 +17,4 @@ /**

*/
export { LoaderInterface, Events, Segment, XhrSetupCallback } from "./loader-interface";
export { default as HybridLoader } from "./hybrid-loader";
export declare const version: string;
export declare const version = "0.5.0";
export * from "./loader-interface";
export * from "./hybrid-loader";

@@ -18,8 +18,8 @@ "use strict";

*/
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
var loader_interface_1 = require("./loader-interface");
exports.Events = loader_interface_1.Events;
exports.Segment = loader_interface_1.Segment;
var hybrid_loader_1 = require("./hybrid-loader");
exports.HybridLoader = hybrid_loader_1.default;
exports.version = typeof (__P2PML_VERSION__) === "undefined" ? "__VERSION__" : __P2PML_VERSION__;
exports.version = "0.5.0";
__export(require("./loader-interface"));
__export(require("./hybrid-loader"));

@@ -19,7 +19,11 @@ /**

readonly url: string;
readonly masterSwarmId: string;
readonly masterManifestUri: string;
readonly streamId: string | undefined;
readonly sequence: string;
readonly range?: string | undefined;
readonly priority: number;
readonly data?: ArrayBuffer | undefined;
readonly downloadSpeed: number;
constructor(id: string, url: string, range?: string | undefined, priority?: number, data?: ArrayBuffer | undefined, downloadSpeed?: number);
data?: ArrayBuffer | undefined;
downloadBandwidth: number;
constructor(id: string, url: string, masterSwarmId: string, masterManifestUri: string, streamId: string | undefined, sequence: string, range?: string | undefined, priority?: number, data?: ArrayBuffer | undefined, downloadBandwidth?: number);
}

@@ -64,17 +68,8 @@ export declare enum Events {

export interface LoaderInterface {
on(eventName: string, listener: Function): this;
load(segments: Segment[], swarmId: string): void;
getSegment(id: string): Segment | undefined;
on(eventName: string, listener: (...params: any[]) => void): this;
load(segments: Segment[], streamSwarmId: string): void;
getSegment(id: string): Promise<Segment | undefined>;
getSettings(): any;
getDetails(): any;
destroy(): void;
destroy(): Promise<void>;
}
export interface SegmentValidatorCallback {
(segment: Segment, method: "http" | "p2p", peerId?: string): Promise<void>;
}
export interface XhrSetupCallback {
(xhr: XMLHttpRequest, url: string): void;
}
export interface SegmentUrlBuilder {
(segment: Segment): string;
}

@@ -19,9 +19,13 @@ "use strict";

class Segment {
constructor(id, url, range, priority = 0, data, downloadSpeed = 0) {
constructor(id, url, masterSwarmId, masterManifestUri, streamId, sequence, range, priority = 0, data, downloadBandwidth = 0) {
this.id = id;
this.url = url;
this.masterSwarmId = masterSwarmId;
this.masterManifestUri = masterManifestUri;
this.streamId = streamId;
this.sequence = sequence;
this.range = range;
this.priority = priority;
this.data = data;
this.downloadSpeed = downloadSpeed;
this.downloadBandwidth = downloadBandwidth;
}

@@ -28,0 +32,0 @@ }

@@ -16,3 +16,3 @@ /**

*/
import STEEmitter from "./stringly-typed-event-emitter";
import { STEEmitter } from "./stringly-typed-event-emitter";
export declare enum MediaPeerSegmentStatus {

@@ -50,7 +50,9 @@ Loaded = 0,

getSegmentsMap(): Map<string, MediaPeerSegmentStatus>;
sendSegmentsMap(segments: Map<string, [string[], MediaPeerSegmentStatus[]]>): void;
sendSegmentsMap(segmentsMap: {
[key: string]: [string, number[]];
}): void;
sendSegmentData(segmentId: string, data: ArrayBuffer): void;
sendSegmentAbsent(segmentId: string): void;
requestSegment(segmentId: string): void;
cancelSegmentRequest(): void;
cancelSegmentRequest(): ArrayBuffer[] | undefined;
private runResponseTimeoutTimer;

@@ -57,0 +59,0 @@ private cancelResponseTimeoutTimer;

@@ -42,3 +42,3 @@ "use strict";

}
class MediaPeer extends stringly_typed_event_emitter_1.default {
class MediaPeer extends stringly_typed_event_emitter_1.STEEmitter {
constructor(peer, settings) {

@@ -159,4 +159,4 @@ super();

const segmentsMap = new Map();
for (const swarmId of Object.keys(segments)) {
const swarmData = segments[swarmId];
for (const streamSwarmId of Object.keys(segments)) {
const swarmData = segments[streamSwarmId];
if (!(swarmData instanceof Array) ||

@@ -178,3 +178,3 @@ (swarmData.length !== 2) ||

}
segmentsMap.set(`${swarmId}+${segmentsIds[i]}`, segmentStatus);
segmentsMap.set(`${streamSwarmId}+${segmentsIds[i]}`, segmentStatus);
}

@@ -199,10 +199,4 @@ }

}
sendSegmentsMap(segments) {
const segmentMap = {};
for (const [swarmId, segmentsIdsAndStatuses] of segments) {
const segmentsIds = segmentsIdsAndStatuses[0].join("|");
const segmentsStatuses = segmentsIdsAndStatuses[1];
segmentMap[swarmId] = [segmentsIds, segmentsStatuses];
}
this.sendCommand({ c: MediaPeerCommands.SegmentsMap, m: segmentMap });
sendSegmentsMap(segmentsMap) {
this.sendCommand({ c: MediaPeerCommands.SegmentsMap, m: segmentsMap });
}

@@ -236,7 +230,10 @@ sendSegmentData(segmentId, data) {

cancelSegmentRequest() {
let downloadingSegment;
if (this.downloadingSegmentId) {
const segmentId = this.downloadingSegmentId;
downloadingSegment = this.downloadingSegment ? this.downloadingSegment.pieces : undefined;
this.terminateSegmentRequest();
this.sendCommand({ c: MediaPeerCommands.CancelSegmentRequest, i: segmentId });
}
return downloadingSegment;
}

@@ -243,0 +240,0 @@ runResponseTimeoutTimer() {

@@ -16,16 +16,9 @@ /**

*/
import STEEmitter from "./stringly-typed-event-emitter";
import { Segment, SegmentValidatorCallback } from "./loader-interface";
import { STEEmitter } from "./stringly-typed-event-emitter";
import { Segment } from "./loader-interface";
import { MediaPeer, MediaPeerSegmentStatus } from "./media-peer";
import { SegmentInternal } from "./segment-internal";
import { SegmentsStorage, SegmentValidatorCallback } from "./hybrid-loader";
export declare class P2PMediaManager extends STEEmitter<"peer-connected" | "peer-closed" | "peer-data-updated" | "segment-loaded" | "segment-error" | "bytes-downloaded" | "bytes-uploaded" | "tracker-update"> {
readonly cachedSegments: Map<string, SegmentInternal>;
readonly settings: {
useP2P: boolean;
trackerAnnounce: string[];
p2pSegmentDownloadTimeout: number;
segmentValidator?: SegmentValidatorCallback;
webRtcMaxMessageSize: number;
rtcConfig?: RTCConfiguration;
};
private sementsStorage;
private settings;
private trackerClient;

@@ -35,7 +28,8 @@ private peers;

private peerSegmentRequests;
private swarmId;
private streamSwarmId;
private readonly peerId;
private debug;
private pendingTrackerClient;
constructor(cachedSegments: Map<string, SegmentInternal>, settings: {
private masterSwarmId?;
constructor(sementsStorage: SegmentsStorage, settings: {
useP2P: boolean;

@@ -47,6 +41,7 @@ trackerAnnounce: string[];

rtcConfig?: RTCConfiguration;
peerRequestsPerAnnounce: number;
});
getPeers(): Map<string, MediaPeer>;
getPeerId(): string;
setSwarmId(swarmId: string): Promise<void>;
setStreamSwarmId(streamSwarmId: string, masterSwarmId: string): Promise<void>;
private createClient;

@@ -58,8 +53,12 @@ private onTrackerError;

download(segment: Segment): boolean;
abort(segment: Segment): void;
abort(segment: Segment): ArrayBuffer[] | undefined;
isDownloading(segment: Segment): boolean;
getActiveDownloadsCount(): number;
destroy(swarmChange?: boolean): void;
sendSegmentsMapToAll(segmentsMap: Map<string, [string[], MediaPeerSegmentStatus[]]>): void;
sendSegmentsMap(peerId: string, segmentsMap: Map<string, [string[], MediaPeerSegmentStatus[]]>): void;
sendSegmentsMapToAll(segmentsMap: {
[key: string]: [string, number[]];
}): void;
sendSegmentsMap(peerId: string, segmentsMap: {
[key: string]: [string, number[]];
}): void;
getOvrallSegmentsMap(): Map<string, MediaPeerSegmentStatus>;

@@ -66,0 +65,0 @@ private onPieceBytesDownloaded;

@@ -35,2 +35,4 @@ "use strict";

const PEER_PROTOCOL_VERSION = 2;
const PEER_ID_VERSION_STRING = index_1.version.replace(/\d*./g, v => `0${parseInt(v, 10) % 100}`.slice(-2)).slice(0, 4);
const PEER_ID_VERSION_PREFIX = `-WW${PEER_ID_VERSION_STRING}-`; // Using WebTorrent client ID in order to not be banned by websocket trackers
class PeerSegmentRequest {

@@ -43,5 +45,2 @@ constructor(peerId, segment) {

function generatePeerId() {
const PEER_ID_VERSION_STRING = index_1.version.replace(/\d*./g, v => `0${parseInt(v, 10) % 100}`.slice(-2)).slice(0, 4);
// Using WebTorrent client ID in order to not be banned by websocket trackers
const PEER_ID_VERSION_PREFIX = `-WW${PEER_ID_VERSION_STRING}-`;
const PEER_ID_SYMBOLS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

@@ -55,6 +54,6 @@ const PEER_ID_LENGTH = 20;

}
class P2PMediaManager extends stringly_typed_event_emitter_1.default {
constructor(cachedSegments, settings) {
class P2PMediaManager extends stringly_typed_event_emitter_1.STEEmitter {
constructor(sementsStorage, settings) {
super();
this.cachedSegments = cachedSegments;
this.sementsStorage = sementsStorage;
this.settings = settings;

@@ -65,3 +64,3 @@ this.trackerClient = null;

this.peerSegmentRequests = new Map();
this.swarmId = null;
this.streamSwarmId = null;
this.debug = Debug("p2pml:p2p-media-manager");

@@ -159,4 +158,7 @@ this.pendingTrackerClient = null;

};
this.onSegmentRequest = (peer, segmentId) => {
const segment = this.cachedSegments.get(segmentId);
this.onSegmentRequest = (peer, segmentId) => __awaiter(this, void 0, void 0, function* () {
if (this.masterSwarmId === undefined) {
return;
}
const segment = yield this.sementsStorage.getSegment(segmentId, this.masterSwarmId);
if (segment) {

@@ -168,3 +170,3 @@ peer.sendSegmentData(segmentId, segment.data);

}
};
});
this.onSegmentLoaded = (peer, segmentId, data) => __awaiter(this, void 0, void 0, function* () {

@@ -178,3 +180,3 @@ const peerSegmentRequest = this.peerSegmentRequests.get(segmentId);

try {
yield this.settings.segmentValidator(new loader_interface_1.Segment(segment.id, segment.url, segment.range, segment.priority, data), "p2p", peer.id);
yield this.settings.segmentValidator(new loader_interface_1.Segment(segment.id, segment.url, segment.masterSwarmId, segment.masterManifestUri, segment.streamId, segment.sequence, segment.range, segment.priority, data), "p2p", peer.id);
}

@@ -224,10 +226,11 @@ catch (error) {

}
setSwarmId(swarmId) {
setStreamSwarmId(streamSwarmId, masterSwarmId) {
return __awaiter(this, void 0, void 0, function* () {
if (this.swarmId === swarmId) {
if (this.streamSwarmId === streamSwarmId) {
return;
}
this.destroy(true);
this.swarmId = swarmId;
this.debug("swarm ID", this.swarmId);
this.streamSwarmId = streamSwarmId;
this.masterSwarmId = masterSwarmId;
this.debug("stream swarm ID", this.streamSwarmId);
this.pendingTrackerClient = {

@@ -239,4 +242,4 @@ isDestroyed: false

// TODO: Edge doesn't support SHA-1. Change to SHA-256 once Edge support is required.
// const infoHash = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(PEER_PROTOCOL_VERSION + this.swarmId));
const infoHash = new sha1().update(PEER_PROTOCOL_VERSION + this.swarmId).digest();
// const infoHash = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(PEER_PROTOCOL_VERSION + this.streamSwarmId));
const infoHash = new sha1().update(PEER_PROTOCOL_VERSION + this.streamSwarmId).digest();
// destroy may be called while waiting for the hash to be calculated

@@ -262,3 +265,6 @@ if (!pendingTrackerClient.isDestroyed) {

rtcConfig: this.settings.rtcConfig,
port: 6881 // a dummy value allows running in Node.js environment
port: 6881,
getAnnounceOpts: () => {
return { numwant: this.settings.peerRequestsPerAnnounce };
}
};

@@ -297,2 +303,3 @@ let oldTrackerClient = this.trackerClient;

abort(segment) {
let downloadingSegment;
const peerSegmentRequest = this.peerSegmentRequests.get(segment.id);

@@ -302,6 +309,7 @@ if (peerSegmentRequest) {

if (peer) {
peer.cancelSegmentRequest();
downloadingSegment = peer.cancelSegmentRequest();
}
this.peerSegmentRequests.delete(segment.id);
}
return downloadingSegment;
}

@@ -315,3 +323,3 @@ isDownloading(segment) {

destroy(swarmChange = false) {
this.swarmId = null;
this.streamSwarmId = null;
if (this.trackerClient) {

@@ -318,0 +326,0 @@ this.trackerClient.stop();

@@ -17,5 +17,5 @@ /**

import { EventEmitter } from "events";
export default class<T extends string> extends EventEmitter {
export declare class STEEmitter<T extends string> extends EventEmitter {
on(event: T, listener: (...args: any[]) => void): this;
emit(event: T, ...args: any[]): boolean;
}

@@ -19,6 +19,6 @@ "use strict";

const events_1 = require("events");
class default_1 extends events_1.EventEmitter {
class STEEmitter extends events_1.EventEmitter {
on(event, listener) { return super.on(event, listener); }
emit(event, ...args) { return super.emit(event, ...args); }
}
exports.default = default_1;
exports.STEEmitter = STEEmitter;
{
"name": "p2p-media-loader-core",
"description": "P2P Media Loader core functionality",
"version": "0.5.0",
"version": "0.6.0",
"license": "Apache-2.0",

@@ -40,33 +40,28 @@ "author": "Novage",

"dependencies": {
"bittorrent-tracker": "^9.10.1",
"bittorrent-tracker": "^9.11.0",
"debug": "^4.1.1",
"events": "^3.0.0",
"get-browser-rtc": "^1.0.2",
"sha.js": "^2.4.11"
"sha.js": "^2.4.11",
"simple-peer": "^9.4.0"
},
"devDependencies": {
"@types/assert": "^1.4.2",
"@types/debug": "^4.1.2",
"@types/debug": "^4.1.4",
"@types/events": "^3.0.0",
"@types/mocha": "^5.2.6",
"@types/node": "^11.11.4",
"browserify": "^16.2.3",
"browserify-versionify": "^1.0.6",
"copyfiles": "^2.1.0",
"@types/mocha": "^5.2.7",
"@types/node": "^12.6.8",
"browserify": "^16.3.0",
"copyfiles": "^2.1.1",
"mkdirp": "^0.5.1",
"mocha": "^6.0.2",
"terser": "^3.17.0",
"ts-loader": "^5.3.3",
"mocha": "^6.2.0",
"terser": "^4.1.2",
"ts-loader": "^6.0.4",
"ts-mockito": "^2.3.1",
"ts-node": "^8.0.3",
"tslint": "^5.14.0",
"typescript": "^3.3.4000",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0"
},
"browserify": {
"transform": [
"browserify-versionify"
]
"ts-node": "^8.3.0",
"tslint": "^5.18.0",
"typescript": "^3.5.3",
"webpack": "^4.36.1",
"webpack-cli": "^3.3.6"
}
}

@@ -8,2 +8,3 @@ # P2P Media Loader Core

Useful links:
- [P2P development, support & consulting](https://novage.com.ua/)
- [Demo](http://novage.com.ua/p2p-media-loader/demo.html)

@@ -44,4 +45,4 @@ - [FAQ](https://github.com/Novage/p2p-media-loader/blob/master/FAQ.md)

| --- | ---- | ------ | ------ |
| `cachedSegmentExpiration` | Integer | 300000 | Segment lifetime in cache. The segment is deleted from the cache if the last access time is greater than this value (in milliseconds). Cached segments are shared over P2P network.
| `cachedSegmentsCount` | Integer | 30 | Max number of segments that can be stored in the cache. Cached segments are shared over P2P network.
| `cachedSegmentExpiration` | Integer | 300000 | Segment lifetime in cache. The segment is deleted from the cache if the last access time is greater than this value (in milliseconds). Cached segments are shared over P2P network. Affects only default segments storage.
| `cachedSegmentsCount` | Integer | 30 | Max number of segments that can be stored in the cache. Cached segments are shared over P2P network. Affects only default segments storage.
| `requiredSegmentsPriority` | Integer | 1 | The maximum priority of the segments to be downloaded (if not available) as quickly as possible (i.e. via HTTP method). First segment that should be downloaded has priority 0.

@@ -51,4 +52,4 @@ | `useP2P` | Boolean | true | Enable/Disable peers interaction

| `simultaneousHttpDownloads` | Integer | 2 | Max number of simultaneous downloads from HTTP source
| `httpDownloadProbability` | Float | 0.06 | Probability of downloading remaining not downloaded segment in the segments queue via HTTP
| `httpDownloadProbabilityInterval` | Integer | 500 | Interval of the httpDownloadProbability check (in milliseconds)
| `httpDownloadProbability` | Float | 0.1 | Probability of downloading remaining not downloaded segment in the segments queue via HTTP
| `httpDownloadProbabilityInterval` | Integer | 1000 | Interval of the httpDownloadProbability check (in milliseconds)
| `httpDownloadProbabilitySkipIfNoPeers` | Boolean | false | Don't download segments over HTTP randomly when there is no peers

@@ -59,2 +60,3 @@ | `httpFailedSegmentTimeout` | Integer | 10000 | Timeout before trying to load a segment again via HTTP after failed attempt (in milliseconds)

| `httpDownloadInitialTimeoutPerSegment` | Integer | 4000 | If initial HTTP download timeout is enabled (see `httpDownloadInitialTimeout`) this parameter sets additional timeout for a single sequential segment download over P2P. It will cancel initial HTTP download timeout mode if a segment download is timed out.
| `httpUseRanges` | Boolean | false | Use HTTP ranges requests where it is possible. Allows to continue (and not start over) aborted P2P downloads over HTTP.
| `simultaneousP2PDownloads` | Integer | 3 | Max number of simultaneous downloads from peers

@@ -64,3 +66,4 @@ | `p2pDownloadMaxPriority` | Integer | 20 | Segments with higher priority will not be downloaded over P2P

| `webRtcMaxMessageSize` | Integer | 64 * 1024 - 1 | Max WebRTC message size. 64KiB - 1B should work with most of recent browsers. Set it to 16KiB for older browsers support.
| `trackerAnnounce` | String[] | wss://tracker.btorrent.xyz wss://tracker.openwebtorrent.com wss://tracker.fastcast.nz | Torrent trackers (announcers) to use
| `trackerAnnounce` | String[] | wss://tracker.novage.com.ua wss://tracker.openwebtorrent.com | WebTorrent trackers to use for announcement
| `peerRequestsPerAnnounce` | Integer | 10 | Number of requested peers in each announce for each tracker. Maximum is 10.
| `rtcConfig` | [RTCConfiguration](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection#RTCConfiguration_dictionary) | Object | An [RTCConfiguration](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection#RTCConfiguration_dictionary) dictionary providing options to configure WebRTC connections.

@@ -70,5 +73,17 @@ | `segmentValidator` | Function | undefined | Segment validation callback - validates the data after it has been downloaded.<br><br>Arguments:<br>`segment` (Segment) - The segment object.<br>`method` (String) - Can be "http" or "p2p" only.<br>`peerId` (String) - The ID of the peer that the segment was downloaded from in case it is P2P download; and *undefined* for HTTP donwload.<br><br>Returns:<br>A promise - if resolved the segment considered to be valid, if rejected the error object will be passed to `SegmentError` event.

| `segmentUrlBuilder` | Function | undefined | Allow to modify the segment URL before HTTP request. If handled, expected a function of one argument of type `Segment` that returns a `string` - generated segment URL.
| `segmentsStorage` | Object | undefined | A storage for the downloaded segments. By default the segments are stored in JavaScript memory. Can be used to implement offline plabyack. See [SegmentsStorage](#segmentsstorage-interface) interface for details.
### `loader.load(segments, swarmId)`
### SegmentsStorage interface
```typescript
interface SegmentsStorage {
storeSegment(segment: Segment): Promise<void>;
getSegmentsMap(masterSwarmId: string): Promise<Map<string, {segment: Segment}>>;
getSegment(id: string, masterSwarmId: string): Promise<Segment | undefined>;
clean(lockedSementsfilter?: (id: string) => boolean): Promise<boolean>;
destroy(): Promise<void>;
}
```
### `loader.load(segments, streamSwarmId)`
Creates new queue of segments to download. Aborts all http and peer connections for segments that are not in the new load and emits `Events.SegmentAbort` event for each aborted event.

@@ -78,3 +93,3 @@

- `segments` - array of `Segment` class instances with populated `url` and `priority` field;
- `swarmId` - used for gathering peers in pool;
- `streamSwarmId` - current swarm;

@@ -184,2 +199,14 @@ ### `loader.on(Events.SegmentLoaded, function (segment, peerId) {})`

+ URL of the segment
`masterSwarmId`
+ a `String`
+ segment's master swarm ID
`masterManifestUri`
+ a `String`
+ segment's master manifest URI
`streamId`
+ a `String` or `undefined`
+ segment's stream ID
`sequence`
+ a `String`
+ segment's sequence ID
- `range`

@@ -186,0 +213,0 @@ + a `String`

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc