p2p-media-loader-shaka
Version:
P2P Media Loader Shaka Player integration
472 lines (471 loc) • 14.5 kB
JavaScript
import { Core as e, Core as t, CoreRequestError as n, debug as r, generateStreamShortId as i } from "p2p-media-loader-core";
//#region src/manifest-parser-decorator.ts
var a = [
"mp4a",
"ac-3",
"ec-3",
"ec+3",
"opus",
"vorb",
"flac"
], o = class {
shaka;
originalManifestParser;
debug = r("p2pml-shaka:manifest-parser");
isHls;
segmentManager;
player;
constructor(e, t) {
this.shaka = e, this.originalManifestParser = t, this.isHls = this.originalManifestParser instanceof e.hls.HlsParser;
}
configure(e) {
return this.originalManifestParser.configure(e);
}
banLocation(e) {
return this.originalManifestParser.banLocation(e);
}
onInitialVariantChosen(e) {
return this.originalManifestParser.onInitialVariantChosen(e);
}
setP2PMediaLoaderData(e) {
e && (this.segmentManager = e.segmentManager, this.player = e.player, e.streamInfo.protocol = this.isHls ? "hls" : "dash");
}
async start(e, t) {
let { p2pml: n } = t.networkingEngine;
this.setP2PMediaLoaderData(n);
let r = await this.originalManifestParser.start(e, t);
return n ? (this.isHls && this.hookHlsStreamMediaSequenceTimeMaps(r.variants), this.processStreams(r.variants), r) : r;
}
stop() {
return this.originalManifestParser.stop();
}
update() {
return this.originalManifestParser.update();
}
setMediaElement(e) {
return this.originalManifestParser.setMediaElement(e);
}
onExpirationUpdated(e, t) {
return this.originalManifestParser.onExpirationUpdated(e, t);
}
processStreams(e) {
let { segmentManager: t } = this;
if (!t) return;
let n = /* @__PURE__ */ new Set(), r = (e, r, i) => (this.hookSegmentIndex(e), t.setStream(e, r, i), n.add(e.id), !0);
for (let t of e) {
let { video: e, audio: o } = t;
if (e && !n.has(e.id)) {
let n = t.bandwidth === 0, o = e.codecs ? e.codecs.split(",").map((e) => e.trim().toLowerCase()).filter((e) => !a.some((t) => e.startsWith(t))).join(",") : void 0, { frameRate: s, hdr: c } = e;
r(e, "main", i({
bitrate: t.bandwidth,
codecs: n ? void 0 : o,
width: n ? void 0 : e.width,
height: n ? void 0 : e.height,
frameRate: n ? void 0 : s,
videoRange: n ? void 0 : c
}));
}
if (o && !n.has(o.id)) {
let n = !e, a = o.label ?? o.originalId ?? void 0, s = i({
bitrate: n ? t.bandwidth : 0,
codecs: n ? void 0 : o.codecs,
language: n ? void 0 : o.language,
channels: n ? void 0 : o.channelsCount,
name: n ? void 0 : a
});
r(o, n ? "main" : "secondary", s);
}
}
}
hookSegmentIndex(e) {
let { segmentManager: t } = this;
if (!t) return;
let n = (n, r = !1) => {
let i = null, a, o, s = n.get, c = (u) => {
let d = s.call(n, u);
if (d === i || !this.player?.isLive() && e.isSegmentIndexAlreadyRead) return d;
i = d, n.get = s;
try {
let r = l(n, this.shaka);
if (!r) throw Error("Segment references not found");
let i = r[0], s = r[r.length - 1];
if (i === a && s === o) return d;
a = i, o = s, t.updateStreamSegments(e, r), e.isSegmentIndexAlreadyRead = !0, this.debug(`Stream ${e.id} is updated`);
} catch {} finally {
(!e.isSegmentIndexAlreadyRead || this.player?.isLive() || !r) && (n.get = c);
}
return d;
};
n.get = c;
};
if (e.segmentIndex) {
n(e.segmentIndex);
return;
}
let r = e.createSegmentIndex;
e.createSegmentIndex = async () => {
let t = await r.call(e);
return e.segmentIndex && n(e.segmentIndex, !0), t;
};
}
hookHlsStreamMediaSequenceTimeMaps(e) {
let t = u(this.originalManifestParser), n, r, i = [
"video",
"audio",
"text",
"image"
];
for (let e of t) i.every((t) => e.has(t)) && (n = e.get("video"), r = e.get("audio"));
if (n && r) {
for (let t of e) {
let { video: e, audio: i } = t;
e && (e.mediaSequenceTimeMap = n), i && (i.mediaSequenceTimeMap = r);
}
return;
}
let a = t.find((e) => {
let t = e.values().next().value;
return typeof t == "object" && t?.streams?.createSegmentIndex;
});
if (!a) return;
let o = [...a.values()];
for (let e of o) {
if (e?.stream?.mediaSequenceTimeMap) continue;
let t = u(e).find((e) => {
let [t, n] = e.entries().next().value ?? [];
return typeof t == "number" && typeof n == "number";
});
t && (e.stream.mediaSequenceTimeMap = t);
}
}
}, s = class extends o {
constructor(e) {
super(e, new e.hls.HlsParser());
}
}, c = class extends o {
constructor(e) {
super(e, new e.dash.DashParser());
}
};
function l(e, t) {
for (let n in e) if (Array.isArray(e[n]) && e[n].length > 0 && e[n][0] instanceof t.media.SegmentReference) return e[n];
else if (typeof e[n] == "object") {
let r = l(e[n], t);
if (r) return r;
}
return null;
}
function u(e) {
return Object.keys(e).map((t) => e[t]).filter((e) => e instanceof Map);
}
//#endregion
//#region src/stream-utils.ts
function d({ segmentReference: e, externalId: t, runtimeId: n }) {
let { byteRange: r, url: i, startTime: a, endTime: o } = h(e);
return {
runtimeId: n ?? p(i, r),
externalId: t,
byteRange: r,
url: i,
startTime: a,
endTime: o
};
}
function f(e) {
let { url: t, byteRange: n } = h(e);
return p(t, n);
}
function p(e, t) {
if (!t) return e;
let n = typeof t == "string" ? m(t) : t;
return n ? `${e}|${n.start}-${n.end}` : e;
}
function m(e) {
if (!e?.includes("bytes=")) return;
let t = e.split("=")[1].split("-"), n = parseInt(t[0]), r = parseInt(t[1]);
if (!(isNaN(n) || isNaN(r))) return {
start: n,
end: r
};
}
function h(e) {
let t = e.getUris(), n = t[1] ?? t[0], r = e.getStartByte(), i = e.getEndByte() ?? void 0, a = e.getStartTime(), o = e.getEndTime();
return {
byteRange: i === void 0 ? void 0 : {
start: r,
end: i
},
url: n,
startTime: a,
endTime: o
};
}
function g(e) {
let { shakaStream: t } = e, n = t.mediaSequenceTimeMap;
if (!n) return;
let r = n.keys().next().value;
if (r !== void 0) return r + n.size - 1;
}
//#endregion
//#region src/segment-manager.ts
var _ = .5, v = class {
core;
streamInfo;
constructor(e, t) {
this.core = t, this.streamInfo = e;
}
setStream(e, t, n) {
this.core.addStreamIfNoneExists({
runtimeId: e.id.toString(),
type: t,
index: n,
shakaStream: e
}), e.segmentIndex && this.updateStreamSegments(e);
}
updateStreamSegments(e, t) {
let n = this.core.getStream(e.id.toString());
if (!n) return;
let { segmentIndex: r } = n.shakaStream;
if (!t && r) try {
t = [...r].filter((e) => !!e);
} catch {
return;
}
t && (this.streamInfo.protocol === "hls" ? this.processHlsSegmentReferences(n, t) : this.processDashSegmentReferences(n, t));
}
processDashSegmentReferences(e, t) {
let n = new Set(e.segments.keys()), r = [];
for (let i of t) {
let t = Math.trunc(i.getStartTime() / _), a = f(i);
if (!e.segments.has(a)) {
let e = d({
segmentReference: i,
externalId: t,
runtimeId: a
});
r.push(e);
}
n.delete(a);
}
!r.length && !n.size || this.core.updateStream(e.runtimeId, r, n.values());
}
processHlsSegmentReferences(e, t) {
let { segments: n } = e, r = g(e), i = [];
if (n.size === 0) {
let n = r === void 0 ? 0 : r - t.length + 1;
for (let [e, r] of t.entries()) {
let t = d({
segmentReference: r,
externalId: n + e
});
i.push(t);
}
this.core.updateStream(e.runtimeId, i);
return;
}
if (r === void 0) return;
let a = r;
for (let e of y(t)) {
let t = f(e);
if (n.has(t)) break;
let r = d({
runtimeId: t,
segmentReference: e,
externalId: a
});
i.push(r), a--;
}
i.reverse();
let o = [], s = i.length;
for (let e of b(n, s)) o.push(e.runtimeId);
!i.length && !o.length || this.core.updateStream(e.runtimeId, i, o);
}
};
function* y(e) {
for (let t = e.length - 1; t >= 0; t--) yield e[t];
}
function* b(e, t) {
let n = 0;
for (let r of e.values()) {
if (n >= t) break;
yield r, n++;
}
}
//#endregion
//#region src/loading-handler.ts
var x = class {
shaka;
core;
streamInfo;
loadArgs;
constructor(e, t, n) {
this.shaka = e, this.core = t, this.streamInfo = n;
}
defaultLoad() {
return this.shaka.net.HttpFetchPlugin.parse(...this.loadArgs);
}
load(...e) {
this.loadArgs = e;
let { RequestType: t } = this.shaka.net.NetworkingEngine, [n, r, i] = e;
if (i === t.SEGMENT) return this.loadSegment(n, r);
let a = this.defaultLoad();
return i === t.MANIFEST && this.handleManifestLoading(a.promise).catch(() => void 0), a;
}
async handleManifestLoading(e) {
if (!this.streamInfo.manifestResponseUrl) {
let t = await e;
this.setManifestResponseUrl(t.uri);
}
}
loadSegment(e, t) {
let r = t.headers.Range, i = p(e, r), a = this.core.isSegmentLoadable(i);
return !this.core.hasSegment(i) || !a ? this.defaultLoad() : new this.shaka.util.AbortableOperation((async () => {
let { request: r, callbacks: a } = C();
this.core.loadSegment(i, a);
try {
let { data: n, bandwidth: i } = await r;
return {
data: n,
headers: {},
originalRequest: t,
uri: e,
originalUri: e,
timeMs: S(i, n.byteLength)
};
} catch (e) {
if (e instanceof n) {
let { Error: t } = this.shaka.util;
if (e.type === "aborted") throw new t(t.Severity.RECOVERABLE, t.Category.NETWORK, this.shaka.util.Error.Code.OPERATION_ABORTED);
}
throw e;
}
})(), () => (this.core.abortSegmentLoading(i), Promise.resolve()));
}
setManifestResponseUrl(e) {
this.streamInfo.manifestResponseUrl = e, this.core.setManifestResponseUrl(e);
}
};
function S(e, t) {
let n = t * 8;
return e > 0 ? Math.round(n / e) * 1e3 : 0;
}
function C() {
let e, t;
return {
request: new Promise((n, r) => {
e = n, t = r;
}),
callbacks: {
onSuccess: e,
onError: t
}
};
}
//#endregion
//#region src/engine.ts
var w = 25, T = class e {
player;
shaka;
streamInfo = {};
core;
segmentManager;
requestFilter;
constructor(e, n = window.shaka) {
E(n), this.shaka = n, this.core = new t(e?.core), this.segmentManager = new v(this.streamInfo, this.core);
}
bindShakaPlayer(e) {
if (this.player === e) return;
this.player && this.destroy(), this.player = e, this.player.configure("manifest.defaultPresentationDelay", w), this.player.configure("manifest.dash.ignoreSuggestedPresentationDelay", !0);
let t = /\d+/.exec(this.shaka.Player.version);
parseInt(t ? t[0] : "0", 10) >= 5 ? this.player.configure("streaming.preferNativeHls", !1) : this.player.configure("streaming.useNativeHlsOnSafari", !1), this.updatePlayerEventHandlers("register");
}
applyDynamicConfig(e) {
e.core && this.core.applyDynamicConfig(e.core);
}
getConfig() {
return { core: this.core.getConfig() };
}
addEventListener(e, t) {
this.core.addEventListener(e, t);
}
removeEventListener(e, t) {
this.core.removeEventListener(e, t);
}
updatePlayerEventHandlers = (e) => {
let { player: t } = this;
if (!t) return;
let n = t.getNetworkingEngine();
if (n) if (e === "register") {
let e = {
player: t,
shaka: this.shaka,
core: this.core,
streamInfo: this.streamInfo,
segmentManager: this.segmentManager
};
this.requestFilter = (t, n) => {
n.p2pml = e;
}, n.p2pml = e, n.registerRequestFilter(this.requestFilter);
} else n.p2pml = void 0, this.requestFilter && n.unregisterRequestFilter(this.requestFilter);
let r = e === "register" ? "addEventListener" : "removeEventListener";
t[r]("loaded", this.handlePlayerLoaded), t[r]("loading", this.destroyCurrentStreamContext), t[r]("unloading", this.handlePlayerUnloading), t[r]("adaptation", this.onVariantChanged), t[r]("variantchanged", this.onVariantChanged);
};
onVariantChanged = () => {
if (!this.player) return;
let e = this.player.getVariantTracks().find((e) => e.active);
e && this.core.setActiveLevelBitrate(e.bandwidth);
};
handlePlayerLoaded = () => {
this.player && (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 = (e) => {
let t = this.player?.getMediaElement();
if (!t) return;
let n = e === "register" ? "addEventListener" : "removeEventListener";
t[n]("timeupdate", this.handlePlaybackUpdate), t[n]("ratechange", this.handlePlaybackUpdate), t[n]("seeking", this.handlePlaybackUpdate);
};
handlePlaybackUpdate = (e) => {
let t = e.target;
this.core.updatePlayback(t.currentTime, t.playbackRate);
};
destroy() {
this.destroyCurrentStreamContext(), this.updatePlayerEventHandlers("unregister"), this.updateMediaElementEventHandlers("unregister"), this.player = void 0;
}
static registerManifestParsers(e) {
let t = () => new s(e), n = () => new c(e), r = e.media.ManifestParser;
r.registerParserByMime("application/dash+xml", n), r.registerParserByMime("application/x-mpegurl", t), r.registerParserByMime("application/vnd.apple.mpegurl", t);
}
static unregisterManifestParsers(e) {
let t = e.media.ManifestParser;
t.unregisterParserByMime("mpd"), t.unregisterParserByMime("application/dash+xml"), t.unregisterParserByMime("m3u8"), t.unregisterParserByMime("application/x-mpegurl"), t.unregisterParserByMime("application/vnd.apple.mpegurl");
}
static registerNetworkingEngineSchemes(e) {
let { NetworkingEngine: t } = e.net, n = (...t) => {
let { p2pml: n } = t[1];
return n ? new x(n.shaka, n.core, n.streamInfo).load(...t) : e.net.HttpFetchPlugin.parse(...t);
};
t.registerScheme("http", n), t.registerScheme("https", n), t.registerScheme("data", n);
}
static unregisterNetworkingEngineSchemes(e) {
let { NetworkingEngine: t } = e.net;
t.unregisterScheme("http"), t.unregisterScheme("https"), t.unregisterScheme("data");
}
static registerPlugins(t = window.shaka) {
E(t), e.registerManifestParsers(t), e.registerNetworkingEngineSchemes(t);
}
static unregisterPlugins(t = window.shaka) {
E(t), e.unregisterManifestParsers(t), e.unregisterNetworkingEngineSchemes(t);
}
};
function E(e) {
if (!e) throw Error("shaka namespace is not defined in global scope and not passed as an argument to Shaka P2P engine constructor");
}
//#endregion
export { e as Core, T as ShakaP2PEngine };
//# sourceMappingURL=p2p-media-loader-shaka.es.min.js.map