p2p-media-loader-shaka
Version:
P2P Media Loader Shaka Player integration
377 lines (376 loc) • 14.5 kB
JavaScript
var L = Object.defineProperty;
var T = (a, e, t) => e in a ? L(a, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[e] = t;
var g = (a, e, t) => T(a, typeof e != "symbol" ? e + "" : e, t);
import { debug as C, CoreRequestError as H, Core as q } from "p2p-media-loader-core";
class x {
constructor(e, t) {
g(this, "debug", C("p2pml-shaka:manifest-parser"));
g(this, "isHls");
g(this, "segmentManager");
g(this, "player");
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) {
const { p2pml: n } = t.networkingEngine;
this.setP2PMediaLoaderData(n);
const r = await this.originalManifestParser.start(e, t);
return n && (this.isHls && this.hookHlsStreamMediaSequenceTimeMaps(r.variants), this.processStreams(r.variants)), 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) {
const { segmentManager: t } = this;
if (!t) return;
const n = /* @__PURE__ */ new Set(), r = (h, d, s) => (this.hookSegmentIndex(h), t.setStream(h, d, s), n.add(h.id), !0);
let i = 0, c = 0;
for (const h of e) {
const { video: d, audio: s } = h;
d && !n.has(d.id) && r(d, "main", i++), s && !n.has(s.id) && r(s, d ? "secondary" : "main", c++);
}
}
hookSegmentIndex(e) {
const { segmentManager: t } = this;
if (!t) return;
const n = (i, c = !1) => {
let h, d, s = null;
const o = i.get, l = (u) => {
var v, P;
const m = o.call(i, u);
if (m === s || !((v = this.player) != null && v.isLive()) && e.isSegmentIndexAlreadyRead) return m;
s = m, i.get = o;
try {
const f = b(i, this.shaka);
if (!f) throw new Error("Segment references not found");
const E = f[0], k = f[f.length - 1];
if (E === h && k === d) return m;
h = E, d = k, t.updateStreamSegments(e, f), e.isSegmentIndexAlreadyRead = !0, this.debug(`Stream ${e.id} is updated`);
} catch {
} finally {
e.isSegmentIndexAlreadyRead && !((P = this.player) != null && P.isLive()) && c || (i.get = l);
}
return m;
};
i.get = l;
};
if (e.segmentIndex) return void n(e.segmentIndex);
const r = e.createSegmentIndex;
e.createSegmentIndex = async () => {
const i = await r.call(e);
return e.segmentIndex && n(e.segmentIndex, !0), i;
};
}
hookHlsStreamMediaSequenceTimeMaps(e) {
var d;
const t = I(this.originalManifestParser);
let n, r;
const i = ["video", "audio", "text", "image"];
for (const s of t) i.every((o) => s.has(o)) && (n = s.get("video"), r = s.get("audio"));
if (n && r) {
for (const s of e) {
const { video: o, audio: l } = s;
o && (o.mediaSequenceTimeMap = n), l && (l.mediaSequenceTimeMap = n);
}
return;
}
const c = t.find((s) => {
var l;
const o = s.values().next().value;
return typeof o == "object" && ((l = o == null ? void 0 : o.streams) == null ? void 0 : l.createSegmentIndex);
});
if (!c) return;
const h = [...c.values()];
for (const s of h) {
if ((d = s == null ? void 0 : s.stream) != null && d.mediaSequenceTimeMap) continue;
const o = I(s).find((l) => {
const [u, m] = l.entries().next().value ?? [];
return typeof u == "number" && typeof m == "number";
});
o && (s.stream.mediaSequenceTimeMap = o);
}
}
}
class U extends x {
constructor(e) {
super(e, new e.hls.HlsParser());
}
}
class A extends x {
constructor(e) {
super(e, new e.dash.DashParser());
}
}
function b(a, e) {
for (const t in a) {
if (Array.isArray(a[t]) && a[t].length > 0 && a[t][0] instanceof e.media.SegmentReference) return a[t];
if (typeof a[t] == "object") {
const n = b(a[t], e);
if (n) return n;
}
}
return null;
}
function I(a) {
return Object.values(a).filter((e) => e instanceof Map);
}
function y({ segmentReference: a, externalId: e, runtimeId: t }) {
const { byteRange: n, url: r, startTime: i, endTime: c } = R(a);
return { runtimeId: t ?? M(r, n), externalId: e, byteRange: n, url: r, startTime: i, endTime: c };
}
function w(a) {
const { url: e, byteRange: t } = R(a);
return M(e, t);
}
function M(a, e) {
if (!e) return a;
const t = typeof e == "string" ? function(n) {
if (!(n != null && n.includes("bytes="))) return;
const r = n.split("=")[1].split("-").map((h) => parseInt(h)), [i, c] = r;
return { start: i, end: c };
}(e) : e;
return t ? `${a}|${t.start}-${t.end}` : a;
}
function R(a) {
const e = a.getUris(), t = e[1] ?? e[0], n = a.getStartByte(), r = a.getEndByte() ?? void 0;
return { byteRange: r !== void 0 ? { start: n, end: r } : void 0, url: t, startTime: a.getStartTime(), endTime: a.getEndTime() };
}
class N {
constructor(e, t) {
g(this, "core");
g(this, "streamInfo");
this.core = t, this.streamInfo = e;
}
setStream(e, t, n = -1) {
this.core.addStreamIfNoneExists({ runtimeId: e.id.toString(), type: t, index: n, shakaStream: e }), e.segmentIndex && this.updateStreamSegments(e);
}
updateStreamSegments(e, t) {
const n = this.core.getStream(e.id.toString());
if (!n) return;
const { segmentIndex: r } = n.shakaStream;
if (!t && r) try {
t = [...r];
} catch {
return;
}
t && (this.streamInfo.protocol === "hls" ? this.processHlsSegmentReferences(n, t) : this.processDashSegmentReferences(n, t));
}
processDashSegmentReferences(e, t) {
const n = new Set(e.segments.keys()), r = [];
for (const i of t) {
const c = Math.trunc(i.getStartTime() / 0.5), h = w(i);
if (!e.segments.has(h)) {
const d = y({ segmentReference: i, externalId: c, runtimeId: h });
r.push(d);
}
n.delete(h);
}
(r.length || n.size) && this.core.updateStream(e.runtimeId, r, n.values());
}
processHlsSegmentReferences(e, t) {
const { segments: n } = e, r = function(s) {
const { shakaStream: o } = s, l = o.mediaSequenceTimeMap;
if (!l) return;
const u = l.keys().next().value;
return u !== void 0 ? u + l.size - 1 : void 0;
}(e), i = [];
if (n.size === 0) {
const s = r === void 0 ? 0 : r - t.length + 1;
for (const [o, l] of t.entries()) {
const u = y({ segmentReference: l, externalId: s + o });
i.push(u);
}
return void this.core.updateStream(e.runtimeId, i);
}
if (!r) return;
let c = r;
for (const s of function* (o) {
for (let l = o.length - 1; l >= 0; l--) yield o[l];
}(t)) {
const o = w(s);
if (n.has(o)) break;
const l = y({ runtimeId: o, segmentReference: s, externalId: c });
i.push(l), c--;
}
i.reverse();
const h = [], d = i.length;
for (const s of function* (o, l) {
let u = 0;
for (const m of o.values()) {
if (u >= l) break;
yield m, u++;
}
}(n, d)) h.push(s.runtimeId);
(i.length || h.length) && this.core.updateStream(e.runtimeId, i, h);
}
}
class B {
constructor(e, t, n) {
g(this, "loadArgs");
this.shaka = e, this.core = t, this.streamInfo = n;
}
defaultLoad() {
return this.shaka.net.HttpFetchPlugin.parse(...this.loadArgs);
}
load(...e) {
this.loadArgs = e;
const { RequestType: t } = this.shaka.net.NetworkingEngine, [n, r, i] = e;
if (i === t.SEGMENT) return this.loadSegment(n, r);
const c = this.defaultLoad();
return i === t.MANIFEST && this.handleManifestLoading(c.promise), c;
}
async handleManifestLoading(e) {
if (!this.streamInfo.manifestResponseUrl) {
const t = await e;
this.setManifestResponseUrl(t.uri);
}
}
loadSegment(e, t) {
const n = t.headers.Range, r = M(e, n), i = this.core.isSegmentLoadable(r);
return !this.core.hasSegment(r) || !i ? this.defaultLoad() : new this.shaka.util.AbortableOperation((async () => {
const { request: c, callbacks: h } = function() {
let d, s;
return { request: new Promise((l, u) => {
d = l, s = u;
}), callbacks: { onSuccess: d, onError: s } };
}();
this.core.loadSegment(r, h);
try {
const { data: d, bandwidth: s } = await c;
return { data: d, headers: {}, originalRequest: t, uri: e, originalUri: e, timeMs: D(s, d.byteLength) };
} catch (d) {
if (d instanceof H) {
const { Error: s } = this.shaka.util;
if (d.type === "aborted") throw new s(s.Severity.RECOVERABLE, s.Category.NETWORK, this.shaka.util.Error.Code.OPERATION_ABORTED);
}
throw d;
}
})(), () => (this.core.abortSegmentLoading(r), Promise.resolve()));
}
setManifestResponseUrl(e) {
this.streamInfo.manifestResponseUrl = e, this.core.setManifestResponseUrl(e);
}
}
function D(a, e) {
const t = 8 * e;
return 1e3 * Math.round(t / a);
}
class p {
constructor(e, t = window.shaka) {
g(this, "player");
g(this, "shaka");
g(this, "streamInfo", {});
g(this, "core");
g(this, "segmentManager");
g(this, "requestFilter");
g(this, "updatePlayerEventHandlers", (e) => {
const { player: t } = this;
if (!t) return;
const n = t.getNetworkingEngine();
if (n) if (e === "register") {
const i = { player: t, shaka: this.shaka, core: this.core, streamInfo: this.streamInfo, segmentManager: this.segmentManager };
this.requestFilter = (c, h) => {
h.p2pml = i;
}, n.p2pml = i, n.registerRequestFilter(this.requestFilter);
} else n.p2pml = void 0, this.requestFilter && n.unregisterRequestFilter(this.requestFilter);
const 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);
});
g(this, "onVariantChanged", () => {
if (!this.player) return;
const e = this.player.getVariantTracks().find((t) => t.active);
e && this.core.setActiveLevelBitrate(e.bandwidth);
});
g(this, "handlePlayerLoaded", () => {
this.player && (this.core.setIsLive(this.player.isLive()), this.updateMediaElementEventHandlers("register"));
});
g(this, "handlePlayerUnloading", () => {
this.destroyCurrentStreamContext(), this.updateMediaElementEventHandlers("unregister");
});
g(this, "destroyCurrentStreamContext", () => {
this.streamInfo.protocol = void 0, this.streamInfo.manifestResponseUrl = void 0, this.core.destroy();
});
g(this, "updateMediaElementEventHandlers", (e) => {
var r;
const t = (r = this.player) == null ? void 0 : r.getMediaElement();
if (!t) return;
const n = e === "register" ? "addEventListener" : "removeEventListener";
t[n]("timeupdate", this.handlePlaybackUpdate), t[n]("ratechange", this.handlePlaybackUpdate), t[n]("seeking", this.handlePlaybackUpdate);
});
g(this, "handlePlaybackUpdate", (e) => {
const t = e.target;
this.core.updatePlayback(t.currentTime, t.playbackRate);
});
S(t), this.shaka = t, this.core = new q(e == null ? void 0 : e.core), this.segmentManager = new N(this.streamInfo, this.core);
}
bindShakaPlayer(e) {
this.player !== e && (this.player && this.destroy(), this.player = e, this.player.configure("manifest.defaultPresentationDelay", 25), this.player.configure("manifest.dash.ignoreSuggestedPresentationDelay", !0), 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);
}
destroy() {
this.destroyCurrentStreamContext(), this.updatePlayerEventHandlers("unregister"), this.updateMediaElementEventHandlers("unregister"), this.player = void 0;
}
static registerManifestParsers(e) {
const t = () => new U(e), n = e.media.ManifestParser;
n.registerParserByMime("application/dash+xml", () => new A(e)), n.registerParserByMime("application/x-mpegurl", t), n.registerParserByMime("application/vnd.apple.mpegurl", t);
}
static unregisterManifestParsers(e) {
const 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) {
const { NetworkingEngine: t } = e.net, n = (...r) => {
const i = r[1], { p2pml: c } = i;
return c ? new B(c.shaka, c.core, c.streamInfo).load(...r) : e.net.HttpFetchPlugin.parse(...r);
};
t.registerScheme("http", n), t.registerScheme("https", n), t.registerScheme("data", n);
}
static unregisterNetworkingEngineSchemes(e) {
const { NetworkingEngine: t } = e.net;
t.unregisterScheme("http"), t.unregisterScheme("https"), t.unregisterScheme("data");
}
static registerPlugins(e = window.shaka) {
S(e), p.registerManifestParsers(e), p.registerNetworkingEngineSchemes(e);
}
static unregisterPlugins(e = window.shaka) {
S(e), p.unregisterManifestParsers(e), p.unregisterNetworkingEngineSchemes(e);
}
}
function S(a) {
if (!a) throw new Error("shaka namespace is not defined in global scope and not passed as an argument to Shaka P2P engine constructor");
}
export {
p as ShakaP2PEngine
};
//# sourceMappingURL=p2p-media-loader-shaka.es.min.js.map