nativeshell.js 8.4 KB

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