Play-With-MPV

使用 MPV 播放网页上的视频

目前为 2022-09-10 提交的版本,查看 最新版本

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