UNPKG

p2p-media-loader-shaka

Version:

P2P Media Loader Shaka Player integration

472 lines (471 loc) 14.5 kB
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