UNPKG

@oplayer/shaka

Version:

shaka-player plugin for oplayer

371 lines (370 loc) 13.5 kB
/** * name: @oplayer/shaka * version: v1.2.26-beta.5 * description: shaka-player plugin for oplayer * author: shiyiya * homepage: https://github.com/shiyiya/oplayer */ import { loadSDK } from "@oplayer/core"; const defaultMatcher = (source) => { if (source.format && ["m3u8", "mpd", "shaka"].includes(source.format)) { return true; } return (source.format === "auto" || typeof source.format === "undefined") && /(m3u8|mpd|shaka)(#|\?|$)/i.test(source.src); }; class ShakaPlugin { constructor(options) { this.key = "shaka"; this.name = "oplayer-plugin-shaka"; this.version = "1.2.26-beta.5"; this.options = { matcher: defaultMatcher, qualityControl: true, audioControl: true, textControl: true, qualityControlType: "menu" }; this.setupQuality = (player, instance, qualityControlType) => { let tracks = []; if (instance.getLoadMode() != ShakaPlugin.library.Player.LoadMode.SRC_EQUALS) { tracks = instance.getVariantTracks(); } const selectedTrack = tracks.find((track) => track.active); if (selectedTrack) { tracks = tracks.filter((track) => { if (track.language != selectedTrack.language) { return false; } if (track.channelsCount && selectedTrack.channelsCount && track.channelsCount != selectedTrack.channelsCount) { return false; } if (JSON.stringify(track.audioRoles) != JSON.stringify(selectedTrack.audioRoles)) { return false; } return true; }); } if (instance.isAudioOnly()) { tracks = tracks.filter((track, idx) => { return tracks.findIndex((t) => t.bandwidth == track.bandwidth) == idx; }); } else { const audiosIds = [].concat(new Set(tracks.map((t) => t.audioId))).filter((t) => t !== null); if (audiosIds.length > 1) { tracks = tracks.filter((track, idx) => { const otherIdx = tracks.findIndex((t) => { const ret = t.height == track.height && t.videoBandwidth == track.videoBandwidth && t.frameRate == track.frameRate && t.hdr == track.hdr && t.videoLayout == track.videoLayout; return ret; }); return otherIdx == idx; }); } else { tracks = tracks.filter((track, idx) => { const otherIdx = tracks.findIndex((t) => { const ret = t.height == track.height && t.bandwidth == track.bandwidth && t.frameRate == track.frameRate && t.hdr == track.hdr && t.videoLayout == track.videoLayout; return ret; }); return otherIdx == idx; }); } } if (!(tracks.length > 1)) return; if (instance.isAudioOnly()) { tracks.sort((t1, t2) => { return t2.bandwidth - t1.bandwidth; }); } else { tracks.sort((t1, t2) => { if (t2.height == t1.height || t1.height == null || t2.height == null) { return t2.bandwidth - t1.bandwidth; } return t2.height - t1.height; }); } const abrEnabled = instance.getConfiguration().abr.enabled; const settings = tracks.map((t) => { return { name: !instance.isAudioOnly() && t.height && t.width ? this.getResolutionLabel_(t, tracks) : t.bandwidth ? Math.round(t.bandwidth / 1e3) + " kbits/s" : "Unknown", default: !abrEnabled && t == selectedTrack, value: t }; }); const ctrl = qualityControlType == "menu" ? player.context.ui.menu : player.context.ui.setting; const autoText = player.locales.get("Auto"); ctrl.unregister(ShakaPlugin.name + "-Quality"); ctrl.register({ icon: qualityControlType == "setting" ? player.context.ui.icons.quality : void 0, name: qualityControlType == "setting" ? "Quality" : !abrEnabled && selectedTrack ? this.getResolutionLabel_(selectedTrack, []) : autoText, type: "selector", key: ShakaPlugin.name + "-Quality", children: [{ name: player.locales.get("Auto"), default: abrEnabled, value: -1 }].concat(settings), onChange: (_ref, dom) => { let value = _ref.value; const isAuto = value == -1; instance.configure({ abr: { enabled: isAuto } }); if (!isAuto) { dom.textContent = this.getResolutionLabel_(value, []); instance.selectVariantTrack( value, /* clearBuffer */ true ); } else { dom.textContent = autoText; } } }); }; this.setupAudioSelection = (player, instance) => { const audioTracks = instance.getAudioTracks(); if (!(audioTracks.length > 1)) return; const levels = audioTracks.sort((a, b) => { return a.language.localeCompare(b.language); }).map((level) => { var _a, _b; return { //@ts-expect-error name: level.language + " " + (((_b = (_a = ShakaPlugin.library.util.MimeUtils).getNormalizedCodec) == null ? void 0 : _b.call(_a, level.codecs)) || level.codecs), default: level.active, value: level }; }); this.settingUpdater({ player, name: "Language", icon: player.context.ui.icons.lang, settings: levels, onChange(_ref2) { let value = _ref2.value; instance.selectAudioTrack(value); } }); }; this.setupTextSelection = (player, instance) => { const tracks = instance.getTextTracks(); if (!(tracks.length > 1)) return; const isTextTrackVisible = instance.isTextTrackVisible(); const levels = [{ name: player.locales.get("Off"), default: !isTextTrackVisible, value: -1 }].concat(tracks.sort((a, b) => { return a.language.localeCompare(b.language); }).map((level) => { return { name: level.language, default: isTextTrackVisible && level.active, value: level }; })); this.settingUpdater({ player, name: "Subtitle", icon: player.context.ui.icons.lang, settings: levels, onChange(_ref3) { let value = _ref3.value; if (value != -1) instance.selectTextTrack(value); instance.setTextTrackVisibility(value != -1); } }); }; Object.assign(this.options, options); } apply(player) { this.player = player; return this; } async load(player, source) { var _a, _b; if (!this.options.matcher(source)) return false; const _this$options = this.options, library = _this$options.library, config = _this$options.config, requestFilter = _this$options.requestFilter, qualityControl = _this$options.qualityControl, audioControl = _this$options.audioControl, textControl = _this$options.textControl, qualityControlType = _this$options.qualityControlType; if (!ShakaPlugin.library) { ShakaPlugin.library = globalThis.shaka || (library ? await loadSDK(library, "shaka") : (await import("shaka-player/dist/shaka-player.compiled")).default); ShakaPlugin.library.polyfill.installAll(); } const ShakaPlayer = ShakaPlugin.library.Player; if (!ShakaPlayer.isBrowserSupported()) return false; this.instance = new ShakaPlayer(); await this.instance.attach(player.$video); if (config) { this.instance.configure(config); } if (requestFilter) { (_a = this.instance.getNetworkingEngine()) == null ? void 0 : _a.registerRequestFilter(requestFilter); } const eventManager = this.instance.eventManager = new ShakaPlugin.library.util.EventManager(); eventManager.listen(this.instance, "loading", (event) => { player.emit("loading", event); }); eventManager.listen(this.instance, "loaded", (event) => { player.emit("loaded", event); }); eventManager.listen(this.instance, "error", (event) => { player.emit("error", Object.assign({ pluginName: ShakaPlugin.name }, event)); }); eventManager.listenOnce(player.$video, "seeking", () => { setTimeout(() => { player.emit("seeked"); }); }); try { await this.instance.load(source.src); } catch (error) { player.emit("error", Object.assign({ pluginName: ShakaPlugin.name }, error)); } if (player.options.isLive) { eventManager.listenOnce(player.$video, "loadedmetadata", () => { player.$video.currentTime = this.seekRange.end; }); const button = (_b = player.$root.querySelector('[aria-label="time"')) == null ? void 0 : _b.parentElement; const dot = button == null ? void 0 : button.firstElementChild; if (button && dot) { eventManager.listen(button, "click", () => { player.$video.currentTime = this.seekRange.end; }); const backText = player.locales.get("Back to Live"); const updateIsLive = () => { const timeBehindLiveEdge = this.seekRange.end - player.$video.currentTime; if (timeBehindLiveEdge > 5) { dot.style.backgroundColor = "#ccc"; button.ariaLabel = backText; } else { dot.style.cssText = ""; button.removeAttribute("aria-label"); } }; this.instance.eventManager.listen(player.$video, "timeupdate", updateIsLive); } Object.defineProperty(player.$video, "duration", { get: () => { if (this.instance) return this._duration; return player.$video.duration; } }); Object.defineProperty(player, "currentTime", { get: () => { if (this.instance) return this.getCurrentTime(); else return player.$video.currentTime; } }); Object.defineProperty(player, "seek", { value: (v) => { if (this.instance) player.$video.currentTime = this.seekRange.start + v; else player.$video.currentTime = v; } }); } if (player.context.ui) { if (qualityControl) { this.setupQuality(player, this.instance, qualityControlType); } if (audioControl) { this.setupAudioSelection(player, this.instance); } if (textControl) { this.setupTextSelection(player, this.instance); } } return this; } getCurrentTime() { if (!this.instance) return 0; const mediaElement = this.instance.getMediaElement(); return mediaElement ? mediaElement.currentTime - this.seekRange.start : 0; } get seekRange() { if (!this.instance) return { start: 0, end: 0 }; return this.instance.seekRange(); } get _duration() { if (!this.instance) return 0; return this.seekRange.end - this.seekRange.start; } async destroy() { var _a, _b, _c; ["Quality", "Language", "Subtitle"].forEach((it) => this.player.context.ui.setting.unregister(ShakaPlugin.name + "-" + it)); this.player.context.ui.menu.unregister(ShakaPlugin.name + "-Quality"); (_a = this.instance) == null ? void 0 : _a.eventManager.removeAll(); await ((_b = this.instance) == null ? void 0 : _b.unload()); await ((_c = this.instance) == null ? void 0 : _c.destroy()); this.instance = void 0; } settingUpdater(arg) { const name = arg.name, icon = arg.icon, onChange = arg.onChange, player = arg.player, settings = arg.settings; player.context.ui.setting.unregister(ShakaPlugin.name + "-" + name); player.context.ui.setting.register({ name: player.locales.get(name), icon, onChange, type: "selector", key: ShakaPlugin.name + "-" + name, children: settings }); } getResolutionLabel_(track, tracks) { const trackHeight = track.height || 0; const trackWidth = track.width || 0; let height = trackHeight; const aspectRatio = trackWidth / trackHeight; if (aspectRatio > 16 / 9) { height = Math.round(trackWidth * 9 / 16); } let text = height + "p"; if (height == 2160) { text = "4K"; } const frameRates = /* @__PURE__ */ new Set(); for (const item of tracks) { if (item.frameRate) { frameRates.add(Math.round(item.frameRate)); } } if (frameRates.size > 1) { const frameRate = track.frameRate; if (frameRate && (frameRate >= 50 || frameRate <= 20)) { text += Math.round(frameRate); } } if (track.hdr == "PQ" || track.hdr == "HLG") { text += " (HDR)"; } if (track.videoLayout == "CH-STEREO") { text += " (3D)"; } const hasDuplicateResolution = tracks.some((otherTrack) => { return otherTrack != track && otherTrack.height == track.height; }); if (hasDuplicateResolution && this.options.qualityControlType == "setting") { const hasDuplicateBandwidth = tracks.some((otherTrack) => { return otherTrack != track && otherTrack.height == track.height && (otherTrack.videoBandwidth || otherTrack.bandwidth) == (track.videoBandwidth || track.bandwidth); }); if (!hasDuplicateBandwidth) { const bandwidth = track.videoBandwidth || track.bandwidth; text += " (" + Math.round(bandwidth / 1e3) + " kbits/s)"; } } return text; } } function create(options) { return new ShakaPlugin(options); } export { create as default };