p2p-media-loader-shaka
Version:
P2P Media Loader Shaka Player integration
705 lines (704 loc) • 25.3 kB
JavaScript
import { Core, Core as Core$1, CoreRequestError, debug, generateStreamShortId } from "p2p-media-loader-core";
//#region src/manifest-parser-decorator.ts
var AUDIO_CODECS = [
"mp4a",
"ac-3",
"ec-3",
"ec+3",
"opus",
"vorb",
"flac"
];
var ManifestParserDecorator = class {
shaka;
originalManifestParser;
debug = debug("p2pml-shaka:manifest-parser");
isHls;
segmentManager;
player;
constructor(shaka, originalManifestParser) {
this.shaka = shaka;
this.originalManifestParser = originalManifestParser;
this.isHls = this.originalManifestParser instanceof shaka.hls.HlsParser;
}
configure(config) {
return this.originalManifestParser.configure(config);
}
banLocation(uri) {
return this.originalManifestParser.banLocation(uri);
}
onInitialVariantChosen(variant) {
return this.originalManifestParser.onInitialVariantChosen(variant);
}
setP2PMediaLoaderData(p2pml) {
if (!p2pml) return;
this.segmentManager = p2pml.segmentManager;
this.player = p2pml.player;
p2pml.streamInfo.protocol = this.isHls ? "hls" : "dash";
}
async start(uri, playerInterface) {
const { p2pml } = playerInterface.networkingEngine;
this.setP2PMediaLoaderData(p2pml);
const manifest = await this.originalManifestParser.start(uri, playerInterface);
if (!p2pml) return manifest;
if (this.isHls) this.hookHlsStreamMediaSequenceTimeMaps(manifest.variants);
this.processStreams(manifest.variants);
return manifest;
}
stop() {
return this.originalManifestParser.stop();
}
update() {
return this.originalManifestParser.update();
}
setMediaElement(mediaElement) {
return this.originalManifestParser.setMediaElement(mediaElement);
}
onExpirationUpdated(sessionId, expiration) {
return this.originalManifestParser.onExpirationUpdated(sessionId, expiration);
}
processStreams(variants) {
const { segmentManager } = this;
if (!segmentManager) return;
const processedStreams = /* @__PURE__ */ new Set();
const processStream = (stream, type, index) => {
this.hookSegmentIndex(stream);
segmentManager.setStream(stream, type, index);
processedStreams.add(stream.id);
return true;
};
for (const variant of variants) {
const { video, audio } = variant;
if (video && !processedStreams.has(video.id)) {
const isMissingMetadata = variant.bandwidth === 0;
const videoCodecs = video.codecs ? video.codecs.split(",").map((c) => c.trim().toLowerCase()).filter((c) => !AUDIO_CODECS.some((p) => c.startsWith(p))).join(",") : void 0;
const { frameRate, hdr: videoRange } = video;
processStream(video, "main", generateStreamShortId({
bitrate: variant.bandwidth,
codecs: isMissingMetadata ? void 0 : videoCodecs,
width: isMissingMetadata ? void 0 : video.width,
height: isMissingMetadata ? void 0 : video.height,
frameRate: isMissingMetadata ? void 0 : frameRate,
videoRange: isMissingMetadata ? void 0 : videoRange
}));
}
if (audio && !processedStreams.has(audio.id)) {
const isMain = !video;
const name = audio.label ?? audio.originalId ?? void 0;
const index = generateStreamShortId({
bitrate: isMain ? variant.bandwidth : 0,
codecs: isMain ? void 0 : audio.codecs,
language: isMain ? void 0 : audio.language,
channels: isMain ? void 0 : audio.channelsCount,
name: isMain ? void 0 : name
});
processStream(audio, isMain ? "main" : "secondary", index);
}
}
}
hookSegmentIndex(stream) {
const { segmentManager } = this;
if (!segmentManager) return;
const substituteSegmentIndexGet = (segmentIndex, callFromCreateSegmentIndexMethod = false) => {
let prevReference = null;
let prevFirstItemReference;
let prevLastItemReference;
const originalGet = segmentIndex.get;
const customGet = (position) => {
const reference = originalGet.call(segmentIndex, position);
if (reference === prevReference || !this.player?.isLive() && stream.isSegmentIndexAlreadyRead) return reference;
prevReference = reference;
segmentIndex.get = originalGet;
try {
const references = getReferencesArray(segmentIndex, this.shaka);
if (!references) throw new Error("Segment references not found");
const firstItemReference = references[0];
const lastItemReference = references[references.length - 1];
if (firstItemReference === prevFirstItemReference && lastItemReference === prevLastItemReference) return reference;
prevFirstItemReference = firstItemReference;
prevLastItemReference = lastItemReference;
segmentManager.updateStreamSegments(stream, references);
stream.isSegmentIndexAlreadyRead = true;
this.debug(`Stream ${stream.id} is updated`);
} catch {} finally {
if (!stream.isSegmentIndexAlreadyRead || !!this.player?.isLive() || !callFromCreateSegmentIndexMethod) segmentIndex.get = customGet;
}
return reference;
};
segmentIndex.get = customGet;
};
if (stream.segmentIndex) {
substituteSegmentIndexGet(stream.segmentIndex);
return;
}
const createSegmentIndexOriginal = stream.createSegmentIndex;
stream.createSegmentIndex = async () => {
const result = await createSegmentIndexOriginal.call(stream);
if (stream.segmentIndex) substituteSegmentIndexGet(stream.segmentIndex, true);
return result;
};
}
hookHlsStreamMediaSequenceTimeMaps(variants) {
const maps = getMapPropertiesFromObject(this.originalManifestParser);
let videoMap;
let audioMap;
const keysToCheck = [
"video",
"audio",
"text",
"image"
];
for (const map of maps) {
if (!keysToCheck.every((key) => map.has(key))) continue;
videoMap = map.get("video");
audioMap = map.get("audio");
}
if (videoMap && audioMap) {
for (const variant of variants) {
const { video: videoStream, audio: audioStream } = variant;
if (videoStream) videoStream.mediaSequenceTimeMap = videoMap;
if (audioStream) audioStream.mediaSequenceTimeMap = audioMap;
}
return;
}
const manifestVariantsMap = maps.find((map) => {
const item = map.values().next().value;
return typeof item === "object" && item?.streams?.createSegmentIndex;
});
if (!manifestVariantsMap) return;
const manifestVariantMapValues = [...manifestVariantsMap.values()];
for (const variant of manifestVariantMapValues) {
if (variant?.stream?.mediaSequenceTimeMap) continue;
const mediaSequenceTimeMap = getMapPropertiesFromObject(variant).find((map) => {
const [key, value] = map.entries().next().value ?? [];
return typeof key === "number" && typeof value === "number";
});
if (!mediaSequenceTimeMap) continue;
variant.stream.mediaSequenceTimeMap = mediaSequenceTimeMap;
}
}
};
var HlsManifestParser = class extends ManifestParserDecorator {
constructor(shaka) {
super(shaka, new shaka.hls.HlsParser());
}
};
var DashManifestParser = class extends ManifestParserDecorator {
constructor(shaka) {
super(shaka, new shaka.dash.DashParser());
}
};
function getReferencesArray(obj, shaka) {
for (const key in obj) if (Array.isArray(obj[key]) && obj[key].length > 0 && obj[key][0] instanceof shaka.media.SegmentReference) return obj[key];
else if (typeof obj[key] === "object") {
const references = getReferencesArray(obj[key], shaka);
if (references) return references;
}
return null;
}
function getMapPropertiesFromObject(object) {
return Object.keys(object).map((key) => object[key]).filter((property) => property instanceof Map);
}
//#endregion
//#region src/stream-utils.ts
function createSegment({ segmentReference, externalId, runtimeId }) {
const { byteRange, url, startTime, endTime } = getSegmentInfoFromReference(segmentReference);
return {
runtimeId: runtimeId ?? getSegmentRuntimeId(url, byteRange),
externalId,
byteRange,
url,
startTime,
endTime
};
}
function getSegmentRuntimeIdFromReference(segmentReference) {
const { url, byteRange } = getSegmentInfoFromReference(segmentReference);
return getSegmentRuntimeId(url, byteRange);
}
function getSegmentRuntimeId(url, byteRange) {
if (!byteRange) return url;
const range = typeof byteRange === "string" ? getByteRangeFromHeaderString(byteRange) : byteRange;
if (!range) return url;
return `${url}|${range.start}-${range.end}`;
}
function getByteRangeFromHeaderString(rangeStr) {
if (!rangeStr?.includes("bytes=")) return void 0;
const parts = rangeStr.split("=")[1].split("-");
const start = parseInt(parts[0]);
const end = parseInt(parts[1]);
if (isNaN(start) || isNaN(end)) return void 0;
return {
start,
end
};
}
function getSegmentInfoFromReference(segmentReference) {
const uris = segmentReference.getUris();
const responseUrl = uris[1] ?? uris[0];
const start = segmentReference.getStartByte();
const end = segmentReference.getEndByte() ?? void 0;
const startTime = segmentReference.getStartTime();
const endTime = segmentReference.getEndTime();
return {
byteRange: end !== void 0 ? {
start,
end
} : void 0,
url: responseUrl,
startTime,
endTime
};
}
function getStreamLastMediaSequence(stream) {
const { shakaStream } = stream;
const map = shakaStream.mediaSequenceTimeMap;
if (!map) return;
const firstMediaSequence = map.keys().next().value;
if (firstMediaSequence === void 0) return;
return firstMediaSequence + map.size - 1;
}
//#endregion
//#region src/segment-manager.ts
var SEGMENT_ID_RESOLUTION_IN_SECONDS = .5;
var SegmentManager = class {
core;
streamInfo;
constructor(streamInfo, core) {
this.core = core;
this.streamInfo = streamInfo;
}
setStream(shakaStream, type, index) {
this.core.addStreamIfNoneExists({
runtimeId: shakaStream.id.toString(),
type,
index,
shakaStream
});
if (shakaStream.segmentIndex) this.updateStreamSegments(shakaStream);
}
updateStreamSegments(shakaStream, segmentReferences) {
const stream = this.core.getStream(shakaStream.id.toString());
if (!stream) return;
const { segmentIndex } = stream.shakaStream;
if (!segmentReferences && segmentIndex) try {
segmentReferences = [...segmentIndex].filter((ref) => !!ref);
} catch {
return;
}
if (!segmentReferences) return;
if (this.streamInfo.protocol === "hls") this.processHlsSegmentReferences(stream, segmentReferences);
else this.processDashSegmentReferences(stream, segmentReferences);
}
processDashSegmentReferences(managerStream, segmentReferences) {
const staleSegmentsIds = new Set(managerStream.segments.keys());
const newSegments = [];
for (const reference of segmentReferences) {
const externalId = Math.trunc(reference.getStartTime() / SEGMENT_ID_RESOLUTION_IN_SECONDS);
const runtimeId = getSegmentRuntimeIdFromReference(reference);
if (!managerStream.segments.has(runtimeId)) {
const segment = createSegment({
segmentReference: reference,
externalId,
runtimeId
});
newSegments.push(segment);
}
staleSegmentsIds.delete(runtimeId);
}
if (!newSegments.length && !staleSegmentsIds.size) return;
this.core.updateStream(managerStream.runtimeId, newSegments, staleSegmentsIds.values());
}
processHlsSegmentReferences(managerStream, segmentReferences) {
const { segments } = managerStream;
const lastMediaSequence = getStreamLastMediaSequence(managerStream);
const newSegments = [];
if (segments.size === 0) {
const firstReferenceMediaSequence = lastMediaSequence === void 0 ? 0 : lastMediaSequence - segmentReferences.length + 1;
for (const [index, reference] of segmentReferences.entries()) {
const segment = createSegment({
segmentReference: reference,
externalId: firstReferenceMediaSequence + index
});
newSegments.push(segment);
}
this.core.updateStream(managerStream.runtimeId, newSegments);
return;
}
if (lastMediaSequence === void 0) return;
let mediaSequence = lastMediaSequence;
for (const reference of itemsBackwards(segmentReferences)) {
const runtimeId = getSegmentRuntimeIdFromReference(reference);
if (segments.has(runtimeId)) break;
const segment = createSegment({
runtimeId,
segmentReference: reference,
externalId: mediaSequence
});
newSegments.push(segment);
mediaSequence--;
}
newSegments.reverse();
const staleSegmentIds = [];
const countToDelete = newSegments.length;
for (const segment of nSegmentsBackwards(segments, countToDelete)) staleSegmentIds.push(segment.runtimeId);
if (!newSegments.length && !staleSegmentIds.length) return;
this.core.updateStream(managerStream.runtimeId, newSegments, staleSegmentIds);
}
};
function* itemsBackwards(items) {
for (let i = items.length - 1; i >= 0; i--) yield items[i];
}
function* nSegmentsBackwards(segments, count) {
let i = 0;
for (const segment of segments.values()) {
if (i >= count) break;
yield segment;
i++;
}
}
//#endregion
//#region src/loading-handler.ts
var Loader = class {
shaka;
core;
streamInfo;
loadArgs;
constructor(shaka, core, streamInfo) {
this.shaka = shaka;
this.core = core;
this.streamInfo = streamInfo;
}
defaultLoad() {
return this.shaka.net.HttpFetchPlugin.parse(...this.loadArgs);
}
load(...args) {
this.loadArgs = args;
const { RequestType } = this.shaka.net.NetworkingEngine;
const [url, request, requestType] = args;
if (requestType === RequestType.SEGMENT) return this.loadSegment(url, request);
const loading = this.defaultLoad();
if (requestType === RequestType.MANIFEST) this.handleManifestLoading(loading.promise).catch(() => void 0);
return loading;
}
async handleManifestLoading(loadingPromise) {
if (!this.streamInfo.manifestResponseUrl) {
const response = await loadingPromise;
this.setManifestResponseUrl(response.uri);
}
}
loadSegment(segmentUrl, originalRequest) {
const byteRangeString = originalRequest.headers.Range;
const segmentRuntimeId = getSegmentRuntimeId(segmentUrl, byteRangeString);
const isSegmentDownloadableByP2PCore = this.core.isSegmentLoadable(segmentRuntimeId);
if (!this.core.hasSegment(segmentRuntimeId) || !isSegmentDownloadableByP2PCore) return this.defaultLoad();
const loadSegment = async () => {
const { request, callbacks } = getSegmentRequest();
this.core.loadSegment(segmentRuntimeId, callbacks);
try {
const { data, bandwidth } = await request;
return {
data,
headers: {},
originalRequest,
uri: segmentUrl,
originalUri: segmentUrl,
timeMs: getLoadingDurationBasedOnBandwidth(bandwidth, data.byteLength)
};
} catch (error) {
if (error instanceof CoreRequestError) {
const { Error: ShakaError } = this.shaka.util;
if (error.type === "aborted") throw new ShakaError(ShakaError.Severity.RECOVERABLE, ShakaError.Category.NETWORK, this.shaka.util.Error.Code.OPERATION_ABORTED);
}
throw error;
}
};
return new this.shaka.util.AbortableOperation(loadSegment(), () => {
this.core.abortSegmentLoading(segmentRuntimeId);
return Promise.resolve();
});
}
setManifestResponseUrl(responseUrl) {
this.streamInfo.manifestResponseUrl = responseUrl;
this.core.setManifestResponseUrl(responseUrl);
}
};
function getLoadingDurationBasedOnBandwidth(bandwidth, bytesLoaded) {
const bits = bytesLoaded * 8;
return bandwidth > 0 ? Math.round(bits / bandwidth) * 1e3 : 0;
}
function getSegmentRequest() {
let onSuccess;
let onError;
return {
request: new Promise((resolve, reject) => {
onSuccess = resolve;
onError = reject;
}),
callbacks: {
onSuccess,
onError
}
};
}
//#endregion
//#region src/engine.ts
var LIVE_EDGE_DELAY = 25;
/**
* Represents a Peer-to-Peer (P2P) engine designed to enhance media streaming efficiency.
* This class integrates P2P technologies into Shaka Player, enabling the distribution of media segments via a peer network
* alongside traditional HTTP fetching. This reduces server bandwidth costs and improves scalability by sharing the load
* across multiple clients.
*
* The engine manages core functionalities such as segment fetching, segment management, peer connection management,
* and event handling related to the P2P and HLS processes.
*
* @example
* // Initializing the ShakaP2PEngine with custom configuration
* const shakaP2PEngine = new ShakaP2PEngine({
* core: {
* highDemandTimeWindow: 30, // 30 seconds
* simultaneousHttpDownloads: 3,
* webRtcMaxMessageSize: 64 * 1024, // 64 KB
* p2pNotReceivingBytesTimeoutMs: 10000, // 10 seconds
* p2pInactiveLoaderDestroyTimeoutMs: 15000, // 15 seconds
* httpNotReceivingBytesTimeoutMs: 8000, // 8 seconds
* httpErrorRetries: 2,
* p2pErrorRetries: 2,
* announceTrackers: ["wss://personal.tracker.com"],
* rtcConfig: {
* iceServers: [{ urls: "stun:personal.stun.com" }]
* },
* swarmId: "example-swarm-id"
* }
* });
*/
var ShakaP2PEngine = class ShakaP2PEngine {
player;
shaka;
streamInfo = {};
core;
segmentManager;
requestFilter;
/**
* Constructs an instance of `ShakaP2PEngine`.
*
* @param config An optional configuration for customizing the P2P engine's behavior.
* @param shaka The Shaka Player library instance.
*/
constructor(config, shaka = window.shaka) {
validateShaka(shaka);
this.shaka = shaka;
this.core = new Core$1(config?.core);
this.segmentManager = new SegmentManager(this.streamInfo, this.core);
}
/**
* Configures and initializes the Shaka Player instance with predefined settings optimized for P2P performance.
*
* @param player The Shaka Player instance to configure.
*/
bindShakaPlayer(player) {
if (this.player === player) return;
if (this.player) this.destroy();
this.player = player;
this.player.configure("manifest.defaultPresentationDelay", LIVE_EDGE_DELAY);
this.player.configure("manifest.dash.ignoreSuggestedPresentationDelay", true);
const versionMatch = /\d+/.exec(this.shaka.Player.version);
if (parseInt(versionMatch ? versionMatch[0] : "0", 10) >= 5) this.player.configure("streaming.preferNativeHls", false);
else this.player.configure("streaming.useNativeHlsOnSafari", false);
this.updatePlayerEventHandlers("register");
}
/**
* Applies dynamic configuration updates to the P2P engine.
*
* @param dynamicConfig The configuration changes to apply.
*
* @example
* // Assuming `shakaP2PEngine` is an instance of ShakaP2PEngine
*
* const newDynamicConfig = {
* core: {
* // Increase the number of cached segments to 1000
* cachedSegmentsCount: 1000,
* // 50 minutes of segments will be preemptively downloaded via HTTP connections
* httpDownloadTimeWindow: 3000,
* // 100 minutes of segments will be preemptively downloaded via P2P connections
* p2pDownloadTimeWindow: 6000,
* }
* };
*
* shakaP2PEngine.applyDynamicConfig(newDynamicConfig);
*/
applyDynamicConfig(dynamicConfig) {
if (dynamicConfig.core) this.core.applyDynamicConfig(dynamicConfig.core);
}
/**
* Retrieves the current configuration of the `ShakaP2PEngine`.
*
* @returns The configuration as a readonly object.
*/
getConfig() {
return { core: this.core.getConfig() };
}
/**
* Adds an event listener for the specified event.
* @param eventName The name of the event to listen for.
* @param listener The callback function to be invoked when the event is triggered.
*
* @example
* // Listening for a segment being successfully loaded
* shakaP2PEngine.addEventListener('onSegmentLoaded', (details) => {
* console.log('Segment Loaded:', details);
* });
*
* @example
* // Handling segment load errors
* shakaP2PEngine.addEventListener('onSegmentError', (errorDetails) => {
* console.error('Error loading segment:', errorDetails);
* });
*
* @example
* // Tracking data downloaded from peers
* shakaP2PEngine.addEventListener('onChunkDownloaded', (bytesLength, downloadSource, peerId) => {
* console.log(`Downloaded ${bytesLength} bytes from ${downloadSource} ${peerId ? 'from peer ' + peerId : 'from server'}`);
* });
*/
addEventListener(eventName, listener) {
this.core.addEventListener(eventName, listener);
}
/**
* Removes an event listener for the specified event.
* @param eventName The name of the event.
* @param listener The callback function that was previously added.
*/
removeEventListener(eventName, listener) {
this.core.removeEventListener(eventName, listener);
}
updatePlayerEventHandlers = (type) => {
const { player } = this;
if (!player) return;
const networkingEngine = player.getNetworkingEngine();
if (networkingEngine) if (type === "register") {
const p2pml = {
player,
shaka: this.shaka,
core: this.core,
streamInfo: this.streamInfo,
segmentManager: this.segmentManager
};
this.requestFilter = (requestType, request) => {
request.p2pml = p2pml;
};
networkingEngine.p2pml = p2pml;
networkingEngine.registerRequestFilter(this.requestFilter);
} else {
networkingEngine.p2pml = void 0;
if (this.requestFilter) networkingEngine.unregisterRequestFilter(this.requestFilter);
}
const method = type === "register" ? "addEventListener" : "removeEventListener";
player[method]("loaded", this.handlePlayerLoaded);
player[method]("loading", this.destroyCurrentStreamContext);
player[method]("unloading", this.handlePlayerUnloading);
player[method]("adaptation", this.onVariantChanged);
player[method]("variantchanged", this.onVariantChanged);
};
onVariantChanged = () => {
if (!this.player) return;
const activeTrack = this.player.getVariantTracks().find((track) => track.active);
if (!activeTrack) return;
this.core.setActiveLevelBitrate(activeTrack.bandwidth);
};
handlePlayerLoaded = () => {
if (!this.player) return;
this.core.setIsLive(this.player.isLive());
this.updateMediaElementEventHandlers("register");
};
handlePlayerUnloading = () => {
this.destroyCurrentStreamContext();
this.updateMediaElementEventHandlers("unregister");
};
destroyCurrentStreamContext = () => {
this.streamInfo.protocol = void 0;
this.streamInfo.manifestResponseUrl = void 0;
this.core.destroy();
};
updateMediaElementEventHandlers = (type) => {
const media = this.player?.getMediaElement();
if (!media) return;
const method = type === "register" ? "addEventListener" : "removeEventListener";
media[method]("timeupdate", this.handlePlaybackUpdate);
media[method]("ratechange", this.handlePlaybackUpdate);
media[method]("seeking", this.handlePlaybackUpdate);
};
handlePlaybackUpdate = (event) => {
const media = event.target;
this.core.updatePlayback(media.currentTime, media.playbackRate);
};
/** Cleans up and releases all resources, and unregisters all event handlers. */
destroy() {
this.destroyCurrentStreamContext();
this.updatePlayerEventHandlers("unregister");
this.updateMediaElementEventHandlers("unregister");
this.player = void 0;
}
static registerManifestParsers(shaka) {
const hlsParserFactory = () => new HlsManifestParser(shaka);
const dashParserFactory = () => new DashManifestParser(shaka);
const Parser = shaka.media.ManifestParser;
Parser.registerParserByMime("application/dash+xml", dashParserFactory);
Parser.registerParserByMime("application/x-mpegurl", hlsParserFactory);
Parser.registerParserByMime("application/vnd.apple.mpegurl", hlsParserFactory);
}
static unregisterManifestParsers(shaka) {
const Parser = shaka.media.ManifestParser;
Parser.unregisterParserByMime("mpd");
Parser.unregisterParserByMime("application/dash+xml");
Parser.unregisterParserByMime("m3u8");
Parser.unregisterParserByMime("application/x-mpegurl");
Parser.unregisterParserByMime("application/vnd.apple.mpegurl");
}
static registerNetworkingEngineSchemes(shaka) {
const { NetworkingEngine } = shaka.net;
const handleLoading = (...args) => {
const { p2pml } = args[1];
if (!p2pml) return shaka.net.HttpFetchPlugin.parse(...args);
return new Loader(p2pml.shaka, p2pml.core, p2pml.streamInfo).load(...args);
};
NetworkingEngine.registerScheme("http", handleLoading);
NetworkingEngine.registerScheme("https", handleLoading);
NetworkingEngine.registerScheme("data", handleLoading);
}
static unregisterNetworkingEngineSchemes(shaka) {
const { NetworkingEngine } = shaka.net;
NetworkingEngine.unregisterScheme("http");
NetworkingEngine.unregisterScheme("https");
NetworkingEngine.unregisterScheme("data");
}
/**
* Registers plugins related to P2P functionality into the Shaka Player.
* Plugins must be registered before initializing the player to ensure proper integration.
*
* @param shaka The Shaka Player library. Defaults to the global Shaka Player instance if not provided.
*/
static registerPlugins(shaka = window.shaka) {
validateShaka(shaka);
ShakaP2PEngine.registerManifestParsers(shaka);
ShakaP2PEngine.registerNetworkingEngineSchemes(shaka);
}
/**
* Unregisters plugins related to P2P functionality from the Shaka Player.
*
* @param shaka The Shaka Player library. Defaults to the global Shaka Player instance if not provided.
*/
static unregisterPlugins(shaka = window.shaka) {
validateShaka(shaka);
ShakaP2PEngine.unregisterManifestParsers(shaka);
ShakaP2PEngine.unregisterNetworkingEngineSchemes(shaka);
}
};
function validateShaka(shaka) {
if (!shaka) throw new Error("shaka namespace is not defined in global scope and not passed as an argument to Shaka P2P engine constructor");
}
//#endregion
export { Core, ShakaP2PEngine };
//# sourceMappingURL=p2p-media-loader-shaka.es.js.map