embyToLocalPlayer

需要 Python。调用本地播放器,并回传播放记录。支持:纯本地|网络|持久性缓存|下载。适配 Jellyfin Plex。

当前为 2022-10-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name embyToLocalPlayer
  3. // @name:zh-CN embyToLocalPlayer
  4. // @name:en embyToLocalPlayer
  5. // @namespace https://github.com/kjtsune/embyToLocalPlayer
  6. // @version 1.1.0
  7. // @description 需要 Python。调用本地播放器,并回传播放记录。支持:纯本地|网络|持久性缓存|下载。适配 Jellyfin Plex。
  8. // @description:zh-CN 需要 Python。调用本地播放器,并回传播放记录。支持:纯本地|网络|持久性缓存|下载。适配 Jellyfin Plex。
  9. // @description:en Require Python. Play by disk or network. Update watch history to emby server (PotPlayer mpv IINA MPC VLC). Support Jellyfin Plex.
  10. // @author Kjtsune
  11. // @match *://*/web/index.html*
  12. // @icon https://www.google.com/s2/favicons?sz=64&domain=emby.media
  13. // @grant unsafeWindow
  14. // @grant GM_xmlhttpRequest
  15. // @grant GM_registerMenuCommand
  16. // @grant GM_unregisterMenuCommand
  17. // @run-at document-start
  18. // @license MIT
  19. // ==/UserScript==
  20. 'use strict';
  21. /*
  22. * **如何更新**:
  23. 将 `_config.ini` 重命名为 `.ini`,其他全删除。再次 github 下载解压当前文件夹。(`.ini` 优先于 `_config.ini` )
  24. * 以前通过 `ahk` 自启的用户,运行`_debug.bat ` 重新添加启动项并手动删除旧的就可以。
  25.  
  26. 2022-10-24
  27. 1. 增加持久性缓存(边下边播)详见 FAQ。
  28. 2. 附带下载功能及下载管理。
  29.  
  30. 2022-10-15:
  31. 1. Windows:`_debug.bat` 增加一键后台自启,减少 ahk 依赖。
  32. 2. 不再将内置字幕转为外挂字幕。正常播放器都可以设置字幕语言优先顺序。
  33. 3. 修复 emby 4.7.8 播放时时间被重置。
  34.  
  35. 2022-10-06:
  36. 1. 增加 macOS 支持。mpv VLC。
  37. 2. 增加 IINA 支持。
  38. 3. 修复 Linux VLC 播放时间获取。
  39.  
  40. 2022-09-27:
  41. 1. 增加 弹弹play 支持。(动漫弹幕播放器)
  42. 2. 增加 网络模式 与 读盘模式 混合功能。
  43.  
  44. 2022-09-24:
  45. 1. 增加 Plex 支持。
  46. 2. _debug.ahk 可创建开机启动项
  47.  
  48. 2022-09-21:
  49. 1. 增加 PotPlayer 回传进度支持。
  50. 2. 增加 VLC 回传进度支持。
  51. 3. 修复首次启动时系统编码判断问题。
  52. 4. 修复 `.ini` 被记事本修改后可能编码错误。
  53. */
  54.  
  55. function switchLocalStorage(key, defaultValue = 'true', trueValue = 'true', falseValue = 'false') {
  56. if (key in localStorage) {
  57. let value = (localStorage.getItem(key) === trueValue) ? falseValue : trueValue;
  58. localStorage.setItem(key, value);
  59. } else {
  60. localStorage.setItem(key, defaultValue)
  61. }
  62. console.log('switchLocalStorage ', key, ' to ', localStorage.getItem(key))
  63. }
  64.  
  65. function setModeSwitchMenu(storageKey, menuStart = '', menuEnd = '', defaultValue = '关闭', trueValue = '开启', falseValue = '关闭') {
  66. let switchNameMap = { 'true': trueValue, 'false': falseValue, null: defaultValue };
  67. let menuId = GM_registerMenuCommand(menuStart + switchNameMap[localStorage.getItem(storageKey)] + menuEnd, clickMenu);
  68.  
  69. function clickMenu() {
  70. GM_unregisterMenuCommand(menuId);
  71. switchLocalStorage(storageKey)
  72. menuId = GM_registerMenuCommand(menuStart + switchNameMap[localStorage.getItem(storageKey)] + menuEnd, clickMenu);
  73. }
  74.  
  75. }
  76.  
  77. const originFetch = fetch;
  78. unsafeWindow.fetch = async (url, request) => {
  79. // console.log('%c%o%s', 'background:yellow;', url, ' MYLOG')
  80. if (url.indexOf('/PlaybackInfo?UserId') > -1 && url.indexOf('IsPlayback=true') > -1
  81. && localStorage.getItem('webPlayerEnable') != 'true') {
  82. let response = await originFetch(url, request);
  83. let data = await response.clone().json()
  84. embyToLocalPlayer(url, request, data);
  85. } else {
  86. // console.log('%c%o%s', "color:orange;", 'url ', url)
  87. return originFetch(url, request);
  88. }
  89. }
  90.  
  91. async function getItemInfo(playbackUrl, request) {
  92. let response = await fetch(playbackUrl, request);
  93. if (response.ok) {
  94. return await response.json();
  95. } else {
  96. throw new Error(response.statusText);
  97. }
  98. }
  99.  
  100. async function embyToLocalPlayer(playbackUrl, request, response) {
  101. let data = {
  102. playbackData: response,
  103. playbackUrl: playbackUrl,
  104. request: request,
  105. mountDiskEnable: localStorage.getItem('mountDiskEnable'),
  106.  
  107. };
  108. sendDataToLocalServer(data, 'embyToLocalPlayer');
  109. }
  110.  
  111. function sendDataToLocalServer(data, path) {
  112. let url = `http://127.0.0.1:58000/${path}/`
  113. GM_xmlhttpRequest({
  114. method: "POST",
  115. url: url,
  116. data: JSON.stringify(data),
  117. headers: {
  118. "Content-Type": "application/json"
  119. },
  120. });
  121. }
  122.  
  123. function initXMLHttpRequest() {
  124. let open = XMLHttpRequest.prototype.open;
  125. XMLHttpRequest.prototype.open = function (...args) {
  126. // 正常请求不匹配的网址
  127. // console.log(args, "---all_args");
  128. let url = args[1]
  129. if (url.indexOf('playQueues?type=video') == -1) {
  130. return open.apply(this, args);
  131. }
  132. // 请求前拦截
  133. if (url.indexOf('playQueues?type=video') != -1
  134. && localStorage.getItem('webPlayerEnable') != 'true') {
  135. // console.log(args, "-----args");
  136. fetch(url, {
  137. method: args[0],
  138. headers: {
  139. 'Accept': 'application/json',
  140. }
  141. })
  142. .then(response => response.json())
  143. .then((res) => {
  144. let data = {
  145. playbackData: res,
  146. playbackUrl: url,
  147. mountDiskEnable: localStorage.getItem('mountDiskEnable'),
  148.  
  149. };
  150. sendDataToLocalServer(data, 'plexToLocalPlayer');
  151. // console.log(data, "-----data")
  152. });
  153. return;
  154. }
  155. return open.apply(this, args);
  156. }
  157. }
  158.  
  159. // 初始化请求并拦截 plex
  160. initXMLHttpRequest()
  161.  
  162. setModeSwitchMenu('webPlayerEnable', '网页播放模式已经 ')
  163. setModeSwitchMenu('mountDiskEnable', '读取硬盘模式已经 ')