123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504 |
- const jmpInfo = JSON.parse(window.atob("@@data@@"));
- window.jmpInfo = jmpInfo;
- const features = [
- "filedownload",
- "displaylanguage",
- "htmlaudioautoplay",
- "htmlvideoautoplay",
- "externallinks",
- "clientsettings",
- "multiserver",
- "exitmenu",
- "remotecontrol",
- "fullscreenchange",
- "filedownload",
- "remotevideo",
- "displaymode",
- "screensaver",
- "fileinput"
- ];
- const plugins = [
- 'mpvVideoPlayer',
- 'mpvAudioPlayer',
- 'jmpInputPlugin',
- 'jmpUpdatePlugin',
- 'jellyscrubPlugin',
- 'skipIntroPlugin'
- ];
- function loadScript(src) {
- return new Promise((resolve, reject) => {
- const s = document.createElement('script');
- s.src = src;
- s.onload = resolve;
- s.onerror = reject;
- document.head.appendChild(s);
- });
- }
- // Add plugin loaders
- for (const plugin of plugins) {
- window[plugin] = async () => {
- await loadScript(`${jmpInfo.scriptPath}${plugin}.js`);
- return window["_" + plugin];
- };
- }
- window.NativeShell = {
- openUrl(url, target) {
- window.api.system.openExternalUrl(url);
- },
- downloadFile(downloadInfo) {
- window.api.system.openExternalUrl(downloadInfo.url);
- },
- openClientSettings() {
- showSettingsModal();
- },
- getPlugins() {
- return plugins;
- }
- };
- function getDeviceProfile() {
- const CodecProfiles = [];
- if (jmpInfo.settings.video.force_transcode_dovi) {
- CodecProfiles.push({
- 'Type': 'Video',
- 'Conditions': [
- {
- 'Condition': 'NotEquals',
- 'Property': 'VideoRangeType',
- 'Value': 'DOVI'
- }
- ]
- });
- }
- if (jmpInfo.settings.video.force_transcode_hdr) {
- CodecProfiles.push({
- 'Type': 'Video',
- 'Conditions': [
- {
- 'Condition': 'Equals',
- 'Property': 'VideoRangeType',
- 'Value': 'SDR'
- }
- ]
- });
- }
- if (jmpInfo.settings.video.force_transcode_hi10p) {
- CodecProfiles.push({
- 'Type': 'Video',
- 'Conditions': [
- {
- 'Condition': 'LessThanEqual',
- 'Property': 'VideoBitDepth',
- 'Value': '8',
- }
- ]
- });
- }
- if (jmpInfo.settings.video.force_transcode_hevc) {
- CodecProfiles.push({
- 'Type': 'Video',
- 'Codec': 'hevc',
- 'Conditions': [
- {
- 'Condition': 'Equals',
- 'Property': 'Width',
- 'Value': '0',
- }
- ],
- });
- CodecProfiles.push({
- 'Type': 'Video',
- 'Codec': 'h265',
- 'Conditions': [
- {
- 'Condition': 'Equals',
- 'Property': 'Width',
- 'Value': '0',
- }
- ],
- });
- }
- if (jmpInfo.settings.video.force_transcode_av1) {
- CodecProfiles.push({
- 'Type': 'Video',
- 'Codec': 'av1',
- 'Conditions': [
- {
- 'Condition': 'Equals',
- 'Property': 'Width',
- 'Value': '0',
- }
- ],
- });
- }
- if (jmpInfo.settings.video.force_transcode_4k) {
- CodecProfiles.push({
- 'Type': 'Video',
- 'Conditions': [
- {
- 'Condition': 'LessThanEqual',
- 'Property': 'Width',
- 'Value': '1920',
- },
- {
- 'Condition': 'LessThanEqual',
- 'Property': 'Height',
- 'Value': '1080',
- }
- ]
- });
- }
- const DirectPlayProfiles = [{'Type': 'Audio'}, {'Type': 'Photo'}];
- if (!jmpInfo.settings.video.always_force_transcode) {
- DirectPlayProfiles.push({'Type': 'Video'});
- }
- return {
- 'Name': 'Jellyfin Media Player',
- 'MaxStaticBitrate': 1000000000,
- 'MusicStreamingTranscodingBitrate': 1280000,
- 'TimelineOffsetSeconds': 5,
- 'TranscodingProfiles': [
- {'Type': 'Audio'},
- {
- 'Container': 'ts',
- 'Type': 'Video',
- 'Protocol': 'hls',
- 'AudioCodec': 'aac,mp3,ac3,opus,vorbis',
- 'VideoCodec': jmpInfo.settings.video.allow_transcode_to_hevc
- ? (
- jmpInfo.settings.video.prefer_transcode_to_h265
- ? 'h265,hevc,h264,mpeg4,mpeg2video'
- : 'h264,h265,hevc,mpeg4,mpeg2video'
- )
- : 'h264,mpeg4,mpeg2video',
- 'MaxAudioChannels': '6'
- },
- {'Container': 'jpeg', 'Type': 'Photo'}
- ],
- DirectPlayProfiles,
- 'ResponseProfiles': [],
- 'ContainerProfiles': [],
- CodecProfiles,
- 'SubtitleProfiles': [
- {'Format': 'srt', 'Method': 'External'},
- {'Format': 'srt', 'Method': 'Embed'},
- {'Format': 'ass', 'Method': 'External'},
- {'Format': 'ass', 'Method': 'Embed'},
- {'Format': 'sub', 'Method': 'Embed'},
- {'Format': 'sub', 'Method': 'External'},
- {'Format': 'ssa', 'Method': 'Embed'},
- {'Format': 'ssa', 'Method': 'External'},
- {'Format': 'smi', 'Method': 'Embed'},
- {'Format': 'smi', 'Method': 'External'},
- {'Format': 'pgssub', 'Method': 'Embed'},
- {'Format': 'dvdsub', 'Method': 'Embed'},
- {'Format': 'dvbsub', 'Method': 'Embed'},
- {'Format': 'pgs', 'Method': 'Embed'}
- ]
- };
- }
- async function createApi() {
- try {
- // Can't append script until document exists
- await new Promise(resolve => {
- document.addEventListener('DOMContentLoaded', resolve);
- });
- await loadScript('qrc:///qtwebchannel/qwebchannel.js');
- } catch (e) {
- // try clearing out any cached CSPs
- let foundCache = false;
- for (const cache of await caches.keys()) {
- const dataDeleted = await caches.delete(cache);
- if (dataDeleted) {
- foundCache = true;
- }
- }
- if (foundCache) {
- window.location.reload();
- }
- throw e;
- }
- const channel = await new Promise((resolve) => {
- /*global QWebChannel */
- new QWebChannel(window.qt.webChannelTransport, resolve);
- });
- return channel.objects;
- }
- let rawSettings = {};
- Object.assign(rawSettings, jmpInfo.settings);
- const settingsFromStorage = window.sessionStorage.getItem('settings');
- if (settingsFromStorage) {
- rawSettings = JSON.parse(settingsFromStorage);
- Object.assign(jmpInfo.settings, rawSettings);
- }
- const settingsDescriptionsFromStorage = window.sessionStorage.getItem('settingsDescriptions');
- if (settingsDescriptionsFromStorage) {
- jmpInfo.settingsDescriptions = JSON.parse(settingsDescriptionsFromStorage);
- }
- jmpInfo.settingsDescriptionsUpdate = [];
- jmpInfo.settingsUpdate = [];
- window.apiPromise = createApi();
- window.initCompleted = new Promise(async (resolve) => {
- window.api = await window.apiPromise;
- const settingUpdate = (section, key) => (
- (data) => new Promise(resolve => {
- rawSettings[section][key] = data;
- window.sessionStorage.setItem("settings", JSON.stringify(rawSettings));
- window.api.settings.setValue(section, key, data, resolve);
- })
- );
- const setSetting = (section, key) => {
- Object.defineProperty(jmpInfo.settings[section], key, {
- set: settingUpdate(section, key),
- get: () => rawSettings[section][key]
- });
- };
- for (const settingGroup of Object.keys(rawSettings)) {
- jmpInfo.settings[settingGroup] = {};
- for (const setting of Object.keys(rawSettings[settingGroup])) {
- setSetting(settingGroup, setting, jmpInfo.settings[settingGroup][setting]);
- }
- }
- window.api.settings.sectionValueUpdate.connect(
- (section, data) => {
- Object.assign(rawSettings[section], data);
- for (const callback of jmpInfo.settingsUpdate) {
- try {
- callback(section, data);
- } catch (e) {
- console.error("Update handler failed:", e);
- }
- }
- // Settings will be outdated if page reloads, so save them to session storage
- window.sessionStorage.setItem("settings", JSON.stringify(rawSettings));
- }
- );
- window.api.settings.groupUpdate.connect(
- (section, data) => {
- jmpInfo.settingsDescriptions[section] = data.settings;
- for (const callback of jmpInfo.settingsDescriptionsUpdate) {
- try {
- callback(section, data);
- } catch (e) {
- console.error("Description update handler failed:", e);
- }
- }
- // Settings will be outdated if page reloads, so save them to session storage
- window.sessionStorage.setItem("settingsDescriptions", JSON.stringify(jmpInfo.settingsDescriptions));
- }
- );
- resolve();
- });
- window.NativeShell.AppHost = {
- init() {},
- getDefaultLayout() {
- return jmpInfo.mode;
- },
- supports(command) {
- return features.includes(command.toLowerCase());
- },
- getDeviceProfile,
- getSyncProfile: getDeviceProfile,
- appName() {
- return "Jellyfin Media Player";
- },
- appVersion() {
- return navigator.userAgent.split(" ")[1];
- },
- deviceName() {
- return jmpInfo.deviceName;
- },
- exit() {
- window.api.system.exit();
- }
- };
- async function showSettingsModal() {
- await initCompleted;
- const modalContainer = document.createElement("div");
- modalContainer.className = "dialogContainer";
- modalContainer.style.backgroundColor = "rgba(0,0,0,0.5)";
- modalContainer.addEventListener("click", e => {
- if (e.target == modalContainer) {
- modalContainer.remove();
- }
- });
- document.body.appendChild(modalContainer);
- const modalContainer2 = document.createElement("div");
- modalContainer2.className = "focuscontainer dialog dialog-fixedSize dialog-small formDialog opened";
- modalContainer.appendChild(modalContainer2);
- const modalHeader = document.createElement("div");
- modalHeader.className = "formDialogHeader";
- modalContainer2.appendChild(modalHeader);
- const title = document.createElement("h3");
- title.className = "formDialogHeaderTitle";
- title.textContent = "Jellyfin Media Player Settings";
- modalHeader.appendChild(title);
- const modalContents = document.createElement("div");
- modalContents.className = "formDialogContent smoothScrollY";
- modalContents.style.paddingTop = "2em";
- modalContents.style.marginBottom = "6.2em";
- modalContainer2.appendChild(modalContents);
- const settingUpdateHandlers = {};
- for (const section of Object.keys(jmpInfo.settingsDescriptions)) {
- const group = document.createElement("fieldset");
- group.className = "editItemMetadataForm editMetadataForm dialog-content-centered";
- group.style.border = 0;
- group.style.outline = 0;
- modalContents.appendChild(group);
- const createSection = async (clear) => {
- if (clear) {
- group.innerHTML = "";
- }
- const values = jmpInfo.settings[section];
- const settings = jmpInfo.settingsDescriptions[section];
- const legend = document.createElement("legend");
- const legendHeader = document.createElement("h2");
- legendHeader.textContent = section;
- legendHeader.style.textTransform = "capitalize";
- legend.appendChild(legendHeader);
- if (section == "plugins") {
- const legendSubHeader = document.createElement("h4");
- legendSubHeader.textContent = "Plugins are UNOFFICIAL and require a restart to take effect.";
- legend.appendChild(legendSubHeader);
- }
- group.appendChild(legend);
- for (const setting of settings) {
- const label = document.createElement("label");
- label.className = "inputContainer";
- label.style.marginBottom = "1.8em";
- label.style.display = "block";
- label.style.textTransform = "capitalize";
- if (setting.options) {
- const safeValues = {};
- const control = document.createElement("select");
- control.className = "emby-select-withcolor emby-select";
- for (const option of setting.options) {
- safeValues[String(option.value)] = option.value;
- const opt = document.createElement("option");
- opt.value = option.value;
- opt.selected = option.value == values[setting.key];
- let optionName = option.title;
- const swTest = `${section}.${setting.key}.`;
- const swTest2 = `${section}.`;
- if (optionName.startsWith(swTest)) {
- optionName = optionName.substring(swTest.length);
- } else if (optionName.startsWith(swTest2)) {
- optionName = optionName.substring(swTest2.length);
- }
- opt.appendChild(document.createTextNode(optionName));
- control.appendChild(opt);
- }
- control.addEventListener("change", async (e) => {
- jmpInfo.settings[section][setting.key] = safeValues[e.target.value];
- });
- const labelText = document.createElement('label');
- labelText.className = "inputLabel";
- labelText.textContent = setting.key + ": ";
- label.appendChild(labelText);
- label.appendChild(control);
- } else {
- const control = document.createElement("input");
- control.type = "checkbox";
- control.checked = values[setting.key];
- control.addEventListener("change", e => {
- jmpInfo.settings[section][setting.key] = e.target.checked;
- });
- label.appendChild(control);
- label.appendChild(document.createTextNode(" " + setting.key));
- }
- group.appendChild(label);
- }
- };
- settingUpdateHandlers[section] = () => createSection(true);
- createSection();
- }
- const onSectionUpdate = (section) => {
- if (section in settingUpdateHandlers) {
- settingUpdateHandlers[section]();
- }
- };
- jmpInfo.settingsDescriptionsUpdate.push(onSectionUpdate);
- jmpInfo.settingsUpdate.push(onSectionUpdate);
- if (jmpInfo.settings.main.userWebClient) {
- const group = document.createElement("fieldset");
- group.className = "editItemMetadataForm editMetadataForm dialog-content-centered";
- group.style.border = 0;
- group.style.outline = 0;
- modalContents.appendChild(group);
- const legend = document.createElement("legend");
- const legendHeader = document.createElement("h2");
- legendHeader.textContent = "Saved Server";
- legend.appendChild(legendHeader);
- const legendSubHeader = document.createElement("h4");
- legendSubHeader.textContent = (
- "The server you first connected to is your saved server. " +
- "It provides the web client for Jellyfin Media Player in the absence of a bundled one. " +
- "You can use this option to change it to another one. This does NOT log you off."
- );
- legend.appendChild(legendSubHeader);
- group.appendChild(legend);
- const resetSavedServer = document.createElement("button");
- resetSavedServer.className = "raised button-cancel block btnCancel emby-button";
- resetSavedServer.textContent = "Reset Saved Server"
- resetSavedServer.style.marginLeft = "auto";
- resetSavedServer.style.marginRight = "auto";
- resetSavedServer.style.maxWidth = "50%";
- resetSavedServer.addEventListener("click", async () => {
- window.jmpInfo.settings.main.userWebClient = '';
- window.location.href = jmpInfo.scriptPath + "/find-webclient.html";
- });
- group.appendChild(resetSavedServer);
- }
- const closeContainer = document.createElement("div");
- closeContainer.className = "formDialogFooter";
- modalContents.appendChild(closeContainer);
- const close = document.createElement("button");
- close.className = "raised button-cancel block btnCancel formDialogFooterItem emby-button";
- close.textContent = "Close"
- close.addEventListener("click", () => {
- modalContainer.remove();
- });
- closeContainer.appendChild(close);
- }
|