nativeshell.js 8.5 KB

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