@oplayer/shaka
Version:
shaka-player plugin for oplayer
371 lines (370 loc) • 13.5 kB
JavaScript
/**
* name: @oplayer/shaka
* version: v1.2.26-beta.6
* 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.6";
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.js")).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, "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
};