Play-With-MPV

使用 MPV 播放网页上的视频

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

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