|
@@ -1,4 +1,5 @@
|
|
|
-const viewdata = JSON.parse(window.atob("@@data@@"));
|
|
|
+const jmpInfo = JSON.parse(window.atob("@@data@@"));
|
|
|
+window.jmpInfo = jmpInfo;
|
|
|
|
|
|
const features = [
|
|
|
"filedownload",
|
|
@@ -40,7 +41,7 @@ function loadScript(src) {
|
|
|
// Add plugin loaders
|
|
|
for (const plugin of plugins) {
|
|
|
window[plugin] = async () => {
|
|
|
- await loadScript(`${viewdata.scriptPath}${plugin}.js`);
|
|
|
+ await loadScript(`${jmpInfo.scriptPath}${plugin}.js`);
|
|
|
return window["_" + plugin];
|
|
|
};
|
|
|
}
|
|
@@ -77,7 +78,7 @@ function getDeviceProfile() {
|
|
|
}
|
|
|
];
|
|
|
|
|
|
- if (viewdata.force_transcode_hdr) {
|
|
|
+ if (jmpInfo.settings.video.force_transcode_hdr) {
|
|
|
CodecProfiles.push({
|
|
|
'Type': 'Video',
|
|
|
'Conditions': [
|
|
@@ -90,6 +91,82 @@ function getDeviceProfile() {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ 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,
|
|
@@ -102,14 +179,18 @@ function getDeviceProfile() {
|
|
|
'Type': 'Video',
|
|
|
'Protocol': 'hls',
|
|
|
'AudioCodec': 'aac,mp3,ac3,opus,flac,vorbis',
|
|
|
- 'VideoCodec': viewdata.allow_transcode_to_hevc
|
|
|
- ? 'h264,h265,hevc,mpeg4,mpeg2video'
|
|
|
+ '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': [{'Type': 'Video'}, {'Type': 'Audio'}, {'Type': 'Photo'}],
|
|
|
+ DirectPlayProfiles,
|
|
|
'ResponseProfiles': [],
|
|
|
'ContainerProfiles': [],
|
|
|
CodecProfiles,
|
|
@@ -134,6 +215,11 @@ function getDeviceProfile() {
|
|
|
|
|
|
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
|
|
@@ -157,15 +243,80 @@ async function createApi() {
|
|
|
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() {
|
|
|
- window.apiPromise = createApi();
|
|
|
- (async () => {
|
|
|
- window.api = await window.apiPromise;
|
|
|
- })();
|
|
|
- },
|
|
|
+ init() {},
|
|
|
getDefaultLayout() {
|
|
|
- return viewdata.mode;
|
|
|
+ return jmpInfo.mode;
|
|
|
},
|
|
|
supports(command) {
|
|
|
return features.includes(command.toLowerCase());
|
|
@@ -179,7 +330,7 @@ window.NativeShell.AppHost = {
|
|
|
return navigator.userAgent.split(" ")[1];
|
|
|
},
|
|
|
deviceName() {
|
|
|
- return viewdata.deviceName;
|
|
|
+ return jmpInfo.deviceName;
|
|
|
},
|
|
|
exit() {
|
|
|
window.api.system.exit();
|
|
@@ -187,9 +338,7 @@ window.NativeShell.AppHost = {
|
|
|
};
|
|
|
|
|
|
async function showSettingsModal() {
|
|
|
- let settings = await new Promise(resolve => {
|
|
|
- window.api.settings.settingDescriptions(resolve);
|
|
|
- });
|
|
|
+ await initCompleted;
|
|
|
|
|
|
const modalContainer = document.createElement("div");
|
|
|
modalContainer.className = "dialogContainer";
|
|
@@ -220,7 +369,8 @@ async function showSettingsModal() {
|
|
|
modalContents.style.marginBottom = "6.2em";
|
|
|
modalContainer2.appendChild(modalContents);
|
|
|
|
|
|
- for (let section of settings) {
|
|
|
+ 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;
|
|
@@ -232,23 +382,22 @@ async function showSettingsModal() {
|
|
|
group.innerHTML = "";
|
|
|
}
|
|
|
|
|
|
- const values = await new Promise(resolve => {
|
|
|
- window.api.settings.allValues(section.key, resolve);
|
|
|
- });
|
|
|
+ const values = jmpInfo.settings[section];
|
|
|
+ const settings = jmpInfo.settingsDescriptions[section];
|
|
|
|
|
|
const legend = document.createElement("legend");
|
|
|
const legendHeader = document.createElement("h2");
|
|
|
- legendHeader.textContent = section.key;
|
|
|
+ legendHeader.textContent = section;
|
|
|
legendHeader.style.textTransform = "capitalize";
|
|
|
legend.appendChild(legendHeader);
|
|
|
- if (section.key == "plugins") {
|
|
|
+ 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 section.settings) {
|
|
|
+ for (const setting of settings) {
|
|
|
const label = document.createElement("label");
|
|
|
label.className = "inputContainer";
|
|
|
label.style.marginBottom = "1.8em";
|
|
@@ -264,8 +413,8 @@ async function showSettingsModal() {
|
|
|
opt.value = option.value;
|
|
|
opt.selected = option.value == values[setting.key];
|
|
|
let optionName = option.title;
|
|
|
- const swTest = `${section.key}.${setting.key}.`;
|
|
|
- const swTest2 = `${section.key}.`;
|
|
|
+ const swTest = `${section}.${setting.key}.`;
|
|
|
+ const swTest2 = `${section}.`;
|
|
|
if (optionName.startsWith(swTest)) {
|
|
|
optionName = optionName.substring(swTest.length);
|
|
|
} else if (optionName.startsWith(swTest2)) {
|
|
@@ -275,16 +424,7 @@ async function showSettingsModal() {
|
|
|
control.appendChild(opt);
|
|
|
}
|
|
|
control.addEventListener("change", async (e) => {
|
|
|
- await new Promise(resolve => {
|
|
|
- window.api.settings.setValue(section.key, setting.key, safeValues[e.target.value], resolve);
|
|
|
- });
|
|
|
-
|
|
|
- if (setting.key == "devicetype") {
|
|
|
- section = (await new Promise(resolve => {
|
|
|
- window.api.settings.settingDescriptions(resolve);
|
|
|
- })).filter(x => x.key == section.key)[0];
|
|
|
- createSection(true);
|
|
|
- }
|
|
|
+ jmpInfo.settings[section][setting.key] = safeValues[e.target.value];
|
|
|
});
|
|
|
const labelText = document.createElement('label');
|
|
|
labelText.className = "inputLabel";
|
|
@@ -296,7 +436,7 @@ async function showSettingsModal() {
|
|
|
control.type = "checkbox";
|
|
|
control.checked = values[setting.key];
|
|
|
control.addEventListener("change", e => {
|
|
|
- window.api.settings.setValue(section.key, setting.key, e.target.checked);
|
|
|
+ jmpInfo.settings[section][setting.key] = e.target.checked;
|
|
|
});
|
|
|
label.appendChild(control);
|
|
|
label.appendChild(document.createTextNode(" " + setting.key));
|
|
@@ -304,14 +444,19 @@ async function showSettingsModal() {
|
|
|
group.appendChild(label);
|
|
|
}
|
|
|
};
|
|
|
+ settingUpdateHandlers[section] = () => createSection(true);
|
|
|
createSection();
|
|
|
}
|
|
|
|
|
|
- const savedServer = await new Promise(resolve => {
|
|
|
- window.api.settings.value('main', 'userWebClient', resolve);
|
|
|
- });
|
|
|
+ const onSectionUpdate = (section) => {
|
|
|
+ if (section in settingUpdateHandlers) {
|
|
|
+ settingUpdateHandlers[section]();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ jmpInfo.settingsDescriptionsUpdate.push(onSectionUpdate);
|
|
|
+ jmpInfo.settingsUpdate.push(onSectionUpdate);
|
|
|
|
|
|
- if (savedServer) {
|
|
|
+ if (jmpInfo.settings.main.userWebClient) {
|
|
|
const group = document.createElement("fieldset");
|
|
|
group.className = "editItemMetadataForm editMetadataForm dialog-content-centered";
|
|
|
group.style.border = 0;
|
|
@@ -337,10 +482,8 @@ async function showSettingsModal() {
|
|
|
resetSavedServer.style.marginRight = "auto";
|
|
|
resetSavedServer.style.maxWidth = "50%";
|
|
|
resetSavedServer.addEventListener("click", async () => {
|
|
|
- await new Promise(resolve => {
|
|
|
- window.api.settings.setValue('main', 'userWebClient', '', resolve);
|
|
|
- });
|
|
|
- window.location.href = viewdata.scriptPath + "/find-webclient.html";
|
|
|
+ window.jmpInfo.settings.main.userWebClient = '';
|
|
|
+ window.location.href = jmpInfo.scriptPath + "/find-webclient.html";
|
|
|
});
|
|
|
group.appendChild(resetSavedServer);
|
|
|
}
|