nativeshell.js 7.6 KB

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