nativeshell.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. const viewdata = JSON.parse(window.atob("@@data@@"));
  2. const features = [
  3. "filedownload",
  4. "displaylanguage",
  5. "htmlaudioautoplay",
  6. "htmlvideoautoplay",
  7. "externallinks",
  8. "clientsettings",
  9. "multiserver",
  10. "remotecontrol",
  11. "fullscreenchange",
  12. "filedownload",
  13. "remotevideo",
  14. "displaymode",
  15. "screensaver",
  16. "fileinput"
  17. ];
  18. const plugins = [
  19. 'mpvVideoPlayer',
  20. 'mpvAudioPlayer',
  21. 'jmpInputPlugin',
  22. 'jmpUpdatePlugin'
  23. ];
  24. function loadScript(src) {
  25. return new Promise((resolve, reject) => {
  26. const s = document.createElement('script');
  27. s.src = src;
  28. s.onload = resolve;
  29. s.onerror = reject;
  30. document.head.appendChild(s);
  31. });
  32. }
  33. // Add plugin loaders
  34. for (const plugin of plugins) {
  35. window[plugin] = async () => {
  36. await loadScript(`${viewdata.scriptPath}${plugin}.js`);
  37. return window["_" + plugin];
  38. };
  39. }
  40. window.NativeShell = {
  41. openUrl(url, target) {
  42. window.api.system.openExternalUrl(url);
  43. },
  44. downloadFile(downloadInfo) {
  45. window.api.system.openExternalUrl(downloadInfo.url);
  46. },
  47. openClientSettings() {
  48. showSettingsModal();
  49. },
  50. getPlugins() {
  51. return plugins;
  52. }
  53. };
  54. function getDeviceProfile() {
  55. return {
  56. 'Name': 'Jellyfin Media Player',
  57. 'MusicStreamingTranscodingBitrate': 1280000,
  58. 'TimelineOffsetSeconds': 5,
  59. 'TranscodingProfiles': [
  60. {'Type': 'Audio'},
  61. {
  62. 'Container': 'ts',
  63. 'Type': 'Video',
  64. 'Protocol': 'hls',
  65. 'AudioCodec': 'aac,mp3,ac3,opus,flac,vorbis',
  66. 'VideoCodec': 'h264,h265,hevc,mpeg4,mpeg2video',
  67. 'MaxAudioChannels': '6'
  68. },
  69. {'Container': 'jpeg', 'Type': 'Photo'}
  70. ],
  71. 'DirectPlayProfiles': [{'Type': 'Video'}, {'Type': 'Audio'}, {'Type': 'Photo'}],
  72. 'ResponseProfiles': [],
  73. 'ContainerProfiles': [],
  74. 'CodecProfiles': [],
  75. 'SubtitleProfiles': [
  76. {'Format': 'srt', 'Method': 'External'},
  77. {'Format': 'srt', 'Method': 'Embed'},
  78. {'Format': 'ass', 'Method': 'External'},
  79. {'Format': 'ass', 'Method': 'Embed'},
  80. {'Format': 'sub', 'Method': 'Embed'},
  81. {'Format': 'sub', 'Method': 'External'},
  82. {'Format': 'ssa', 'Method': 'Embed'},
  83. {'Format': 'ssa', 'Method': 'External'},
  84. {'Format': 'smi', 'Method': 'Embed'},
  85. {'Format': 'smi', 'Method': 'External'},
  86. {'Format': 'pgssub', 'Method': 'Embed'},
  87. {'Format': 'dvdsub', 'Method': 'Embed'},
  88. {'Format': 'pgs', 'Method': 'Embed'}
  89. ]
  90. };
  91. }
  92. async function createApi() {
  93. await loadScript('qrc:///qtwebchannel/qwebchannel.js');
  94. const channel = await new Promise((resolve) => {
  95. /*global QWebChannel */
  96. new QWebChannel(window.qt.webChannelTransport, resolve);
  97. });
  98. return channel.objects;
  99. }
  100. window.NativeShell.AppHost = {
  101. init() {
  102. window.apiPromise = createApi();
  103. (async () => {
  104. window.api = await window.apiPromise;
  105. })();
  106. },
  107. getDefaultLayout() {
  108. return viewdata.mode;
  109. },
  110. supports(command) {
  111. return features.includes(command.toLowerCase());
  112. },
  113. getDeviceProfile,
  114. getSyncProfile: getDeviceProfile,
  115. appName() {
  116. return "Jellyfin Media Player";
  117. },
  118. appVersion() {
  119. return navigator.userAgent.split(" ")[1];
  120. },
  121. deviceName() {
  122. return viewdata.deviceName;
  123. }
  124. };
  125. async function showSettingsModal() {
  126. let settings = await new Promise(resolve => {
  127. window.api.settings.settingDescriptions(resolve);
  128. });
  129. const modalContainer = document.createElement("div");
  130. Object.assign(modalContainer.style, {
  131. position: "fixed",
  132. top: 10,
  133. bottom: 10,
  134. left: 10,
  135. right: 10,
  136. zIndex: 2000,
  137. width: "100%",
  138. height: "100%",
  139. backgroundColor: "#000000C0",
  140. display: "flex",
  141. justifyContent: "center"
  142. });
  143. modalContainer.addEventListener("click", e => {
  144. if (e.target == modalContainer) {
  145. modalContainer.remove();
  146. }
  147. });
  148. document.body.appendChild(modalContainer);
  149. const modalContainer2 = document.createElement("div");
  150. Object.assign(modalContainer2.style, {
  151. width: "100%",
  152. maxWidth: "500px",
  153. overflowY: "auto",
  154. margin: "20px",
  155. height: "auto"
  156. });
  157. modalContainer.appendChild(modalContainer2);
  158. const modal = document.createElement("div");
  159. modal.className = "jmp-settings-modal";
  160. Object.assign(modal.style, {
  161. width: "100%",
  162. padding: "20px",
  163. boxSizing: "border-box",
  164. backgroundColor: "#202020",
  165. height: "min-content",
  166. color: "#fff"
  167. });
  168. modalContainer2.appendChild(modal);
  169. const title = document.createElement("h1");
  170. title.textContent = "Jellyfin Media Player Settings";
  171. modal.appendChild(title);
  172. for (let section of settings) {
  173. const group = document.createElement("fieldset");
  174. modal.appendChild(group);
  175. const createSection = async (clear) => {
  176. if (clear) {
  177. group.innerHTML = "";
  178. }
  179. const values = await new Promise(resolve => {
  180. window.api.settings.allValues(section.key, resolve);
  181. });
  182. const legend = document.createElement("legend");
  183. legend.textContent = section.key;
  184. group.appendChild(legend);
  185. for (const setting of section.settings) {
  186. const label = document.createElement("label");
  187. label.style.display = "block";
  188. if (setting.options) {
  189. const safeValues = {};
  190. const control = document.createElement("select");
  191. control.style.maxWidth = "350px";
  192. for (const option of setting.options) {
  193. safeValues[String(option.value)] = option.value;
  194. const opt = document.createElement("option");
  195. opt.value = option.value;
  196. opt.selected = option.value == values[setting.key];
  197. let optionName = option.title;
  198. const swTest = `${section.key}.${setting.key}.`;
  199. const swTest2 = `${section.key}.`;
  200. if (optionName.startsWith(swTest)) {
  201. optionName = optionName.substring(swTest.length);
  202. } else if (optionName.startsWith(swTest2)) {
  203. optionName = optionName.substring(swTest2.length);
  204. }
  205. opt.appendChild(document.createTextNode(optionName));
  206. control.appendChild(opt);
  207. }
  208. control.addEventListener("change", async (e) => {
  209. await new Promise(resolve => {
  210. window.api.settings.setValue(section.key, setting.key, safeValues[e.target.value], resolve);
  211. });
  212. if (setting.key == "devicetype") {
  213. section = (await new Promise(resolve => {
  214. window.api.settings.settingDescriptions(resolve);
  215. })).filter(x => x.key == section.key)[0];
  216. createSection(true);
  217. }
  218. });
  219. label.appendChild(document.createTextNode(setting.key + " "));
  220. label.appendChild(control);
  221. } else {
  222. const control = document.createElement("input");
  223. control.type = "checkbox";
  224. control.checked = values[setting.key];
  225. control.addEventListener("change", e => {
  226. window.api.settings.setValue(section.key, setting.key, e.target.checked);
  227. });
  228. label.appendChild(control);
  229. label.appendChild(document.createTextNode(" " + setting.key));
  230. }
  231. group.appendChild(label);
  232. }
  233. };
  234. createSection();
  235. }
  236. const closeContainer = document.createElement("div");
  237. Object.assign(closeContainer.style, {
  238. width: "100%",
  239. display: "flex",
  240. justifyContent: "flex-end",
  241. paddingTop: "10px"
  242. });
  243. modal.appendChild(closeContainer);
  244. const close = document.createElement("button");
  245. close.textContent = "Close"
  246. close.addEventListener("click", () => {
  247. modalContainer.remove();
  248. });
  249. closeContainer.appendChild(close);
  250. }