Play-With-MPV

使用 MPV 播放网页上的视频

当前为 2022-09-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Play-With-MPV
  3. // @name:zh 使用 MPV 播放
  4. // @description 使用 MPV 播放网页上的视频
  5. // @namespace https://github.com/LuckyPuppy514
  6. // @version 2.0.2
  7. // @commit v1.2.1 新增 powershell 脚本升级提醒功能
  8. // @commit v1.2.2 修复 youtube 标题带 | 导致错误脚本升级提醒
  9. // @commit v1.2.3 修改 imomoe 域名
  10. // @commit v1.3.0 新增域名:www.6dm.cc, www.dmla.cc(第一线路:大部分支持,其他线路:小部分支持)
  11. // @commit v1.3.0 新增域名:www.dm233.me(线路III:大部分支持,其他线路:大部分不支持)
  12. // @commit v1.3.0 代码重构,使用继承方便后续添加网站支持
  13. // @commit v1.4.0 b站bug修复:标题带数字,解析出错,修复并优化了获取视频链接的速度
  14. // @commit v1.4.0 新增对plex支持(本地:*://*/web/index.html*,远程:https://app.plex.tv/desktop/*)
  15. // @commit v1.4.1 修复b站番剧播放目录为列表时,无法获取正确集数的bug
  16. // @commit v1.4.2 修复b站番剧播放的bug
  17. // @commit v1.4.3 修改cdn为unpkg,某些网络无法访问cdn,导致js加载失败(有问题,请自行修改:unpkg.com => cdn.jsdelivr.net/npm)
  18. // @commit v1.4.4 www.dmla.cc 域名变更为:www.dmlaa.com
  19. // @commit v1.4.5 ddrk.me 域名变更为:ddys.tv
  20. // @commit v1.5.0 代码优化,去除 powershell 脚本,只需添加注册表信息即可
  21. // @commit v1.5.1 B站添加 cid 参数,配合 https://github.com/itKelis/MPV-Play-BiliBili-Comments 可实现弹幕功能
  22. // @commit v1.5.2 注册表代码升级,支持中文标题
  23. // @commit v1.5.3 添加低端影视备用域名
  24. // @commit v2.0.0 代码重构:1. 新增对B站av号视频支持;2. B站,油管,低端影视同步网页播放时间;3. 新增MPV路径设置,方便生成注册表;4. 新增Youtube代理设置;5. 减少暂停失败情况;
  25. // @commit v2.0.1 更新 mpv.net_CM 安装教程链接
  26. // @commit v2.0.2 更新 www.6dm.cc 域名为 www.996dm.com
  27. // @homepage https://github.com/LuckyPuppy514/Play-With-MPV
  28. // @author LuckyPuppy514
  29. // @copyright 2022, Grant LuckyPuppy514 (https://github.com/LuckyPuppy514)
  30. // @license MIT
  31. // @icon https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/mpv.png
  32. // @match *://www.youtube.com/*
  33. // @include https://www.youtube.com/watch/*
  34. // @include https://www.bilibili.com/bangumi/play/*
  35. // @include https://www.bilibili.com/video/*
  36. // @connect api.bilibili.com
  37. // @include https://ddys.tv/*
  38. // @include https://ddys2.me/*
  39. // @include https://www.996dm.com/play/*
  40. // @include http://www.996dm.com/play/*
  41. // @include http://www.dmlaa.com/play/*
  42. // @include https://danmu.yhdmjx.com/*
  43. // @include https://www.dm233.me/play/*
  44. // @include http://www.dmh8.com/player/*
  45. // @include https://www.yhdmp.net/vp/*
  46. // @run-at document-end
  47. // @require https://unpkg.com/js-base64@3.6.1/base64.js
  48. // @require https://unpkg.com/jquery@3.2.1/dist/jquery.min.js
  49. // @grant GM_setValue
  50. // @grant GM_getValue
  51. // ==/UserScript==
  52.  
  53. 'use strict';
  54.  
  55. const REG_VERSION = "20220907";
  56.  
  57. // debug
  58. const IS_DEBUG = false;
  59. const NO_TERMINAL = false;
  60. function debug(data) {
  61. if(IS_DEBUG) {
  62. console.log(data);
  63. }
  64. }
  65.  
  66. const DIV =
  67. `
  68. <div id="pwmpv-button-div">
  69. <button id="pwmpv-about-button"></button>
  70. <button id="pwmpv-play-button"></button>
  71. <button id="pwmpv-setting-button"></button>
  72. </div>
  73.  
  74. <div id="pwmpv-about-div">
  75. <span class="pwmpv-title-span">✨ 关于 Play-With-MPV <button class="pwmpv-close-button">❌</button></span>
  76. <table id="pwmpv-about-table">
  77. <tr>
  78. <td colspan="3" class="pwmpv-title-td">使用 MPV 播放网页中的视频(解码 ⬆️ 补帧 着色器 更多💡)</td>
  79. </tr>
  80. <tr>
  81. <td><a href="https://github.com/LuckyPuppy514/Play-With-MPV#%E4%BD%BF%E7%94%A8-mpv-%E6%92%AD%E6%94%BE%E7%BD%91%E9%A1%B5%E4%B8%AD%E7%9A%84%E8%A7%86%E9%A2%91" target="_blank">🔗 支持网址 🔗</a></td>
  82. <td colspan="2">
  83. <a href="https://www.bilibili.com/" target="_blank"><img class="pwmpv-support-url-icon" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/bilibili.ico"/></a>
  84. <a href="https://ddys.tv/" target="_blank"><img class="pwmpv-support-url-icon-small" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/ddrk.webp"/></a>
  85. <a href="https://www.youtube.com/" target="_blank"><img class="pwmpv-support-url-icon" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/youtube.png"/></a>
  86. <a href="https://www.996dm.com/" target="_blank"><img class="pwmpv-support-url-icon" style="width: 80px;" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/logo_f.png"/></a>
  87. <a href="http://www.dmlaa.com/" target="_blank"><img class="pwmpv-support-url-icon-small" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/fengchedongman.jpg"/></a>
  88. <a href="https://www.dm233.me/" target="_blank"><img class="pwmpv-support-url-icon-small" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/dm233.ico"/></a>
  89. <a href="https://github.com/LuckyPuppy514/Play-With-MPV#%E4%BD%BF%E7%94%A8-mpv-%E6%92%AD%E6%94%BE%E7%BD%91%E9%A1%B5%E4%B8%AD%E7%9A%84%E8%A7%86%E9%A2%91" target="_blank">......</a>
  90. </td>
  91. </tr>
  92. <tr>
  93. <td><a href="https://www.lckp.top/archives/mpvnetcm">🤖 软件安装 🤖</a></td>
  94. <td>
  95. <a href="https://www.lckp.top/archives/mpvnetcm"><img class="pwmpv-support-url-icon" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/mpvnet.png" /></a>
  96. </td>
  97. <td>
  98. <a href="https://www.lckp.top/archives/mpv-lazy"><img class="pwmpv-support-url-icon-large" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/mpv.png" /></a>
  99. </td>
  100. </tr>
  101. <tr>
  102. <td><a href="https://greasyfork.org/zh-CN/scripts/444056-play-with-mpv" target="_blank">🆕 版本更新 🆕</a></td>
  103. <td><a href="https://github.com/LuckyPuppy514/Play-With-MPV" target="_blank">🐳 项目源码 🐳</a></td>
  104. <td><a href="https://github.com/LuckyPuppy514/Play-With-MPV/issues/new" target="_blank">👻 问题反馈 👻</a></td>
  105. </tr>
  106. </table>
  107. <span class="pwmpv-footer-span">
  108. <a href="https://greasyfork.org/zh-CN/scripts/444056-play-with-mpv" target="_blank"><img class="pwmpv-footer-icon" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/tampermonkey.png"/></a>
  109. <a href="https://www.lckp.top" target="_blank">2022 © LuckyPuppy514</a>
  110. <a href="https://github.com/LuckyPuppy514/Play-With-MPV" target="_blank"><img class="pwmpv-footer-icon" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/github.png"/></a>
  111. </span>
  112. </div>
  113.  
  114. <div id="pwmpv-setting-div">
  115. <span class="pwmpv-title-span">🌟 Play-With-MPV 设置 🌟 <button class="pwmpv-close-button">❌</button></span>
  116. <table id="pwmpv-setting-table">
  117. <tr>
  118. <td class="pwmpv-title-td">🔥 MPV路径 🔥</td>
  119. <td><input id="mpv-path-input" type=text placeholder="请输入你的 mpv.com 路径,例如:D:\\daily\\mpv\\mpv.com"></td>
  120. </tr>
  121. <tr>
  122. <td colspan="2" class="pwmpv-tips-td">🩸 如果使用 v2rayN Clash 客户端科学上网,要看油管需要手动添加代理设置 🩸</td>
  123. </tr>
  124. <tr>
  125. <td class="pwmpv-title-td">🌐 代理设置 🌐</td>
  126. <td><input id="proxy-input" type=text placeholder="请输入你的 http 或 socks 代理,例如:http://127.0.0.1:10809"></td>
  127. </tr>
  128. <tr>
  129. <td colspan="2">
  130. <button id="pwmpv-save-button">保存设置</button>
  131. <button id="download-button" data-tip="请先输入 MPV 路径,并保存设置">下载注册表</button>
  132. </td>
  133. </tr>
  134. </table>
  135. <span class="pwmpv-footer-span">
  136. <a href="https://greasyfork.org/zh-CN/scripts/444056-play-with-mpv" target="_blank"><img class="pwmpv-footer-icon" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/tampermonkey.png"/></a>
  137. <a href="https://www.lckp.top" target="_blank">2022 © LuckyPuppy514</a>
  138. <a href="https://github.com/LuckyPuppy514/Play-With-MPV" target="_blank"><img class="pwmpv-footer-icon" src="https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/github.png"/></a>
  139. </span>
  140. </div>
  141.  
  142. <iframe id="firefox-iframe" src="about:blank" style="display:none;"></iframe>
  143. `
  144. const CSS =
  145. `
  146. .pwmpv-close-button {
  147. position: absolute;
  148. top: 3px;
  149. right: 3px;
  150. height: 25px;
  151. width: 40px;
  152. border: none;
  153. font-size: 18px;
  154. background-color: rgba(0, 0, 0, 0);
  155. }
  156. .pwmpv-close-button:hover {
  157. background-color: rgba(0, 0, 0, 0.3);
  158. cursor: pointer;
  159. }
  160. #pwmpv-button-div {
  161. display: none;
  162. }
  163. .pwmpv-title-span {
  164. padding-top: 10px;
  165. font-size: 15px;
  166. }
  167. #pwmpv-about-button {
  168. position: fixed;
  169. bottom: 58px;
  170. left: 8px;
  171. z-index: 999998;
  172.  
  173. width: 25px;
  174. height: 25px;
  175. border: none;
  176. border-radius: 50%;
  177. background-size: cover;
  178. background-color: rgba(255, 255, 255, 0);
  179. background-image: url(https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/about-pink.png);
  180. }
  181. #pwmpv-about-button:hover {
  182. bottom: 56px;
  183. left: 6px;
  184. z-index: 999999;
  185.  
  186. width: 27px;
  187. height: 27px;
  188. cursor: pointer;
  189. }
  190. #pwmpv-about-div {
  191. position: fixed;
  192. top: 40%;
  193. left: 50%;
  194. transform: translate(-50%, -50%);
  195. z-index: 999999;
  196.  
  197. width: 600px;
  198. height: 300px;
  199. border: 6px solid rgba(255, 255, 255, 0.5);
  200. background-color: rgba(234, 122, 153, 0.9);
  201. display: none;
  202. flex-direction: column;
  203. border-radius: 6px;
  204. align-items: center;
  205. color: rgba(255, 255, 255, 1);
  206. }
  207. #pwmpv-about-table {
  208. margin-top: 10px;
  209. width: 570px;
  210. height: 240px;
  211. border-radius: 5px !important;
  212. border: 3px solid rgba(255, 255, 255, 1) !important;
  213. text-align: center;
  214. }
  215. #pwmpv-about-table td {
  216. border: 2px solid rgba(255, 255, 255, 0.5);
  217. }
  218. #pwmpv-about-div a {
  219. color: rgba(255, 255, 255, 1);
  220. text-decoration: none;
  221. font-size: 14px;
  222. }
  223.  
  224. #pwmpv-play-button {
  225. position: fixed;
  226. bottom: 16px;
  227. left: 20px;
  228. z-index: 999999;
  229.  
  230. width: 50px;
  231. height: 50px;
  232. border: none;
  233. border-radius: 50%;
  234. background-size: cover;
  235. background-image: url(https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/mpvnet.png);
  236. cursor: pointer;
  237. }
  238. #pwmpv-play-button:hover {
  239. bottom: 14px;
  240. left: 18px;
  241.  
  242. width: 54px;
  243. height: 54px;
  244. cursor: pointer;
  245. }
  246.  
  247. #pwmpv-setting-button {
  248. position: fixed;
  249. bottom: 56px;
  250. left: 58px;
  251. z-index: 999998;
  252.  
  253. width: 28px;
  254. height: 28px;
  255. border: none;
  256. border-radius: 50%;
  257. background-size: cover;
  258. background-color: rgba(255, 255, 255, 0);
  259. background-image: url(https://cdn.jsdelivr.net/gh/LuckyPuppy514/pic-bed/common/lx-setting.png);
  260. }
  261. #pwmpv-setting-button:hover {
  262. bottom: 54px;
  263. left: 56px;
  264. z-index: 999999;
  265.  
  266. width: 32px;
  267. height: 32px;
  268. cursor: pointer;
  269. }
  270. #pwmpv-setting-div {
  271. position: fixed;
  272. top: 40%;
  273. left: 50%;
  274. transform: translate(-50%, -50%);
  275. z-index: 999999;
  276.  
  277. width: 600px;
  278. height: 300px;
  279. border: 6px solid rgba(255, 255, 255, 0.5);
  280. background-color: rgba(65, 146, 247, 0.9);
  281. display: none;
  282. flex-direction: column;
  283. border-radius: 6px;
  284. align-items: center;
  285. color: rgba(255, 255, 255, 1);
  286. }
  287. #pwmpv-setting-table {
  288. margin-top: 10px;
  289. width: 570px;
  290. height: 240px;
  291. border-radius: 5px !important;
  292. border: 3px solid rgba(255, 255, 255, 1) !important;
  293. text-align: center;
  294. padding: 10px;
  295. }
  296. #pwmpv-setting-table td {
  297. border: 0px solid rgba(255, 255, 255, 0.5);
  298. padding-top: 10px;
  299. }
  300. .pwmpv-title-td{
  301. width: 120px;
  302. height: 30px;
  303. border: none;
  304. font-size: 14px;
  305. }
  306. #pwmpv-setting-table input {
  307. width: 400px;
  308. height: 30px;
  309. border: none;
  310. outline: none;
  311. padding-left: 6px;
  312. border-radius: 3px;
  313. color: rgba(0, 0, 0, 1);
  314. background-color: rgba(255, 255, 255, 0.9);
  315. }
  316. #pwmpv-save-button {
  317. margin-left: 80px;
  318. width: 300px;
  319. height: 30px;
  320. border: none;
  321. border-radius: 3px;
  322. color: rgba(255, 255, 255, 1);
  323. background-color: rgba(0, 255, 50, 0.6);
  324. }
  325. #pwmpv-save-button:hover {
  326. background-color: rgba(0, 255, 0, 0.8);
  327. cursor: pointer;
  328. }
  329. .pwmpv-download-enable:hover {
  330. background-color: rgba(0, 255, 0, 0.8);
  331. cursor: pointer;
  332. }
  333. .pwmpv-download-disable:hover {
  334. cursor: pointer;
  335. }
  336. .pwmpv-download-enable {
  337. margin-left: 10px;
  338. width: 80px;
  339. height: 30px;
  340. border: none;
  341. border-radius: 3px;
  342. color: rgba(255, 255, 255, 1);
  343. background-color: rgba(0, 255, 50, 0.6);
  344. }
  345. .pwmpv-download-disable {
  346. margin-left: 10px;
  347. width: 80px;
  348. height: 30px;
  349. border: none;
  350. border-radius: 3px;
  351. color: rgba(255, 255, 255, 1);
  352. background-color: rgba(0, 0, 0, 0.5);
  353. }
  354. .pwmpv-tips-td {
  355. color: rgba(255, 0, 0, 1);
  356. font-size: 14px;
  357. font-weight: bold;
  358. }
  359. .pwmpv-footer-span {
  360. margin-top: 10px;
  361. margin-bottom: 10px;
  362. color: rgba(255, 255, 255, 1);
  363. }
  364. .pwmpv-footer-span a {
  365. color: rgba(255, 255, 255, 1);
  366. text-decoration: none;
  367. font-size: 14px;
  368. margin-bottom: 1px;
  369. }
  370. .pwmpv-footer-icon {
  371. width: 18px;
  372. height: 18px;
  373. margin-left: 5px;
  374. margin-right: 5px;
  375. margin-bottom: -2px;
  376. }
  377. .pwmpv-support-url-icon {
  378. width: 30px;
  379. height: 30px;
  380. margin-left: 8px;
  381. margin-right: 8px;
  382. }
  383. .pwmpv-support-url-icon-small {
  384. width: 25px;
  385. height: 25px;
  386. margin-left: 8px;
  387. margin-right: 8px;
  388. margin-bottom: 2px;
  389. }
  390. .pwmpv-support-url-icon-large {
  391. width: 37px;
  392. height: 37px;
  393. margin-left: 8px;
  394. margin-right: 8px;
  395. margin-bottom: -4px;
  396. }
  397. `
  398.  
  399. const REG =
  400. `Windows Registry Editor Version 5.00
  401. [HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Google\\Chrome]
  402. "ExternalProtocolDialogShowAlwaysOpenCheckbox"=dword:00000001
  403.  
  404. [HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\Edge]
  405. "ExternalProtocolDialogShowAlwaysOpenCheckbox"=dword:00000001
  406.  
  407. [HKEY_CLASSES_ROOT\\mpv]
  408. @="mpv Protocol"
  409. "URL Protocol"=""
  410.  
  411. [HKEY_CLASSES_ROOT\\mpv\\DefaultIcon]
  412. @=""
  413.  
  414. [HKEY_CLASSES_ROOT\\mpv\\shell]
  415. @=""
  416.  
  417. [HKEY_CLASSES_ROOT\\mpv\\shell\\open]
  418. @=""
  419.  
  420. [HKEY_CLASSES_ROOT\\mpv\\shell\\open\\command]
  421. @="cmd /V:ON /C \\"FOR /F \\"tokens=* USEBACKQ\\" %%F IN (\`powershell -command \\"Add-Type -AssemblyName System.Web;[System.Web.HTTPUtility]::UrlDecode('%1')\\"\`) DO (SET param=%%F) & SET param=!param:mpv://=! & start /min MPV_PATH !param!\\""
  422. `
  423.  
  424. // element id
  425. const BUTTON_DIV = "pwmpv-button-div";
  426. const ABOUT_BUTTON_ID = "pwmpv-about-button";
  427. const ABOUT_DIV_ID = "pwmpv-about-div";
  428. const PLAY_BUTTON_ID = "pwmpv-play-button";
  429. const SETTING_BUTTON_ID = "pwmpv-setting-button";
  430. const SETTING_DIV_ID = "pwmpv-setting-div";
  431. const MPV_PATH_INPUT_ID = "mpv-path-input";
  432. const PROXY_INPUT_ID = "proxy-input";
  433. const SAVE_BUTTON_ID = "pwmpv-save-button";
  434. const DOWNLOAD_BUTTON_ID = "download-button";
  435. const FIREFOX_IFRAME = "firefox-iframe";
  436. // display
  437. const DISPLAY_NONE = "none";
  438. const DISPLAY_FLEX = "flex";
  439. // GM value key
  440. const KEY_MPV_PATH = "MPV_PATH";
  441. const KEY_PROXY = "PROXY";
  442. const KEY_REG_VERSION = "REG_VERSION";
  443.  
  444. function appendHTML() {
  445. var div = document.createElement("div");
  446. div.innerHTML = DIV.trim();
  447. document.body.appendChild(div);
  448. }
  449. function appendCSS() {
  450. var css = document.createElement("style");
  451. css.innerHTML = CSS.trim();
  452. document.head.appendChild(css);
  453. }
  454. function addListener() {
  455. // 关于
  456. var aboutButton = document.getElementById(ABOUT_BUTTON_ID);
  457. var aboutDiv = document.getElementById(ABOUT_DIV_ID);
  458. aboutButton.onclick = function () {
  459. if (aboutDiv.style.display == DISPLAY_NONE) {
  460. aboutDiv.style.display = DISPLAY_FLEX;
  461. settingDiv.style.display = DISPLAY_NONE;
  462. } else {
  463. aboutDiv.style.display = DISPLAY_NONE;
  464. }
  465. };
  466.  
  467. // 播放
  468. var playButton = document.getElementById(PLAY_BUTTON_ID);
  469. playButton.onclick = function () {
  470. handler.playCurrentVideoWithMPV();
  471. }
  472.  
  473. // 设置
  474. var settingButton = document.getElementById(SETTING_BUTTON_ID);
  475. var saveButton = document.getElementById(SAVE_BUTTON_ID);
  476. var downloadButton = document.getElementById(DOWNLOAD_BUTTON_ID);
  477. var settingDiv = document.getElementById(SETTING_DIV_ID);
  478. var mpvPathInput = document.getElementById(MPV_PATH_INPUT_ID);
  479. var proxyInput = document.getElementById(PROXY_INPUT_ID);
  480. settingButton.onclick = function () {
  481. if (settingDiv.style.display == DISPLAY_NONE) {
  482. showSettingDiv();
  483. aboutDiv.style.display = DISPLAY_NONE;
  484. } else {
  485. settingDiv.style.display = DISPLAY_NONE;
  486. }
  487. };
  488. saveButton.onclick = function () {
  489. let oldMpvPath = GM_getValue(KEY_MPV_PATH);
  490. let mpvPath = mpvPathInput.value;
  491. let proxy = proxyInput.value;
  492. if (!mpvPath) {
  493. downloadButton.className = "pwmpv-download-disable";
  494. Toast("⚠️ MPV路径不能为空 ⚠️", 1500);
  495. return;
  496. }
  497. if (/.*[\u4e00-\u9fa5]+.*$/.test(mpvPath)) {
  498. downloadButton.className = "pwmpv-download-disable";
  499. Toast("⚠️ MPV路径不能包含中文 ⚠️", 1500)
  500. return;
  501. }
  502. mpvPath = mpvPath.replaceAll("/", "\\");
  503. mpvPath = mpvPath.replaceAll("\\\\", "\\");
  504. mpvPath = mpvPath.replaceAll("\\", "\\\\");
  505. GM_setValue(KEY_MPV_PATH, mpvPath);
  506. GM_setValue(KEY_PROXY, proxy);
  507. debug(proxy);
  508. downloadButton.className = "pwmpv-download-enable";
  509. if (oldMpvPath != mpvPath) {
  510. Toast("🔥 请重新添加注册表信息 🔥", 2500);
  511. downloadButton.click();
  512. } else {
  513. Toast("✅ 保存成功 ✅", 1500);
  514. }
  515. };
  516. downloadButton.onclick = function () {
  517. generateRegFile();
  518. }
  519. var closeButtons = document.getElementsByClassName("pwmpv-close-button");
  520. for(let closeButton of closeButtons){
  521. closeButton.onclick = function () {
  522. aboutDiv.style.display = DISPLAY_NONE;
  523. settingDiv.style.display = DISPLAY_NONE;
  524. }
  525. }
  526. }
  527. // 显示设置窗口
  528. function showSettingDiv() {
  529. var downloadButton = document.getElementById(DOWNLOAD_BUTTON_ID);
  530. var settingDiv = document.getElementById(SETTING_DIV_ID);
  531. var mpvPathInput = document.getElementById(MPV_PATH_INPUT_ID);
  532. var proxyInput = document.getElementById(PROXY_INPUT_ID);
  533. let mpvPath = GM_getValue(KEY_MPV_PATH);
  534. let proxy = GM_getValue(KEY_PROXY);
  535. if (mpvPath) {
  536. mpvPathInput.value = mpvPath;
  537. downloadButton.className = "pwmpv-download-enable";
  538. } else {
  539. downloadButton.className = "pwmpv-download-disable";
  540. }
  541. if (proxy) {
  542. proxyInput.value = proxy;
  543. }
  544. settingDiv.style.display = DISPLAY_FLEX;
  545. }
  546.  
  547. // 显示消息
  548. function Toast(msg, duration) {
  549. duration = isNaN(duration) ? 3000 : duration;
  550. var m = document.createElement('div');
  551. m.innerHTML = msg;
  552. m.style.cssText = "max-width:60%;min-width: 150px;padding:0 14px;height: 40px;color: rgb(255, 255, 255);line-height: 40px;text-align: center;border-radius: 4px;position: fixed;top: 15%;left: 50%;transform: translate(-50%, -50%);z-index: 999999;background: rgba(0, 0, 0, 0.6);font-size: 14px;";
  553. document.body.appendChild(m);
  554. setTimeout(function () {
  555. var d = 0.5;
  556. m.style.opacity = '0';
  557. setTimeout(function () { document.body.removeChild(m) }, d * 1000);
  558. }, duration);
  559. }
  560.  
  561. // domain
  562. const YOUTUBE = "www.youtube.com";
  563. const BILIBILI = "www.bilibili.com";
  564. const DDRK = "ddys.tv, ddys2.me";
  565. const DM6CC = "www.6dm.cc, www.996dm.com";
  566. const DMLACC = "www.dmlaa.com";
  567. const YHDMJX = "danmu.yhdmjx.com";
  568. const DM233 = "www.dm233.me";
  569. const DMH8 = "www.dmh8.com";
  570. const YHDMP = "www.yhdmp.net";
  571.  
  572. // api
  573. const BILIBILI_API = 'https://api.bilibili.com'
  574.  
  575. // mpv urlprotocol
  576. const MPV_URLPROTOCOL = "mpv://";
  577.  
  578. // try time
  579. const MAX_TRY_TIME = 3;
  580. var tryTime;
  581. var timers;
  582.  
  583. // current page info
  584. var currentUrl;
  585. var currentDomain;
  586. var currentVideoUrl;
  587.  
  588. // video url handler
  589. var handler;
  590.  
  591. var ddrkPlayStatus;
  592. var bilibiliCid;
  593.  
  594. // 通过 URLProtocol 调用 mpv 播放
  595. function playWithMPV(protocolLink) {
  596. let regVersion = GM_getValue(KEY_REG_VERSION);
  597. if (!regVersion || regVersion != REG_VERSION) {
  598. showSettingDiv();
  599. Toast("🆕 注册表配置有更新,请重新下载并添加注册表信息 🆕");
  600. return;
  601. }
  602. var isSupported = false;
  603. if (navigator.userAgent.includes("Firefox/")) {
  604. let iframe = document.getElementById(FIREFOX_IFRAME);
  605. try {
  606. iframe.contentWindow.location.href = protocolLink;
  607. isSupported = true;
  608. } catch (e) {
  609. if (e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
  610. isSupported = false;
  611. dealPlayWithMPVResult(isSupported);
  612. }
  613. }
  614. } else if (navigator.userAgent.includes("Chrome/")) {
  615. let protcolEl = document.getElementById(PLAY_BUTTON_ID);
  616. protcolEl.focus();
  617. protcolEl.onblur = function () {
  618. isSupported = true;
  619. };
  620. location.href = protocolLink;
  621. setTimeout(function () {
  622. protcolEl.onblur = null;
  623. dealPlayWithMPVResult(isSupported);
  624. }, 500);
  625. }
  626. }
  627. // 处理调用 MPV 结果
  628. function dealPlayWithMPVResult(isSupported) {
  629. if (!isSupported) {
  630. Toast("⚠️ 请先设置MPV路径,并添加注册表信息 ⚠️");
  631. showSettingDiv();
  632. }
  633. }
  634. // 生成注册表文件
  635. function generateRegFile() {
  636. var a = document.createElement('a');
  637. var blob = new Blob([REG.replace(KEY_MPV_PATH, GM_getValue(KEY_MPV_PATH))], { 'type': 'application/octet-stream' });
  638. a.href = window.URL.createObjectURL(blob);
  639. a.download = "mpv.reg";
  640. a.click();
  641. GM_setValue(KEY_REG_VERSION, REG_VERSION);
  642. }
  643.  
  644. class Handler {
  645. // 获取当前视频链接
  646. getCurrentVideoUrl() { }
  647. // 获取开始时间
  648. getStartTime() {
  649. return null;
  650. }
  651. // 调用 MPV 播放
  652. playCurrentVideoWithMPV() {
  653. // 携带视频链接
  654. let protocolLink = MPV_URLPROTOCOL + '"' + currentVideoUrl + '"';
  655. // 携带标题
  656. protocolLink = protocolLink + ' --force-media-title="' + document.title + '"';
  657. // 禁用命令行输出及控制
  658. if(NO_TERMINAL){
  659. protocolLink = protocolLink + ' --no-terminal';
  660. }
  661. // B站携带请求头及 cid
  662. if (BILIBILI.indexOf(currentDomain) != -1) {
  663. protocolLink = protocolLink + ' --http-header-fields=referer:"' + currentUrl + ',user-agent:' + navigator.userAgent
  664. + '" --script-opts="cid=' + bilibiliCid + '"';
  665. }
  666. // 油管代理
  667. let proxy = GM_getValue(KEY_PROXY);
  668. if (proxy && YOUTUBE.indexOf(currentDomain) != -1) {
  669. protocolLink = protocolLink + ' --http-proxy=' + proxy + ' --ytdl-raw-options=proxy=[' + proxy + ']';
  670. }
  671. // 开始时间,如果 mpv 开启了退出时记住播放状态,则记住状态优先级更高
  672. let startTime = handler.getStartTime();
  673. if (startTime) {
  674. protocolLink = protocolLink + ' --ss=' + startTime;
  675. }
  676. playWithMPV(protocolLink);
  677. this.pauseCurrentVideo();
  678. }
  679. // 暂停网页视频
  680. pauseCurrentVideo() {
  681. var isPause = false;
  682. let i = 0;
  683. while (i < 5) {
  684. i++;
  685. setTimeout(function () {
  686. if(!isPause){
  687. debug("try to pause");
  688. document.getElementsByTagName("video")[0].pause();
  689. isPause = true;
  690. debug("pause success");
  691. }
  692. }, 1500 * i);
  693. }
  694. }
  695. }
  696. // 油管
  697. class YoutubeHandler extends Handler {
  698. getStartTime(){
  699. let startTimeElements = document.getElementsByClassName("ytp-time-current");
  700. if (startTimeElements){
  701. let length = startTimeElements.length;
  702. if (length > 0 && startTimeElements[length - 1]) {
  703. return startTimeElements[length - 1].innerHTML;
  704. }
  705. }
  706. return null;
  707. }
  708. getCurrentVideoUrl() {
  709. currentVideoUrl = currentUrl;
  710. checkCurrentVideoUrl();
  711. }
  712. }
  713. // B站
  714. class BilibiliHandler extends Handler {
  715. getStartTime() {
  716. let startTimeElement = document.getElementsByClassName("bpx-player-ctrl-time-current")[0];
  717. if (!startTimeElement) {
  718. startTimeElement = document.getElementsByClassName("squirtle-video-time-now")[0];
  719. }
  720. if (startTimeElement) {
  721. return startTimeElement.innerHTML;
  722. }
  723. return null;
  724. }
  725. getCurrentVideoUrl() {
  726. // 投稿视频
  727. let index = currentUrl.indexOf('/video/');
  728. if (index != -1) {
  729. let param = "";
  730. let videoId = currentUrl.substring(index + 7);
  731. if (videoId.startsWith("BV")) {
  732. param = "bvid=" + videoId.substring(2, 12);
  733. } else if (videoId.startsWith("av")) {
  734. param = "aid=" + videoId.substring(2, 10);
  735. } else {
  736. debug("bilibili video id invalid: " + videoId);
  737. return;
  738. }
  739. debug("bilibili video id: " + param);
  740. this.getBilibiliVideoUrl(param);
  741. return;
  742. }
  743.  
  744. // 番剧
  745. let aElement = document.getElementsByClassName('ep-item cursor visited')[0];
  746. if (!aElement) {
  747. aElement = document.getElementsByClassName('ep-item cursor')[0];
  748. }
  749. let epid = aElement.getElementsByTagName('a')[0].href;
  750. epid = epid.substring(epid.indexOf('/ep') + 3);
  751. epid = epid.substring(0, epid.indexOf('/'));
  752. debug('epid: ' + epid);
  753.  
  754. let eno = document.getElementsByClassName("ep-list-progress")[0];
  755. if (!eno) {
  756. return;
  757. }
  758. eno = eno.innerHTML;
  759. eno = eno.substring(0, eno.indexOf('/'));
  760. debug('eno: ' + eno);
  761. this.getBilibiliBangumiUrl(epid, eno);
  762. }
  763. // 获取B站投稿视频链接
  764. getBilibiliVideoUrl(param) {
  765. $.ajax({
  766. type: "GET",
  767. url: BILIBILI_API + "/x/web-interface/view?" + param,
  768. xhrFields: {
  769. withCredentials: true
  770. },
  771. success: function (res) {
  772. debug("get acid and cid by bvid result: ");
  773. debug(res);
  774. let avid = res.data.aid;
  775. let cid = res.data.cid;
  776. let index = currentUrl.indexOf("?p=");
  777. if (index != -1 && res.data.pages.length > 1) {
  778. let p = currentUrl.substring(index + 3);
  779. let endIndex = p.indexOf("&");
  780. if (endIndex != -1) {
  781. p = p.substring(0, endIndex);
  782. }
  783. cid = res.data.pages[p - 1].cid;
  784. }
  785.  
  786. debug("avid: " + avid);
  787. debug("cid: " + cid);
  788. bilibiliCid = cid;
  789.  
  790. let queryBilibiliVideoUrl = "/x/player/playurl?"
  791. + "qn=120&otype=json&fourk=1&fnver=0&fnval=0"
  792. + "&avid=" + avid
  793. + "&cid=" + cid;
  794. $.ajax({
  795. type: "GET",
  796. url: BILIBILI_API + queryBilibiliVideoUrl,
  797. xhrFields: {
  798. withCredentials: true
  799. },
  800. success: function (res) {
  801. debug("get video url by bvid result: ");
  802. debug(res);
  803. currentVideoUrl = res.data.durl[0].url;
  804. checkCurrentVideoUrl();
  805. }
  806. })
  807. }
  808. })
  809. }
  810. // 获取B站番剧视频链接
  811. getBilibiliBangumiUrl(epid, eno){
  812. if (!epid || !eno) {
  813. return;
  814. }
  815. $.ajax({
  816. type: "GET",
  817. url: BILIBILI_API + "/pgc/view/web/season?ep_id=" + epid,
  818. xhrFields: {
  819. withCredentials: true
  820. },
  821. success: function (res) {
  822. debug("get acid and cid by epid result: ");
  823. debug(res);
  824. let episodes = res.result.episodes;
  825. if (eno.indexOf('PV') != -1 || eno.indexOf('OP') != -1 || eno.indexOf('ED') != -1) {
  826. return;
  827. }
  828.  
  829. // 获取 avid and cid
  830. let episode = episodes[eno - 1];
  831. let avid = episode.aid;
  832. let cid = episode.cid;
  833. debug("avid: " + avid);
  834. debug("cid: " + cid);
  835. bilibiliCid = cid;
  836.  
  837. let queryBilibiliVideoUrl = "/pgc/player/web/playurl?"
  838. + "qn=120&otype=json&fourk=1&fnver=0&fnval=0"
  839. + "&avid=" + avid
  840. + "&cid=" + cid;
  841. $.ajax({
  842. type: "GET",
  843. url: BILIBILI_API + queryBilibiliVideoUrl,
  844. xhrFields: {
  845. withCredentials: true
  846. },
  847. success: function (res) {
  848. debug("get video url by epid result: ");
  849. debug(res);
  850. currentVideoUrl = res.result.durl[0].url;
  851. checkCurrentVideoUrl();
  852. }
  853. });
  854. }
  855. })
  856. }
  857. }
  858. // 低端影视
  859. class DdrkHandler extends Handler {
  860. getStartTime(){
  861. let startTimeElements = document.getElementsByClassName("vjs-time-tooltip");
  862. if (startTimeElements){
  863. let length = startTimeElements.length;
  864. if (length > 0 && startTimeElements[length - 1]) {
  865. return startTimeElements[length - 1].innerHTML;
  866. }
  867. }
  868. return null;
  869. }
  870. getCurrentVideoUrl() {
  871. // 点击播放按钮加载 video 元素
  872. if (ddrkPlayStatus == 0) {
  873. let ddrkPlayButton = document.getElementsByClassName('vjs-big-play-button')[0];
  874. if (!ddrkPlayButton) {
  875. debug("ddrk get play button fail");
  876. return;
  877. }
  878. ddrkPlayButton.click();
  879. ddrkPlayStatus = 1;
  880. }
  881. currentVideoUrl = document.getElementById('vjsp_html5_api').src;
  882. checkCurrentVideoUrl();
  883. }
  884. }
  885. // dm6cc 樱花动漫网
  886. class Dm6ccHandler extends Handler {
  887. constructor() {
  888. super();
  889. window.addEventListener('message', function (event) {
  890. currentVideoUrl = event.data;
  891. checkCurrentVideoUrl();
  892. window.removeEventListener("message", () => { });
  893. }, false);
  894. }
  895. pauseCurrentVideo() {
  896. document.getElementsByTagName("iframe")[2].contentWindow.postMessage("pause", "https://" + YHDMJX);
  897. }
  898. }
  899. // 风车动漫
  900. class DmlaccHandler extends Handler {
  901. constructor() {
  902. super();
  903. window.addEventListener('message', function (event) {
  904. currentVideoUrl = event.data;
  905. checkCurrentVideoUrl();
  906. window.removeEventListener("message", () => { });
  907. }, false);
  908. }
  909. pauseCurrentVideo() {
  910. document.getElementsByTagName("iframe")[2].contentWindow.postMessage("pause", "https://" + YHDMJX);
  911. }
  912. }
  913. class YhdmjxHandler extends Handler {
  914. constructor() {
  915. super();
  916. // 监听父页面暂停指令
  917. window.addEventListener("message", function (event) {
  918. if (event.data == "pause") {
  919. document.getElementsByTagName('video')[0].pause();
  920. }
  921. }, false);
  922. }
  923. getCurrentVideoUrl() {
  924. // 发送视频链接到父页面(DmlaccHandler/DmlaccHandler)
  925. currentVideoUrl = document.getElementsByTagName('video')[0].src;
  926. if (checkCurrentVideoUrl()) {
  927. window.parent.postMessage(currentVideoUrl, "*");
  928. }
  929. }
  930. }
  931. // 233动漫网
  932. class Dm233Handler extends Handler {
  933. constructor() {
  934. super();
  935. this.videoElement = null;
  936. }
  937. getCurrentVideoUrl() {
  938. let iframe = document.getElementById('id_main_playiframe');
  939. this.videoElement = iframe.contentWindow.document.getElementsByTagName("video")[0];
  940.  
  941. let videoUrl = this.videoElement.src;
  942. if (videoUrl.startsWith("blob:")) {
  943. videoUrl = iframe.src;
  944. let startIndex = videoUrl.indexOf('url=http') + 4;
  945. let endIndex = videoUrl.indexOf('&getplay_url=');
  946. videoUrl = decodeURIComponent(videoUrl.substring(startIndex, endIndex));
  947. }
  948. currentVideoUrl = videoUrl;
  949. checkCurrentVideoUrl();
  950. }
  951. pauseCurrentVideo() {
  952. this.videoElement.pause();
  953. }
  954. }
  955. // 樱花动漫
  956. class Dmh8Handler extends Handler {
  957. getCurrentVideoUrl() {
  958. let iframe = document.getElementsByTagName('iframe')[2];
  959. let videoUrl = iframe.src;
  960. let startIndex = videoUrl.indexOf('url=http') + 4;
  961. let endIndex = videoUrl.indexOf('m3u8') + 4;
  962. currentVideoUrl = decodeURIComponent(videoUrl.substring(startIndex, endIndex));
  963. checkCurrentVideoUrl();
  964. }
  965. }
  966. // 樱花动漫
  967. class YhdmpHandler extends Handler {
  968. constructor() {
  969. super();
  970. this.videoElement = null;
  971. }
  972. getCurrentVideoUrl() {
  973. let iframe = document.getElementById('yh_playfram');
  974. this.videoElement = iframe.contentWindow.document.getElementsByTagName("video")[0];
  975.  
  976. let videoUrl = iframe.src;
  977. let startIndex = videoUrl.indexOf('url=http') + 4;
  978. let endIndex = videoUrl.indexOf('&getplay_url=');
  979. currentVideoUrl = decodeURIComponent(videoUrl.substring(startIndex, endIndex));
  980. checkCurrentVideoUrl();
  981. }
  982. pauseCurrentVideo() {
  983. this.videoElement.pause();
  984. }
  985. }
  986.  
  987. // 校验视频链接是否有效
  988. function checkCurrentVideoUrl() {
  989. if (!currentVideoUrl || !currentVideoUrl.startsWith("http")) {
  990. debug("current video url is invalid: " + currentVideoUrl);
  991. return false;
  992. }
  993. if (YOUTUBE.indexOf(currentDomain) != -1) {
  994. if(currentUrl.indexOf("/watch") == -1 && currentUrl.indexOf("/playlist") == -1) {
  995. debug("not /watch|/playlist: " + currentUrl);
  996. return false;
  997. }
  998. }
  999. // yun.66dm.net 无法播放
  1000. if (currentVideoUrl.indexOf("yun.66dm.net") != -1) {
  1001. debug("yun.66dm.net: " + currentVideoUrl);
  1002. return false;
  1003. }
  1004. debug("current video url: " + currentVideoUrl);
  1005. if(YHDMJX.indexOf(currentDomain) == -1){
  1006. document.getElementById(BUTTON_DIV).style.display = DISPLAY_FLEX;
  1007. }
  1008. return true;
  1009. }
  1010. // 初始化当前页信息
  1011. function initCurrentPageInfo() {
  1012. debug("init current page info ......");
  1013. document.getElementById(BUTTON_DIV).style.display = DISPLAY_NONE;
  1014. if (timers) {
  1015. for (let timer of timers) {
  1016. debug("clear timer");
  1017. clearTimeout(timer);
  1018. }
  1019. }
  1020. currentUrl = window.location.href;
  1021. currentDomain = window.location.host;
  1022. currentVideoUrl = "";
  1023. ddrkPlayStatus = 0;
  1024. tryTime = 0;
  1025. }
  1026. // 创建处理器
  1027. function createHandler() {
  1028. debug("start create handler: " + currentDomain);
  1029. if (BILIBILI.indexOf(currentDomain) != -1) {
  1030. handler = new BilibiliHandler();
  1031. } else if (DDRK.indexOf(currentDomain) != -1) {
  1032. handler = new DdrkHandler();
  1033. } else if (YOUTUBE.indexOf(currentDomain) != -1) {
  1034. handler = new YoutubeHandler();
  1035. } else if (DM6CC.indexOf(currentDomain) != -1) {
  1036. handler = new Dm6ccHandler();
  1037. } else if (DMLACC.indexOf(currentDomain) != -1) {
  1038. handler = new DmlaccHandler();
  1039. } else if (YHDMJX.indexOf(currentDomain) != -1) {
  1040. handler = new YhdmjxHandler();
  1041. } else if (DM233.indexOf(currentDomain) != -1) {
  1042. handler = new Dm233Handler();
  1043. } else if (DMH8.indexOf(currentDomain) != -1) {
  1044. handler = new Dmh8Handler();
  1045. } else if (YHDMP.indexOf(currentDomain) != -1) {
  1046. handler = new YhdmpHandler();
  1047. }
  1048. }
  1049. // 页面变更监听器
  1050. function pageChangeListener() {
  1051. let newCurrentUrl = window.location.href;
  1052. if (currentUrl != newCurrentUrl) {
  1053. initCurrentPageInfo();
  1054. refreshCurrentVideoUrl();
  1055. }
  1056. }
  1057. // 刷新视频链接
  1058. function refreshCurrentVideoUrl() {
  1059. debug("refresh current video url: " + currentVideoUrl);
  1060. debug("current url: " + currentUrl);
  1061. timers = new Array();
  1062. while (tryTime < MAX_TRY_TIME) {
  1063. timers[tryTime] = setTimeout(function () {
  1064. if (!checkCurrentVideoUrl()) {
  1065. handler.getCurrentVideoUrl();
  1066. }
  1067. debug("timer done");
  1068. }, tryTime * 2000 + 700);
  1069. tryTime = tryTime + 1;
  1070. }
  1071. }
  1072. // 初始化
  1073. function init() {
  1074. // 添加组件和监听器
  1075. appendHTML();
  1076. appendCSS();
  1077. addListener();
  1078.  
  1079. // 初始化页面信息
  1080. initCurrentPageInfo();
  1081. // 创建处理器
  1082. createHandler();
  1083. // 刷新视频链接
  1084. refreshCurrentVideoUrl();
  1085. // 定时监听页面变化
  1086. setInterval(pageChangeListener, 700);
  1087. }
  1088.  
  1089. // 初始化
  1090. init();