Play-With-MPV

使用 MPV 播放网页上的视频

当前为 2022-12-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Play-With-MPV
  3. // @name:zh 使用 MPV 播放
  4. // @namespace https://github.com/LuckyPuppy514
  5. // @version 2.3.2
  6. // @author LuckyPuppy514
  7. // @copyright 2022, Grant LuckyPuppy514 (https://github.com/LuckyPuppy514)
  8. // @license MIT
  9. // @description 使用 MPV 播放网页上的视频
  10. // @homepage https://github.com/LuckyPuppy514/Play-With-MPV
  11. // @icon https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/mpv.png
  12. // @match *://www.youtube.com/*
  13. // @include https://www.youtube.com/watch/*
  14. // @include https://www.bilibili.com/bangumi/play/*
  15. // @include https://www.bilibili.com/video/*
  16. // @include https://live.bilibili.com/*
  17. // @connect api.bilibili.com
  18. // @connect api.live.bilibili.com
  19. // @include https://ddys.tv/*
  20. // @include https://ddys2.me/*
  21. // @include https://www.996dm.com/play/*
  22. // @include http://www.996dm.com/play/*
  23. // @include http://www.dmlaa.com/play/*
  24. // @include https://danmu.yhdmjx.com/*
  25. // @include https://www.dm233.me/play/*
  26. // @include http://www.dmh8.com/player/*
  27. // @include https://www.yhdmp.net/vp/*
  28. // @include https://ani.gamer.com.tw/animeVideo.php?*
  29. // @include http*://*.mp4
  30. // @include http*://*.mkv
  31. // @include http*://*.avi
  32. // @include http*://*.rmvb
  33. // @include http*://alist.*
  34. // @include http*://*:5244*
  35. // @include https://hdzyk.com/?m=*
  36. // @include https://1080zyk*.com/?m=*
  37. // @include https://vip.zykbf.com/?url=*
  38. // @include https://www.kk151.com/play/*
  39. // @include https://jx.m3u8.tv/jiexi/?url=*
  40. // @include https://jx.wolongzywcdn.com:65/m3u8.php?url=*
  41. // @include https://www.m3u8.tv.cdn.8old.cn/jx.php?url=*
  42. // @include https://jx.wujinkk.com/dplayer/?url=*
  43. // @include https://www.ikdmjx.com/?url=*
  44. // @include https://hls.kuaibofang.com/?url=*
  45. // @include https://jx.jxbdzyw.com/m3u8/?url=*
  46. // @include https://libvio.fun/play/*
  47. // @include https://libvio.me/play/*
  48. // @include https://www.libvio.me/play/*
  49. // @include https://sh-data-s02.chinaeast2.cloudapp.chinacloudapi.cn/*.php?url=*
  50. // @include https://p.cfnode1.xyz/*.php?url=*
  51. // @include https://www.bdys01.com/*play/*
  52. // @include https://www.btnull.org/py/*
  53. // @include https://www.pkmp4.com/py/*
  54. // @include https://dick.xfani.com/watch/*
  55. // @include https://m3.moedot.net/muiplayer/?url=*
  56. // @run-at document-end
  57. // @require https://unpkg.com/jquery@3.2.1/dist/jquery.min.js
  58. // @grant GM_setValue
  59. // @grant GM_getValue
  60. // ==/UserScript==
  61.  
  62. 'use strict';
  63. // 注册表版本
  64. const REG_VERSION = "20220907";
  65. // 不输出控制台信息
  66. const NO_TERMINAL = true;
  67.  
  68. // const IS_DEBUG = true;
  69. // function debug(data) {
  70. // if (IS_DEBUG) {
  71. // console.log(data);
  72. // }
  73. // }
  74.  
  75. const DIV =
  76. `
  77. <div id="pwmpv-button-div">
  78. <button id="pwmpv-about-button"></button>
  79. <button id="pwmpv-play-button"></button>
  80. <button id="pwmpv-setting-button"></button>
  81. </div>
  82.  
  83. <div id="pwmpv-about-div">
  84. <span class="pwmpv-title-span">✨ 关于 Play-With-MPV <button class="pwmpv-close-button">❌</button></span>
  85. <table id="pwmpv-about-table">
  86. <tr>
  87. <td colspan="6" class="pwmpv-title-td">使用 MPV 播放网页中的视频(解码 ⬆️ 补帧 着色器 更多💡)</td>
  88. </tr>
  89. <tr>
  90. <td colspan="2"><a href="https://github.com/LuckyPuppy514/Play-With-MPV#-%E7%AE%80%E4%BB%8B" target="_blank">支持网站</a></td>
  91. <td colspan="4">
  92. <a href="https://www.lckp.top/play-with-mpv/index.html" target="_blank">👆 请前往导航页查看 👆</a>
  93. </td>
  94. </tr>
  95. <tr>
  96. <td colspan="2"><a href="https://www.lckp.top/archives/mpvnetcm">支持软件</a></td>
  97. <td colspan="2">
  98. <a href="https://www.lckp.top/archives/mpvnetcm" target="_blank"><img class="pwmpv-support-url-icon" src="https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/mpvnet.png" /></a>
  99. </td>
  100. <td colspan="2">
  101. <a href="https://www.lckp.top/archives/mpv-lazy" target="_blank"><img class="pwmpv-support-url-icon-large" src="https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/mpv.png" /></a>
  102. </td>
  103. </tr>
  104. <tr>
  105. <td colspan="2"><a href="https://github.com/LuckyPuppy514/Play-With-MPV" target="_blank">脚本相关</a></td>
  106. <td colspan="2"><a href="https://greasyfork.org/zh-CN/scripts/444056-play-with-mpv" target="_blank">🆕 版本更新 🆕</a></td>
  107. <td colspan="2"><a href="https://github.com/LuckyPuppy514/Play-With-MPV/issues/new" target="_blank">👻 问题反馈 👻</a></td>
  108. </tr>
  109. </table>
  110. <span class="pwmpv-footer-span">
  111. <a href="https://greasyfork.org/zh-CN/scripts/444056-play-with-mpv" target="_blank"><img class="pwmpv-footer-icon" src="https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/tampermonkey.png"/></a>
  112. <a href="https://www.lckp.top" target="_blank">2022 © LuckyPuppy514</a>
  113. <a href="https://github.com/LuckyPuppy514/Play-With-MPV" target="_blank"><img class="pwmpv-footer-icon" src="https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/github.png"/></a>
  114. </span>
  115. </div>
  116.  
  117. <div id="pwmpv-setting-div">
  118. <span class="pwmpv-title-span">🌟 Play-With-MPV 设置 🌟 <button class="pwmpv-close-button">❌</button></span>
  119. <table id="pwmpv-setting-table">
  120. <tr>
  121. <td class="pwmpv-title-td">软件路径</td>
  122. <td colspan="3"><input id="pwmpv-mpv-path-input" type=text placeholder="请输入你的 mpv.com 路径,例如:D://daily//mpvnet//mpvnet.com"></td>
  123. </tr>
  124. <tr>
  125. <td class="pwmpv-title-td">代理设置</td>
  126. <td colspan="3"><input id="pwmpv-proxy-input" type=text placeholder="请输入你的 http 或 socks 代理,例如:http://127.0.0.1:10809"></td>
  127. </tr>
  128. <tr>
  129. <td class="pwmpv-title-td">最高画质</td>
  130. <td>
  131. <select id="pwmpv-best-quality-select">
  132. <option value="unlimited" selected>无限制</option>
  133. <option value="2160p">2160p</option>
  134. <option value="1440p">1440p</option>
  135. <option value="1080p">1080p</option>
  136. <option value="720p">720p</option>
  137. <option value="480p">480p</option>
  138. </select>
  139. <span class="tip-span">限B站和油管</span>
  140. </td>
  141. <td class="pwmpv-title-td">视频编码</td>
  142. <td>
  143. <select id="pwmpv-bilibili-codecs-select">
  144. <option value="12" selected>HEVC</option>
  145. <option value="13">AV1</option>
  146. <option value="7">AVC</option>
  147. </select>
  148. <span class="tip-span">限B站</span>
  149. </td>
  150. </tr>
  151. <tr>
  152. <td colspan="4">
  153. <button id="pwmpv-save-button">保存设置</button>
  154. <button id="pwmpv-download-button" data-tip="请先输入 MPV 路径,并保存设置">下载注册表</button>
  155. </td>
  156. </tr>
  157. </table>
  158. <span class="pwmpv-footer-span">
  159. <a href="https://greasyfork.org/zh-CN/scripts/444056-play-with-mpv" target="_blank"><img class="pwmpv-footer-icon" src="https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/tampermonkey.png"/></a>
  160. <a href="https://www.lckp.top" target="_blank">2022 © LuckyPuppy514</a>
  161. <a href="https://github.com/LuckyPuppy514/Play-With-MPV" target="_blank"><img class="pwmpv-footer-icon" src="https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/github.png"/></a>
  162. </span>
  163. </div>
  164.  
  165. <iframe id="firefox-iframe" src="about:blank" style="display:none;"></iframe>
  166. `
  167. const CSS =
  168. `
  169. .pwmpv-close-button {
  170. position: absolute;
  171. top: 3px;
  172. right: 3px;
  173. height: 25px;
  174. width: 40px;
  175. border: none;
  176. font-size: 18px;
  177. background-color: rgba(0, 0, 0, 0);
  178. line-height: 0px;
  179. }
  180. .pwmpv-close-button:hover {
  181. background-color: rgba(0, 0, 0, 0.3);
  182. cursor: pointer;
  183. }
  184. #pwmpv-button-div {
  185. display: none;
  186. }
  187. .pwmpv-title-span {
  188. padding-top: 15px;
  189. font-size: 15px;
  190. }
  191. #pwmpv-about-button {
  192. position: fixed;
  193. bottom: 58px;
  194. left: 8px;
  195. z-index: 999998;
  196.  
  197. width: 25px;
  198. height: 25px;
  199. border: none;
  200. border-radius: 50%;
  201. background-size: cover;
  202. background-color: rgba(255, 255, 255, 0);
  203. background-image: url(https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/about-pink.png);
  204. }
  205. #pwmpv-about-button:hover {
  206. bottom: 56px;
  207. left: 6px;
  208. z-index: 999999;
  209.  
  210. width: 27px;
  211. height: 27px;
  212. cursor: pointer;
  213. }
  214. #pwmpv-about-div {
  215. position: fixed;
  216. top: 40%;
  217. left: 50%;
  218. transform: translate(-50%, -50%);
  219. z-index: 999999;
  220.  
  221. width: 600px;
  222. height: 320px;
  223. border: 6px solid rgba(255, 255, 255, 0.5);
  224. background-color: rgba(234, 122, 153, 1);
  225. display: none;
  226. flex-direction: column;
  227. border-radius: 6px;
  228. align-items: center;
  229. color: rgba(255, 255, 255, 1);
  230. }
  231. #pwmpv-about-table {
  232. margin-top: 10px;
  233. width: 570px;
  234. height: 240px;
  235. border-radius: 5px !important;
  236. border: 3px solid rgba(255, 255, 255, 1) !important;
  237. text-align: center;
  238. }
  239. #pwmpv-about-table td {
  240. border: 2px solid rgba(255, 255, 255, 0.5);
  241. padding: 0px 15px 0px 15px;
  242. }
  243. #pwmpv-about-div a {
  244. color: rgba(255, 255, 255, 1);
  245. text-decoration: none;
  246. font-size: 14px;
  247. display: inline-block;
  248. }
  249.  
  250. #pwmpv-play-button {
  251. position: fixed;
  252. bottom: 16px;
  253. left: 20px;
  254. z-index: 999999;
  255.  
  256. width: 50px;
  257. height: 50px;
  258. border: none;
  259. border-radius: 50%;
  260. background-size: cover;
  261. background-image: url(https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/mpvnet.png);
  262. cursor: pointer;
  263. }
  264. #pwmpv-play-button:hover {
  265. bottom: 14px;
  266. left: 18px;
  267.  
  268. width: 54px;
  269. height: 54px;
  270. cursor: pointer;
  271. }
  272.  
  273. #pwmpv-setting-button {
  274. position: fixed;
  275. bottom: 56px;
  276. left: 58px;
  277. z-index: 999998;
  278.  
  279. width: 28px;
  280. height: 28px;
  281. border: none;
  282. border-radius: 50%;
  283. background-size: cover;
  284. background-color: rgba(255, 255, 255, 0);
  285. background-image: url(https://www.lckp.top/gh/LuckyPuppy514/pic-bed/common/lx-setting.png);
  286. }
  287. #pwmpv-setting-button:hover {
  288. bottom: 54px;
  289. left: 56px;
  290. z-index: 999999;
  291.  
  292. width: 32px;
  293. height: 32px;
  294. cursor: pointer;
  295. }
  296. #pwmpv-setting-div {
  297. position: fixed;
  298. top: 40%;
  299. left: 50%;
  300. transform: translate(-50%, -50%);
  301. z-index: 999999;
  302.  
  303. width: 600px;
  304. height: 320px;
  305. border: 6px solid rgba(255, 255, 255, 0.5);
  306. background-color: rgba(65, 146, 247, 1);
  307. display: none;
  308. flex-direction: column;
  309. border-radius: 6px;
  310. align-items: center;
  311. color: rgba(255, 255, 255, 1);
  312. }
  313. #pwmpv-setting-table {
  314. margin-top: 10px;
  315. width: 570px;
  316. height: 240px;
  317. border-radius: 5px !important;
  318. border: 3px solid rgba(255, 255, 255, 1) !important;
  319. text-align: left;
  320. }
  321. #pwmpv-setting-table td {
  322. font-size: 14px;
  323. border: 0px solid rgba(255, 255, 255, 0.5);
  324. padding-top: 18px;
  325. }
  326. .tip-span {
  327. font-size: xx-small;
  328. color: yellow;
  329. position: fixed;
  330. padding-left: 5px;
  331. padding-top: 9px;
  332. }
  333. .pwmpv-title-td {
  334. width: 90px;
  335. height: 30px;
  336. border: none;
  337. font-size: 14px;
  338. padding-left: 12px;
  339. text-align: center;
  340. }
  341. #pwmpv-setting-table input {
  342. font-size: 14px;
  343. width: 430px;
  344. height: 26px;
  345. border: none;
  346. outline: none;
  347. padding-left: 6px;
  348. border-radius: 2px;
  349. color: rgba(0, 0, 0, 1);
  350. background-color: rgba(255, 255, 255, 0.9);
  351. }
  352. #pwmpv-bilibili-codecs-select,
  353. #pwmpv-best-quality-select {
  354. width: 90px;
  355. height: 25px;
  356. border: none;
  357. outline: none;
  358. padding-left: 6px;
  359. border-radius: 2px;
  360. color: rgba(0, 0, 0, 1);
  361. background-color: rgba(255, 255, 255, 0.9);
  362. }
  363. #pwmpv-save-button {
  364. font-size: 14px;
  365. margin-left: 105px;
  366. width: 300px;
  367. height: 30px;
  368. border: none;
  369. border-radius: 3px;
  370. color: rgba(255, 255, 255, 1);
  371. background-color: rgba(0, 255, 50, 0.6);
  372. }
  373. #pwmpv-save-button:hover {
  374. background-color: rgba(0, 255, 0, 0.8);
  375. cursor: pointer;
  376. }
  377. .pwmpv-download-enable:hover {
  378. background-color: rgba(0, 255, 0, 0.8);
  379. cursor: pointer;
  380. }
  381. .pwmpv-download-disable:hover {
  382. cursor: pointer;
  383. }
  384. .pwmpv-download-enable {
  385. font-size: x-small;
  386. margin-left: 10px;
  387. width: 80px;
  388. height: 30px;
  389. border: none;
  390. border-radius: 3px;
  391. color: rgba(255, 255, 255, 1);
  392. background-color: rgba(0, 255, 50, 0.6);
  393.  
  394. }
  395. .pwmpv-download-disable {
  396. font-size: x-small;
  397. margin-left: 10px;
  398. width: 80px;
  399. height: 30px;
  400. border: none;
  401. border-radius: 3px;
  402. color: rgba(255, 255, 255, 1);
  403. background-color: rgba(0, 0, 0, 0.5);
  404. }
  405. .pwmpv-tips-td {
  406. color: rgba(255, 255, 255, 1);
  407. font-size: 12px;
  408. }
  409. .pwmpv-footer-span {
  410. margin-top: 10px;
  411. margin-bottom: 10px;
  412. color: rgba(255, 255, 255, 1);
  413. }
  414. .pwmpv-footer-span a {
  415. color: rgba(255, 255, 255, 1);
  416. text-decoration: none;
  417. font-size: 14px;
  418. margin-bottom: 1px;
  419. display: inline-block;
  420. }
  421. .pwmpv-footer-icon {
  422. width: 18px;
  423. height: 18px;
  424. margin-left: 5px;
  425. margin-right: 5px;
  426. margin-bottom: -2px;
  427. }
  428. .pwmpv-support-url-icon {
  429. width: 30px;
  430. height: 30px;
  431. margin-left: 8px;
  432. margin-right: 8px;
  433. }
  434. .pwmpv-support-url-icon-small {
  435. width: 25px;
  436. height: 25px;
  437. margin-left: 8px;
  438. margin-right: 8px;
  439. margin-bottom: 2px;
  440. }
  441. .pwmpv-support-url-icon-large {
  442. width: 37px;
  443. height: 37px;
  444. margin-left: 8px;
  445. margin-right: 8px;
  446. margin-bottom: -4px;
  447. }
  448. `
  449.  
  450. const REG =
  451. `Windows Registry Editor Version 5.00
  452. [HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Google\\Chrome]
  453. "ExternalProtocolDialogShowAlwaysOpenCheckbox"=dword:00000001
  454.  
  455. [HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\Edge]
  456. "ExternalProtocolDialogShowAlwaysOpenCheckbox"=dword:00000001
  457.  
  458. [HKEY_CLASSES_ROOT\\mpv]
  459. @="mpv Protocol"
  460. "URL Protocol"=""
  461.  
  462. [HKEY_CLASSES_ROOT\\mpv\\DefaultIcon]
  463. @=""
  464.  
  465. [HKEY_CLASSES_ROOT\\mpv\\shell]
  466. @=""
  467.  
  468. [HKEY_CLASSES_ROOT\\mpv\\shell\\open]
  469. @=""
  470.  
  471. [HKEY_CLASSES_ROOT\\mpv\\shell\\open\\command]
  472. @="cmd /V:ON /C \\"FOR /F \\"tokens=* USEBACKQ\\" %%F IN (\`C:\\\\Windows\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe -command \\"Add-Type -AssemblyName System.Web;[System.Web.HTTPUtility]::UrlDecode('%1')\\"\`) DO (SET param=%%F) & SET param=!param:mpv://=! & start /min MPV_PATH !param!\\""
  473. `
  474.  
  475. // element id
  476. const BUTTON_DIV = "pwmpv-button-div";
  477. const ABOUT_BUTTON_ID = "pwmpv-about-button";
  478. const ABOUT_DIV_ID = "pwmpv-about-div";
  479. const PLAY_BUTTON_ID = "pwmpv-play-button";
  480. const SETTING_BUTTON_ID = "pwmpv-setting-button";
  481. const SETTING_DIV_ID = "pwmpv-setting-div";
  482. const MPV_PATH_INPUT_ID = "pwmpv-mpv-path-input";
  483. const PROXY_INPUT_ID = "pwmpv-proxy-input";
  484. const BILIBILI_CODECS_SELECT_ID = "pwmpv-bilibili-codecs-select";
  485. const BEST_QUALITY_SELECT_ID = "pwmpv-best-quality-select";
  486. const SAVE_BUTTON_ID = "pwmpv-save-button";
  487. const DOWNLOAD_BUTTON_ID = "pwmpv-download-button";
  488. // display
  489. const DISPLAY_NONE = "none";
  490. const DISPLAY_FLEX = "flex";
  491. // GM value key
  492. const KEY_MPV_PATH = "MPV_PATH";
  493. const KEY_PROXY = "PROXY";
  494. const KEY_REG_VERSION = "REG_VERSION";
  495. const KEY_BILIBILI_CODECS = "BILIBILI_CODECS";
  496. const BILIBILI_CODECS_HEVC = "12";
  497. const KEY_BEST_QUALITY = "BEST_QUALITY";
  498. const BEST_QUALITY_UNLIMITED = "unlimited";
  499.  
  500. function appendHTML() {
  501. var div = document.createElement("div");
  502. div.innerHTML = DIV.trim();
  503. document.body.appendChild(div);
  504. }
  505. function appendCSS() {
  506. var css = document.createElement("style");
  507. css.innerHTML = CSS.trim();
  508. document.head.appendChild(css);
  509. }
  510. var bilibiliCodecs;
  511. var bestQuality;
  512. var isFullScreen = false;
  513. function addListener() {
  514. // 关于
  515. var aboutButton = document.getElementById(ABOUT_BUTTON_ID);
  516. var aboutDiv = document.getElementById(ABOUT_DIV_ID);
  517. aboutButton.onclick = function () {
  518. if (aboutDiv.style.display != DISPLAY_FLEX) {
  519. aboutDiv.style.display = DISPLAY_FLEX;
  520. settingDiv.style.display = DISPLAY_NONE;
  521. } else {
  522. aboutDiv.style.display = DISPLAY_NONE;
  523. }
  524. };
  525.  
  526. // 播放
  527. var playButton = document.getElementById(PLAY_BUTTON_ID);
  528. playButton.onclick = function () {
  529. let regVersion = GM_getValue(KEY_REG_VERSION);
  530. if (!regVersion || regVersion != REG_VERSION) {
  531. showSettingDiv();
  532. Toast("🆕 注册表配置有更新,请重新下载并添加注册表信息 🆕");
  533. return;
  534. }
  535. handler.playCurrentVideoWithMPV();
  536. }
  537.  
  538. // 设置
  539. var settingButton = document.getElementById(SETTING_BUTTON_ID);
  540. var bilibiliCodecsSelect = document.getElementById(BILIBILI_CODECS_SELECT_ID);
  541. var bestQualitySelect = document.getElementById(BEST_QUALITY_SELECT_ID);
  542. var saveButton = document.getElementById(SAVE_BUTTON_ID);
  543. var downloadButton = document.getElementById(DOWNLOAD_BUTTON_ID);
  544. var settingDiv = document.getElementById(SETTING_DIV_ID);
  545. var mpvPathInput = document.getElementById(MPV_PATH_INPUT_ID);
  546. var proxyInput = document.getElementById(PROXY_INPUT_ID);
  547. settingButton.onclick = function () {
  548. if (settingDiv.style.display != DISPLAY_FLEX) {
  549. showSettingDiv();
  550. aboutDiv.style.display = DISPLAY_NONE;
  551. } else {
  552. settingDiv.style.display = DISPLAY_NONE;
  553. }
  554. };
  555. bilibiliCodecs = GM_getValue(KEY_BILIBILI_CODECS);
  556. if (!bilibiliCodecs) {
  557. bilibiliCodecs = BILIBILI_CODECS_HEVC;
  558. GM_setValue(KEY_BILIBILI_CODECS, bilibiliCodecs);
  559. }
  560. bilibiliCodecsSelect.onchange = function () {
  561. bilibiliCodecs = this.value;
  562. };
  563. bestQuality = GM_getValue(KEY_BEST_QUALITY);
  564. if (!bestQuality) {
  565. bestQuality = BEST_QUALITY_UNLIMITED;
  566. GM_setValue(KEY_BEST_QUALITY, bestQuality);
  567. }
  568. bestQualitySelect.onchange = function () {
  569. bestQuality = this.value;
  570. };
  571. saveButton.onclick = function () {
  572. let oldMpvPath = GM_getValue(KEY_MPV_PATH);
  573. let mpvPath = mpvPathInput.value;
  574. let proxy = proxyInput.value;
  575. if (!mpvPath) {
  576. downloadButton.className = "pwmpv-download-disable";
  577. Toast("⚠️ 软件路径不能为空 ⚠️", 2000);
  578. return;
  579. }
  580. if (/.*[\u4e00-\u9fa5]+.*$/.test(mpvPath)) {
  581. downloadButton.className = "pwmpv-download-disable";
  582. Toast("⚠️ 软件路径不能包含中文 ⚠️", 2000);
  583. return;
  584. }
  585. mpvPath = mpvPath.replace(/[\\|/]+/g, "//");
  586. if (!mpvPath.endsWith(".com")) {
  587. if (!mpvPath.endsWith("//")) {
  588. mpvPath = mpvPath + "//";
  589. }
  590. if (mpvPath.endsWith("mpvnet//")) {
  591. mpvPath = mpvPath + "mpvnet.com";
  592. } else if (mpvPath.endsWith("mpv//") || mpvPath.endsWith("mpv-lazy//")) {
  593. mpvPath = mpvPath + "mpv.com";
  594. } else {
  595. Toast("⚠️ 软件路径错误,正确示例:D:/daily/mpvnet/mpvnet.com ⚠️", 3000)
  596. return;
  597. }
  598. }
  599. mpvPathInput.value = mpvPath;
  600. GM_setValue(KEY_MPV_PATH, mpvPath);
  601. GM_setValue(KEY_PROXY, proxy);
  602. if (bilibiliCodecs != GM_getValue(KEY_BILIBILI_CODECS) || bestQuality != GM_getValue(KEY_BEST_QUALITY)) {
  603. GM_setValue(KEY_BILIBILI_CODECS, bilibiliCodecs);
  604. GM_setValue(KEY_BEST_QUALITY, bestQuality);
  605. // 重新获取视频链接
  606. initCurrentPageInfo();
  607. refreshCurrentVideoUrl();
  608. }
  609. // debug(proxy);
  610. downloadButton.className = "pwmpv-download-enable";
  611. if (oldMpvPath != mpvPath) {
  612. Toast("🔥 请重新添加注册表信息 🔥", 3000);
  613. downloadButton.click();
  614. } else {
  615. Toast("✅ 保存成功 ✅", 2000);
  616. }
  617. };
  618. downloadButton.onclick = function () {
  619. // 生成注册表信息
  620. var a = document.createElement('a');
  621. var blob = new Blob([REG.replace(KEY_MPV_PATH, GM_getValue(KEY_MPV_PATH))], { 'type': 'application/octet-stream' });
  622. a.href = window.URL.createObjectURL(blob);
  623. a.download = "mpv.reg";
  624. a.click();
  625. GM_setValue(KEY_REG_VERSION, REG_VERSION);
  626. }
  627. var closeButtons = document.getElementsByClassName("pwmpv-close-button");
  628. for (let closeButton of closeButtons) {
  629. closeButton.onclick = function () {
  630. aboutDiv.style.display = DISPLAY_NONE;
  631. settingDiv.style.display = DISPLAY_NONE;
  632. }
  633. }
  634. // 全屏
  635. document.addEventListener("fullscreenchange", () => {
  636. if (document.fullscreenElement) {
  637. isFullScreen = true;
  638. document.getElementById(BUTTON_DIV).style.display = DISPLAY_NONE;
  639. } else {
  640. isFullScreen = false;
  641. handler.checkCurrentVideoUrl();
  642. }
  643. });
  644. }
  645. // 显示设置窗口
  646. function showSettingDiv() {
  647. var downloadButton = document.getElementById(DOWNLOAD_BUTTON_ID);
  648. var settingDiv = document.getElementById(SETTING_DIV_ID);
  649. var bilibiliCodecsSelect = document.getElementById(BILIBILI_CODECS_SELECT_ID);
  650. var bestQualitySelect = document.getElementById(BEST_QUALITY_SELECT_ID);
  651. var mpvPathInput = document.getElementById(MPV_PATH_INPUT_ID);
  652. var proxyInput = document.getElementById(PROXY_INPUT_ID);
  653. let mpvPath = GM_getValue(KEY_MPV_PATH);
  654. let proxy = GM_getValue(KEY_PROXY);
  655. bilibiliCodecs = GM_getValue(KEY_BILIBILI_CODECS);
  656. bestQuality = GM_getValue(KEY_BEST_QUALITY);
  657. if (mpvPath) {
  658. mpvPathInput.value = mpvPath;
  659. downloadButton.className = "pwmpv-download-enable";
  660. } else {
  661. downloadButton.className = "pwmpv-download-disable";
  662. }
  663. if (proxy) {
  664. proxyInput.value = proxy;
  665. }
  666. bilibiliCodecsSelect.value = bilibiliCodecs;
  667. bestQualitySelect.value = bestQuality;
  668. settingDiv.style.display = DISPLAY_FLEX;
  669. }
  670. // 显示消息
  671. function Toast(msg, duration) {
  672. duration = isNaN(duration) ? 3000 : duration;
  673. var m = document.createElement('div');
  674. m.innerHTML = msg;
  675. 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.9);font-size: 14px;";
  676. document.body.appendChild(m);
  677. setTimeout(function () {
  678. var d = 0.5;
  679. m.style.opacity = '0';
  680. setTimeout(function () { document.body.removeChild(m) }, d * 1000);
  681. }, duration);
  682. }
  683.  
  684. // mpv urlprotocol
  685. const MPV_URLPROTOCOL = "mpv://";
  686. // mpv urlprotocol link
  687. class UrlProtocol {
  688. constructor() {
  689. this.link = MPV_URLPROTOCOL + '"' + currentVideoUrl + '"';
  690. this.appendNoTerminal();
  691. this.needAppendTitle = false;
  692. }
  693. // 添加参数
  694. append(param) {
  695. this.link = this.link + ' ' + param;
  696. }
  697. // 禁止命令行输出及控制
  698. appendNoTerminal() {
  699. if (NO_TERMINAL) {
  700. this.append('--no-terminal');
  701. }
  702. }
  703. // 开始时间(如果 mpv 开启了退出时记住播放状态,则记住状态优先级更高)
  704. appendStartTime() {
  705. let startTime = handler.getStartTime();
  706. if (startTime) {
  707. this.append('--ss="' + startTime + '"');
  708. }
  709. }
  710. // 标题
  711. appendTitle() {
  712. this.needAppendTitle = true;
  713. }
  714. // 代理
  715. appendProxy() {
  716. let proxy = GM_getValue(KEY_PROXY);
  717. if (proxy) {
  718. this.append('--http-proxy=' + proxy + ' --ytdl-raw-options=proxy=[' + proxy + ']');
  719. }
  720. }
  721. // 最终链接
  722. getLink() {
  723. if (this.needAppendTitle) {
  724. // 限制标题长度(url 有长度限制)
  725. let maxLength = 1900 - this.link.length;
  726. let title = encodeURIComponent(document.title);
  727. if (title.length > maxLength) {
  728. title = title.substring(0, maxLength) + '...';
  729. }
  730. this.append('--force-media-title="' + title + '"');
  731. }
  732. return this.link;
  733. }
  734. }
  735.  
  736. // 网页处理器
  737. var handler;
  738. class Handler {
  739. // 获取当前视频链接
  740. getCurrentVideoUrl() { }
  741. // 获取开始时间
  742. getStartTime() { return null; }
  743. // 暂停网页视频
  744. pauseCurrentVideo() { document.getElementsByTagName("video")[0].pause(); }
  745. // 获取调用 mpv 链接
  746. getUrlProtocolLink() {
  747. let urlProtocol = new UrlProtocol;
  748. urlProtocol.appendStartTime();
  749. urlProtocol.appendTitle();
  750. return urlProtocol.getLink();
  751. }
  752. // 校验视频链接是否有效
  753. checkCurrentVideoUrl() {
  754. if (this.baseCheckCurrentVideoUrl()) {
  755. if (!isFullScreen) {
  756. document.getElementById(BUTTON_DIV).style.display = DISPLAY_FLEX;
  757. }
  758. return true;
  759. }
  760. return false;
  761. }
  762.  
  763. // 调用 mpv 播放
  764. playCurrentVideoWithMPV() {
  765. window.open(this.getUrlProtocolLink(), "_self");
  766. let i = 0;
  767. while (i < 3) {
  768. i++;
  769. setTimeout(function () {
  770. handler.pauseCurrentVideo();
  771. }, 2000 * i);
  772. }
  773. }
  774. // 根据 class name 获取播放时间
  775. getStartTimeByClassName(className) {
  776. let startTimeElements = document.getElementsByClassName(className);
  777. let length = startTimeElements.length;
  778. if (length > 0) {
  779. return startTimeElements[length - 1].innerHTML;
  780. }
  781. return null;
  782. }
  783. // 视频链接基础校验
  784. baseCheckCurrentVideoUrl() {
  785. // debug("current video url: " + currentVideoUrl);
  786. if (!currentVideoUrl || !currentVideoUrl.startsWith("http")
  787. || currentVideoUrl.indexOf("yun.66dm.net") != -1
  788. || currentVideoUrl.indexOf("www.xmfans.me") != -1
  789. || currentVideoUrl.indexOf("sod.bunediy.com") != -1
  790. || currentVideoUrl.indexOf("c2.monidai.com") != -1) {
  791. return false;
  792. }
  793. return true;
  794. }
  795. }
  796.  
  797. // 油管
  798. const YOUTUBE = "www.youtube.com";
  799. const YOUTUBE_QN = {
  800. "unlimited": "",
  801. "2160p": "--ytdl-format=bestvideo[height<=?2160]%2Bbestaudio/best",
  802. "1440p": "--ytdl-format=bestvideo[height<=?1440]%2Bbestaudio/best",
  803. "1080p": "--ytdl-format=bestvideo[height<=?1080]%2Bbestaudio/best",
  804. "720p": "--ytdl-format=bestvideo[height<=?720]%2Bbestaudio/best",
  805. "480p": "--ytdl-format=bestvideo[height<=?480]%2Bbestaudio/best",
  806. };
  807. class YoutubeHandler extends Handler {
  808. getCurrentVideoUrl() {
  809. currentVideoUrl = currentUrl;
  810. this.checkCurrentVideoUrl();
  811. }
  812. getStartTime() {
  813. return this.getStartTimeByClassName("ytp-time-current");
  814. }
  815. getUrlProtocolLink() {
  816. let urlProtocol = new UrlProtocol;
  817. urlProtocol.appendStartTime();
  818. urlProtocol.appendProxy();
  819. if (bestQuality) {
  820. urlProtocol.append(YOUTUBE_QN[bestQuality]);
  821. }
  822. return urlProtocol.getLink();
  823. }
  824. checkCurrentVideoUrl() {
  825. if (currentUrl.indexOf("/watch") == -1 && currentUrl.indexOf("/playlist") == -1) {
  826. return false;
  827. }
  828. return super.checkCurrentVideoUrl();
  829. }
  830. }
  831.  
  832. // B站
  833. const BILIBILI = "www.bilibili.com";
  834. // B站 API
  835. const BILIBILI_API = 'https://api.bilibili.com';
  836. // cid 用于传递给 mpv 获取弹幕
  837. var bilibiliCid;
  838. const BILIBILI_QN = {
  839. "unlimited": 127,
  840. "2160p": 126,
  841. "1440p": 116,
  842. "1080p": 116,
  843. "720p": 74,
  844. "480p": 32,
  845. };
  846. class BilibiliHandler extends Handler {
  847. getCurrentVideoUrl() {
  848. let index = currentUrl.indexOf('/video/');
  849. if (index != -1) {
  850. // 投稿视频
  851. let param = "";
  852. let videoId = currentUrl.substring(index + 7);
  853. if (videoId.startsWith("BV")) {
  854. param = "bvid=" + videoId.match(/BV([0-9a-zA-Z]+)/)[1];
  855. } else if (videoId.startsWith("av")) {
  856. param = "aid=" + videoId.match(/av([0-9]+)/)[1];
  857. } else {
  858. // debug("bilibili video id invalid: " + videoId);
  859. return;
  860. }
  861. // debug("bilibili video id: " + param);
  862. getBilibiliVideoUrl(param);
  863.  
  864. } else {
  865. // 番剧
  866. let visitedLi = document.getElementsByClassName("ep-item cursor visited")[0];
  867. visitedLi = visitedLi ? visitedLi : document.getElementsByClassName('ep-item cursor')[0];
  868. let epid = visitedLi.getElementsByTagName('a')[0].href.match(/ep(\d+)/)[1];
  869. let className = visitedLi.parentElement.parentElement.className;
  870. getBilibiliBangumiUrl(epid, className);
  871. }
  872. }
  873. getStartTime() {
  874. let startTime = this.getStartTimeByClassName("bpx-player-ctrl-time-current");
  875. if (!startTime) {
  876. startTime = this.getStartTimeByClassName("squirtle-video-time-now");
  877. }
  878. return startTime;
  879. }
  880. getUrlProtocolLink() {
  881. let urlProtocol = new UrlProtocol;
  882. urlProtocol.appendStartTime();
  883. urlProtocol.appendTitle();
  884. urlProtocol.append('--audio-file="' + currentAudioUrl + '"');
  885. urlProtocol.append('--http-header-fields="referer: https://www.bilibili.com, user-agent: ' + navigator.userAgent + '"');
  886. urlProtocol.append('--script-opts="cid=' + bilibiliCid + '"');
  887. return urlProtocol.getLink();
  888. }
  889. }
  890. // 获取B站投稿视频链接
  891. function getBilibiliVideoUrl(param) {
  892. $.ajax({
  893. type: "GET",
  894. url: BILIBILI_API + "/x/web-interface/view?" + param,
  895. xhrFields: {
  896. withCredentials: true
  897. },
  898. success: function (res) {
  899. // debug("get acid and cid by avid/bvid result: ");
  900. // debug(res);
  901. let avid = res.data.aid;
  902. let cid = res.data.cid;
  903. let index = currentUrl.indexOf("?p=");
  904. if (index != -1 && res.data.pages.length > 1) {
  905. let p = currentUrl.substring(index + 3);
  906. let endIndex = p.indexOf("&");
  907. if (endIndex != -1) {
  908. p = p.substring(0, endIndex);
  909. }
  910. cid = res.data.pages[p - 1].cid;
  911. }
  912. getBilibiliPlayUrl(avid, cid);
  913. }
  914. })
  915. }
  916. // 获取B站番剧视频链接
  917. function getBilibiliBangumiUrl(epid, className) {
  918. // debug('epid: ' + epid);
  919. // debug('className: ' + className);
  920. $.ajax({
  921. type: "GET",
  922. url: BILIBILI_API + "/pgc/view/web/season?ep_id=" + epid,
  923. xhrFields: {
  924. withCredentials: true
  925. },
  926. success: function (res) {
  927. // debug("get acid and cid by epid result: ");
  928. // debug(res);
  929. var currentEpisode;
  930. var section = new Array;
  931. if (className.indexOf("list-wrapper") != -1) {
  932. section[0] = { episodes: res.result.episodes };
  933. } else {
  934. section = res.result.section;
  935. }
  936. for (let i = 0; i < section.length; i++) {
  937. let episodes = section[i].episodes;
  938. for (const episode of episodes) {
  939. if (episode.id == epid) {
  940. currentEpisode = episode;
  941. break;
  942. }
  943. }
  944. if (currentEpisode) {
  945. break;
  946. }
  947. }
  948. getBilibiliPlayUrl(currentEpisode.aid, currentEpisode.cid);
  949. }
  950. })
  951. }
  952. // 获取B站视频播放链接
  953. function getBilibiliPlayUrl(avid, cid) {
  954. // debug("avid: " + avid);
  955. // debug("cid: " + cid);
  956. bilibiliCid = cid;
  957.  
  958. let queryBilibiliVideoUrl = "/x/player/playurl?"
  959. + "qn=&otype=json&fourk=1&fnver=0&fnval=4048"
  960. + "&avid=" + avid
  961. + "&cid=" + cid;
  962. $.ajax({
  963. type: "GET",
  964. url: BILIBILI_API + queryBilibiliVideoUrl,
  965. xhrFields: {
  966. withCredentials: true
  967. },
  968. success: function (res) {
  969. // debug(res);
  970. let dash = res.data.dash;
  971. let hiRes = dash.flac;
  972. let dolby = dash.dolby;
  973. if (hiRes && hiRes.audio) {
  974. // debug("hi-res: on");
  975. currentAudioUrl = hiRes.audio.baseUrl;
  976. } else if (dolby && dolby.audio) {
  977. // debug("dolby: on");
  978. currentAudioUrl = dolby.audio[0].base_url;
  979. } else {
  980. // debug(dash.audio[0].id);
  981. currentAudioUrl = dash.audio[0].baseUrl;
  982. }
  983. let i = 0;
  984. // 限制画质
  985. let qn = BILIBILI_QN[bestQuality];
  986. while (i < dash.video.length && dash.video[i].id > qn) {
  987. i++;
  988. }
  989. let baseUrl = dash.video[i].baseUrl;
  990. let id = dash.video[i].id;
  991. while (i < dash.video.length) {
  992. if (dash.video[i].id != id) {
  993. break;
  994. }
  995. if (dash.video[i].codecid == bilibiliCodecs) {
  996. baseUrl = dash.video[i].baseUrl;
  997. break;
  998. }
  999. i++;
  1000. }
  1001. currentVideoUrl = baseUrl;
  1002. handler.checkCurrentVideoUrl();
  1003. }
  1004. });
  1005. }
  1006.  
  1007. // B站直播
  1008. const BILIBILI_LIVE = "live.bilibili.com";
  1009. // B站直播 API
  1010. const BILIBILI_LIVE_API = 'https://api.live.bilibili.com';
  1011.  
  1012. const BILIBILI_LIVE_QN = {
  1013. "unlimited": 4,
  1014. "2160p": 4,
  1015. "1440p": 4,
  1016. "1080p": 4,
  1017. "720p": 3,
  1018. "480p": 2,
  1019. };
  1020. class BilibiliLiveHandler extends Handler {
  1021. getCurrentVideoUrl() {
  1022. let url = document.getElementsByTagName("iframe")[0].src;
  1023. let index = url.indexOf("roomid=");
  1024. if (index == -1) {
  1025. return;
  1026. }
  1027. let roomid = url.substring(index + 7);
  1028. roomid = roomid.substring(0, roomid.indexOf("&"));
  1029. let queryBilibiliLiveVideoUrl = "/room/v1/Room/playUrl?"
  1030. + "quality=" + BILIBILI_LIVE_QN[bestQuality]
  1031. + "&cid=" + roomid;
  1032. $.ajax({
  1033. type: "GET",
  1034. url: BILIBILI_LIVE_API + queryBilibiliLiveVideoUrl,
  1035. xhrFields: {
  1036. withCredentials: true
  1037. },
  1038. success: function (res) {
  1039. currentVideoUrl = res.data.durl[0].url;
  1040. handler.checkCurrentVideoUrl();
  1041. }
  1042. });
  1043. }
  1044. getUrlProtocolLink() {
  1045. let urlProtocol = new UrlProtocol;
  1046. urlProtocol.appendTitle();
  1047. urlProtocol.append('--http-header-fields="referer: https://live.bilibili.com, user-agent: ' + navigator.userAgent + '"');
  1048. return urlProtocol.getLink();
  1049. }
  1050. }
  1051.  
  1052. // 低端影视
  1053. const DDRK = "ddys.tv, ddys2.me";
  1054. // 低端影视播放状态
  1055. var ddrkPlayStatus;
  1056.  
  1057. class DdrkHandler extends Handler {
  1058. getCurrentVideoUrl() {
  1059. // 点击播放按钮加载 video 元素
  1060. if (!ddrkPlayStatus) {
  1061. let ddrkPlayButton = document.getElementsByClassName('vjs-big-play-button')[0];
  1062. if (!ddrkPlayButton) {
  1063. // debug("ddrk get play button fail");
  1064. return;
  1065. }
  1066. ddrkPlayButton.click();
  1067. ddrkPlayStatus = true;
  1068. }
  1069. currentVideoUrl = document.getElementById('vjsp_html5_api').src;
  1070. this.checkCurrentVideoUrl();
  1071. }
  1072. getStartTime() {
  1073. return this.getStartTimeByClassName("vjs-time-tooltip");
  1074. }
  1075. }
  1076.  
  1077. // 樱花动漫网
  1078. const DM6CC = "www.6dm.cc, www.996dm.com";
  1079.  
  1080. class Dm6ccHandler extends Handler {
  1081. constructor() {
  1082. super();
  1083. window.addEventListener('message', function (event) {
  1084. currentVideoUrl = event.data;
  1085. this.checkCurrentVideoUrl();
  1086. window.removeEventListener("message", () => { });
  1087. }, false);
  1088. }
  1089. pauseCurrentVideo() {
  1090. document.getElementsByTagName("iframe")[2].contentWindow.postMessage("pause", "https://" + YHDMJX);
  1091. }
  1092. }
  1093.  
  1094. // 风车动漫
  1095. const DMLACC = "www.dmlaa.com";
  1096.  
  1097. class DmlaccHandler extends Handler {
  1098. constructor() {
  1099. super();
  1100. window.addEventListener('message', function (event) {
  1101. currentVideoUrl = event.data;
  1102. this.checkCurrentVideoUrl();
  1103. window.removeEventListener("message", () => { });
  1104. }, false);
  1105. }
  1106. pauseCurrentVideo() {
  1107. document.getElementsByTagName("iframe")[2].contentWindow.postMessage("pause", "https://" + YHDMJX);
  1108. }
  1109. }
  1110.  
  1111. // 樱花动漫网和风车动漫实际播放地址
  1112. const YHDMJX = "danmu.yhdmjx.com";
  1113.  
  1114. class YhdmjxHandler extends Handler {
  1115. constructor() {
  1116. super();
  1117. window.addEventListener("message", function (event) {
  1118. if (event.data == "pause") {
  1119. document.getElementsByTagName('video')[0].pause();
  1120. }
  1121. }, false);
  1122. }
  1123. getCurrentVideoUrl() {
  1124. currentVideoUrl = document.getElementsByTagName('video')[0].src;
  1125. if (this.checkCurrentVideoUrl()) {
  1126. window.parent.postMessage(currentVideoUrl, "*");
  1127. }
  1128. }
  1129. checkCurrentVideoUrl() {
  1130. return this.baseCheckCurrentVideoUrl();
  1131. }
  1132. }
  1133.  
  1134. // 233动漫网
  1135. const DM233 = "www.dm233.me";
  1136.  
  1137. class Dm233Handler extends Handler {
  1138. constructor() {
  1139. super();
  1140. this.videoElement = null;
  1141. }
  1142. getCurrentVideoUrl() {
  1143. let iframe = document.getElementById('id_main_playiframe');
  1144. this.videoElement = iframe.contentWindow.document.getElementsByTagName("video")[0];
  1145. let videoUrl = this.videoElement.src;
  1146. if (videoUrl.startsWith("blob:")) {
  1147. videoUrl = iframe.src;
  1148. let startIndex = videoUrl.indexOf('url=http') + 4;
  1149. let endIndex = videoUrl.indexOf('&getplay_url=');
  1150. videoUrl = decodeURIComponent(videoUrl.substring(startIndex, endIndex));
  1151. }
  1152. currentVideoUrl = videoUrl;
  1153. this.checkCurrentVideoUrl();
  1154. }
  1155. getStartTime() {
  1156. return this.getStartTimeByClassName("dplayer-ptime");
  1157. }
  1158. pauseCurrentVideo() {
  1159. this.videoElement.pause();
  1160. }
  1161. }
  1162.  
  1163. // 樱花动漫
  1164. const DMH8 = "www.dmh8.com";
  1165.  
  1166. class Dmh8Handler extends Handler {
  1167. getCurrentVideoUrl() {
  1168. let iframe = document.getElementsByTagName('iframe')[2];
  1169. let videoUrl = iframe.src;
  1170. let startIndex = videoUrl.indexOf('url=http') + 4;
  1171. let endIndex = videoUrl.indexOf('m3u8') + 4;
  1172. currentVideoUrl = decodeURIComponent(videoUrl.substring(startIndex, endIndex));
  1173. this.checkCurrentVideoUrl();
  1174. }
  1175. getStartTime() {
  1176. return this.getStartTimeByClassName("dplayer-ptime");
  1177. }
  1178. }
  1179.  
  1180. // 樱花动漫
  1181. const YHDMP = "www.yhdmp.net";
  1182.  
  1183. class YhdmpHandler extends Handler {
  1184. constructor() {
  1185. super();
  1186. this.videoElement = null;
  1187. }
  1188. getCurrentVideoUrl() {
  1189. let iframe = document.getElementById('yh_playfram');
  1190. if (!iframe) {
  1191. return;
  1192. }
  1193. this.videoElement = iframe.contentWindow.document.getElementsByTagName("video")[0];
  1194. let videoUrl = iframe.src;
  1195. let startIndex = videoUrl.indexOf('url=http') + 4;
  1196. let endIndex = videoUrl.indexOf('&getplay_url=');
  1197. currentVideoUrl = decodeURIComponent(videoUrl.substring(startIndex, endIndex));
  1198. this.checkCurrentVideoUrl();
  1199. }
  1200. getStartTime() {
  1201. return this.getStartTimeByClassName("dplayer-ptime");
  1202. }
  1203. pauseCurrentVideo() {
  1204. this.videoElement.pause();
  1205. }
  1206. }
  1207.  
  1208. // 巴哈姆特
  1209. const GAMER = "ani.gamer.com.tw";
  1210. // 巴哈姆特 API
  1211. const GAMER_API = "https://ani.gamer.com.tw/ajax/m3u8.php";
  1212.  
  1213. class GamerHandler extends Handler {
  1214. getCurrentVideoUrl() {
  1215. let index = currentUrl.indexOf("sn=") + 3;
  1216. if (index == -1) {
  1217. return;
  1218. }
  1219. let sn = currentUrl.substring(index);
  1220. index = sn.indexOf("&");
  1221. if (index != -1) {
  1222. sn = sn.substring(0, index);
  1223. }
  1224. let device = localStorage.ANIME_deviceid;
  1225. // debug("sn: " + sn + ", device: " + device);
  1226. $.ajax({
  1227. type: "GET",
  1228. url: GAMER_API + "?sn=" + sn + "&device=" + device,
  1229. xhrFields: {
  1230. withCredentials: true
  1231. },
  1232. success: function (res) {
  1233. // debug(res);
  1234. currentVideoUrl = JSON.parse(res).src;
  1235. handler.checkCurrentVideoUrl();
  1236. }
  1237. })
  1238. }
  1239. getStartTime() {
  1240. return this.getStartTimeByClassName("vjs-current-time-display");
  1241. }
  1242. getUrlProtocolLink() {
  1243. let urlProtocol = new UrlProtocol;
  1244. urlProtocol.appendStartTime();
  1245. urlProtocol.appendTitle();
  1246. urlProtocol.appendProxy();
  1247. urlProtocol.append('--http-header-fields="origin: https://ani.gamer.com.tw"');
  1248. return urlProtocol.getLink();
  1249. }
  1250. }
  1251.  
  1252. // alist
  1253. const ALIST = "alist";
  1254.  
  1255. class AlistHandler extends Handler {
  1256. getCurrentVideoUrl() {
  1257. let videoElement = document.getElementsByTagName("video")[0];
  1258. if (!videoElement) {
  1259. return;
  1260. }
  1261. let src = videoElement.src;
  1262. let index = src.indexOf("?");
  1263. if (index != -1) {
  1264. currentVideoUrl = src.substring(0, index + 1) + encodeURIComponent(src.substring(index + 1));
  1265. } else {
  1266. currentVideoUrl = src;
  1267. }
  1268. handler.checkCurrentVideoUrl();
  1269. }
  1270. }
  1271.  
  1272. // 优质资源库
  1273. const HDZYK = "hdzyk.com, 1080zyk1.com, 1080zyk1.com, 1080zyk1.com, 1080zyk1.com, 1080zyk1.com";
  1274.  
  1275. class HdzykHandler extends Handler {
  1276. constructor() {
  1277. super();
  1278. window.addEventListener('message', function (event) {
  1279. currentVideoUrl = event.data;
  1280. this.checkCurrentVideoUrl();
  1281. window.removeEventListener("message", () => { });
  1282. }, false);
  1283. }
  1284. pauseCurrentVideo() {
  1285. document.getElementsByTagName("iframe")[1].contentWindow.postMessage("pause", "https://" + ZYKBF);
  1286. }
  1287. }
  1288.  
  1289. // 优质资源库实际播放地址
  1290. const ZYKBF = "vip.zykbf.com";
  1291.  
  1292. class ZykbfHandler extends Handler {
  1293. constructor() {
  1294. super();
  1295. // 监听父页面暂停指令
  1296. window.addEventListener("message", function (event) {
  1297. if (event.data == "pause") {
  1298. document.getElementsByTagName('video')[0].pause();
  1299. }
  1300. }, false);
  1301. }
  1302. getCurrentVideoUrl() {
  1303. let startIndex = currentUrl.indexOf('url=http') + 4;
  1304. let endIndex = currentUrl.indexOf('m3u8') + 4;
  1305. currentVideoUrl = decodeURIComponent(currentUrl.substring(startIndex, endIndex));
  1306. if (this.checkCurrentVideoUrl()) {
  1307. window.parent.postMessage(currentVideoUrl, "*");
  1308. }
  1309. }
  1310. checkCurrentVideoUrl() {
  1311. return this.baseCheckCurrentVideoUrl();
  1312. }
  1313. }
  1314.  
  1315. // 动漫之家
  1316. const KK151 = "www.kk151.com";
  1317.  
  1318. class Kk151Handler extends Handler {
  1319. constructor() {
  1320. super();
  1321. window.addEventListener('message', function (event) {
  1322. currentVideoUrl = event.data;
  1323. handler.checkCurrentVideoUrl();
  1324. window.removeEventListener("message", () => { });
  1325. }, false);
  1326. }
  1327. }
  1328.  
  1329. // 动漫之家实际播放地址
  1330. const JXM3U8TV = "jx.m3u8.tv, jx.wolongzywcdn.com:65, www.m3u8.tv.cdn.8old.cn, jx.wujinkk.com, www.ikdmjx.com, hls.kuaibofang.com, jx.jxbdzyw.com";
  1331.  
  1332. class Jxm3u8tvHandler extends Handler {
  1333. getCurrentVideoUrl() {
  1334. let startIndex = currentUrl.indexOf('url=http') + 4;
  1335. if (startIndex == 3) {
  1336. startIndex = currentUrl.indexOf('url=%20http') + 7;
  1337. }
  1338. let endIndex = currentUrl.lastIndexOf('m3u8') + 4;
  1339. currentVideoUrl = decodeURIComponent(currentUrl.substring(startIndex, endIndex));
  1340. if (this.checkCurrentVideoUrl()) {
  1341. window.top.postMessage(currentVideoUrl, "*");
  1342. }
  1343. }
  1344. checkCurrentVideoUrl() {
  1345. return this.baseCheckCurrentVideoUrl();
  1346. }
  1347. }
  1348.  
  1349. // LIBVIO
  1350. const LIBVIO = "libvio.fun, www.libvio.me, libvio.me";
  1351.  
  1352. class LibvioHandler extends Handler {
  1353. constructor() {
  1354. super();
  1355. window.addEventListener('message', function (event) {
  1356. currentVideoUrl = event.data;
  1357. handler.checkCurrentVideoUrl();
  1358. window.removeEventListener("message", () => { });
  1359. }, false);
  1360. }
  1361. pauseCurrentVideo() {
  1362. document.getElementsByTagName("iframe")[2].contentWindow.postMessage("pause", "https://" + LIBVIO_PLAYER);
  1363. }
  1364. }
  1365.  
  1366. // LIBVIO 实际播放地址
  1367. const LIBVIO_PLAYER = "sh-data-s02.chinaeast2.cloudapp.chinacloudapi.cn, p.cfnode1.xyz";
  1368.  
  1369. class LibvioPlayerHandler extends Handler {
  1370. constructor() {
  1371. super();
  1372. // 监听父页面暂停指令
  1373. window.addEventListener("message", function (event) {
  1374. if (event.data == "pause") {
  1375. document.getElementsByTagName('video')[0].pause();
  1376. }
  1377. }, false);
  1378. }
  1379. getCurrentVideoUrl() {
  1380. currentVideoUrl = urls;
  1381. if (this.checkCurrentVideoUrl()) {
  1382. window.top.postMessage(currentVideoUrl, "*");
  1383. }
  1384. }
  1385. checkCurrentVideoUrl() {
  1386. return this.baseCheckCurrentVideoUrl();
  1387. }
  1388. }
  1389.  
  1390. // 哔嘀影视
  1391. const BDYS01 = "www.bdys01.com";
  1392.  
  1393. class Bdys01Handler extends Handler {
  1394. getCurrentVideoUrl() {
  1395. currentVideoUrl = document.getElementsByTagName("video")[0].src;
  1396. this.checkCurrentVideoUrl();
  1397. }
  1398. getStartTime() {
  1399. return this.getStartTimeByClassName("dplayer-ptime");
  1400. }
  1401. }
  1402.  
  1403. // 无名小站
  1404. const BTNULL = "www.btnull.org";
  1405.  
  1406. class BtnullHandler extends Handler {
  1407. getCurrentVideoUrl() {
  1408. let html = document.documentElement.outerHTML;
  1409. let index = html.indexOf("_BT.PC.player({url:'http") + 20;
  1410. html = html.substring(index);
  1411. index = html.indexOf("m3u8") + 4;
  1412. currentVideoUrl = html.substring(0, index);
  1413. handler.checkCurrentVideoUrl();
  1414. }
  1415. }
  1416.  
  1417. // 片库
  1418. const PKMP4 = "www.pkmp4.com";
  1419.  
  1420. class Pkmp4Handler extends Handler {
  1421. getCurrentVideoUrl() {
  1422. currentVideoUrl = player_aaaa.url;
  1423. handler.checkCurrentVideoUrl();
  1424. }
  1425. pauseCurrentVideo() {
  1426. document.getElementsByTagName("iframe")[2].contentWindow.document.getElementsByTagName("video")[0].pause();
  1427. }
  1428. }
  1429.  
  1430. // 稀饭动漫
  1431. const XFANI = "dick.xfani.com";
  1432.  
  1433. class XfaniHandler extends Handler {
  1434. constructor() {
  1435. super();
  1436. window.addEventListener('message', function (event) {
  1437. currentVideoUrl = event.data;
  1438. handler.checkCurrentVideoUrl();
  1439. window.removeEventListener("message", () => { });
  1440. }, false);
  1441. }
  1442. pauseCurrentVideo() {
  1443. document.getElementsByTagName("iframe")[2].contentWindow.postMessage("pause", "https://" + XFANI_PLAYER);
  1444. }
  1445. }
  1446.  
  1447. // 稀饭动漫实际播放地址
  1448. const XFANI_PLAYER = "m3.moedot.net";
  1449.  
  1450. class XfaniPlayerHandler extends Handler {
  1451. constructor() {
  1452. super();
  1453. window.addEventListener("message", function (event) {
  1454. if (event.data == "pause") {
  1455. document.getElementsByTagName('video')[0].pause();
  1456. }
  1457. }, false);
  1458. }
  1459. getCurrentVideoUrl() {
  1460. if (config.url.indexOf(".m3u8") > 0 || config.url.indexOf(".mp4") > 0 || config.url.indexOf(".flv") > 0) {
  1461. currentVideoUrl = config.url;
  1462. if (handler.checkCurrentVideoUrl()) {
  1463. window.top.postMessage(currentVideoUrl, "*");
  1464. }
  1465. } else {
  1466. $.ajax({
  1467. type: "POST",
  1468. url: "api_config.php",
  1469. data: { "url": config.url, "time": config.time, "key": config.key, "title": config.title },
  1470. success: function (res) {
  1471. if (res.code == "200") {
  1472. currentVideoUrl = res.url;
  1473. if (handler.checkCurrentVideoUrl()) {
  1474. window.top.postMessage(currentVideoUrl, "*");
  1475. }
  1476. }
  1477. }
  1478. });
  1479. }
  1480. }
  1481. checkCurrentVideoUrl() {
  1482. return this.baseCheckCurrentVideoUrl();
  1483. }
  1484. }
  1485.  
  1486.  
  1487. // 最大尝试次数
  1488. const MAX_TRY_TIME = 8;
  1489. // 定时器
  1490. var timers;
  1491. // 当前页面链接
  1492. var currentUrl;
  1493. // 当前页面域名
  1494. var currentDomain;
  1495. // 当前页面视频链接
  1496. var currentVideoUrl;
  1497. // 当前页面音频链接
  1498. var currentAudioUrl;
  1499. // 巴哈姆特视频时长
  1500. var gamerDurationTime;
  1501.  
  1502. // 初始化当前页信息
  1503. function initCurrentPageInfo() {
  1504. // debug("init current page info ......");
  1505. document.getElementById(BUTTON_DIV).style.display = DISPLAY_NONE;
  1506. if (timers) {
  1507. for (let timer of timers) {
  1508. // debug("clear timer");
  1509. clearTimeout(timer);
  1510. }
  1511. }
  1512. currentUrl = window.location.href;
  1513. currentDomain = window.location.host;
  1514. currentVideoUrl = "";
  1515. ddrkPlayStatus = false;
  1516. }
  1517. // 创建处理器
  1518. function createHandler() {
  1519. // debug("start create handler: " + currentDomain);
  1520. if (BILIBILI.indexOf(currentDomain) != -1) {
  1521. handler = new BilibiliHandler();
  1522. } else if (BILIBILI_LIVE.indexOf(currentDomain) != -1) {
  1523. handler = new BilibiliLiveHandler();
  1524. } else if (DDRK.indexOf(currentDomain) != -1) {
  1525. handler = new DdrkHandler();
  1526. } else if (YOUTUBE.indexOf(currentDomain) != -1) {
  1527. handler = new YoutubeHandler();
  1528. } else if (DM6CC.indexOf(currentDomain) != -1) {
  1529. handler = new Dm6ccHandler();
  1530. } else if (DMLACC.indexOf(currentDomain) != -1) {
  1531. handler = new DmlaccHandler();
  1532. } else if (YHDMJX.indexOf(currentDomain) != -1) {
  1533. handler = new YhdmjxHandler();
  1534. } else if (DM233.indexOf(currentDomain) != -1) {
  1535. handler = new Dm233Handler();
  1536. } else if (DMH8.indexOf(currentDomain) != -1) {
  1537. handler = new Dmh8Handler();
  1538. } else if (YHDMP.indexOf(currentDomain) != -1) {
  1539. handler = new YhdmpHandler();
  1540. } else if (GAMER.indexOf(currentDomain) != -1) {
  1541. handler = new GamerHandler();
  1542. } else if (HDZYK.indexOf(currentDomain) != -1) {
  1543. handler = new HdzykHandler();
  1544. } else if (ZYKBF.indexOf(currentDomain) != -1) {
  1545. handler = new ZykbfHandler();
  1546. } else if (KK151.indexOf(currentDomain) != -1) {
  1547. handler = new Kk151Handler();
  1548. } else if (JXM3U8TV.indexOf(currentDomain) != -1) {
  1549. handler = new Jxm3u8tvHandler();
  1550. } else if (LIBVIO.indexOf(currentDomain) != -1) {
  1551. handler = new LibvioHandler();
  1552. } else if (LIBVIO_PLAYER.indexOf(currentDomain) != -1) {
  1553. handler = new LibvioPlayerHandler();
  1554. } else if (BDYS01.indexOf(currentDomain) != -1) {
  1555. handler = new Bdys01Handler();
  1556. } else if (BTNULL.indexOf(currentDomain) != -1) {
  1557. handler = new BtnullHandler();
  1558. } else if (PKMP4.indexOf(currentDomain) != -1) {
  1559. handler = new Pkmp4Handler();
  1560. } else if (XFANI.indexOf(currentDomain) != -1) {
  1561. handler = new XfaniHandler();
  1562. } else if (XFANI_PLAYER.indexOf(currentDomain) != -1) {
  1563. handler = new XfaniPlayerHandler();
  1564. } else {
  1565. if (document.title.toLowerCase().indexOf(ALIST) != -1) {
  1566. handler = new AlistHandler();
  1567. }
  1568. }
  1569. }
  1570. // 刷新视频链接
  1571. function refreshCurrentVideoUrl() {
  1572. // debug("refresh current video url: " + currentVideoUrl);
  1573. // debug("current url: " + currentUrl);
  1574. timers = new Array();
  1575. let tryTime = 0;
  1576. while (tryTime < MAX_TRY_TIME) {
  1577. timers[tryTime] = setTimeout(function () {
  1578. if (!handler.checkCurrentVideoUrl()) {
  1579. handler.getCurrentVideoUrl();
  1580. }
  1581. // debug("timer done");
  1582. }, tryTime * 2000 + 700);
  1583. tryTime = tryTime + 1;
  1584. }
  1585. }
  1586. // 页面变更监听器
  1587. function pageChangeListener() {
  1588. // debug("page change listener");
  1589. let needRefresh = false;
  1590. let newCurrentUrl = window.location.href;
  1591. if (currentUrl != newCurrentUrl) {
  1592. needRefresh = true;
  1593. }
  1594. // 巴哈姆特
  1595. if (!needRefresh && GAMER.indexOf(currentDomain) != -1) {
  1596. let oldGamerDurationTime = gamerDurationTime;
  1597. let durationDiv = document.getElementsByClassName("vjs-duration-display")[0];
  1598. if (durationDiv) {
  1599. gamerDurationTime = durationDiv.innerHTML;
  1600. if (oldGamerDurationTime && oldGamerDurationTime != gamerDurationTime) {
  1601. needRefresh = true;
  1602. }
  1603. }
  1604. }
  1605. if (needRefresh) {
  1606. // debug("page change");
  1607. initCurrentPageInfo();
  1608. refreshCurrentVideoUrl();
  1609. }
  1610. }
  1611. // 初始化
  1612. function init() {
  1613. console.log("Play-With-MPV ......");
  1614. currentUrl = window.location.href;
  1615. currentDomain = window.location.host;
  1616. if (currentUrl.startsWith("https://live.bilibili.com/p/html/live-web-mng/index.html")) {
  1617. console.log("排除页面:" + currentUrl);
  1618. } else {
  1619. // 创建处理器
  1620. createHandler();
  1621. if (handler) {
  1622. // 添加组件和监听器
  1623. appendHTML();
  1624. appendCSS();
  1625. addListener();
  1626.  
  1627. // 初始化页面信息
  1628. initCurrentPageInfo();
  1629. // 刷新视频链接
  1630. refreshCurrentVideoUrl();
  1631. // 定时监听页面变化
  1632. setInterval(pageChangeListener, 700);
  1633. } else {
  1634. console.log("create handler fail");
  1635. }
  1636. }
  1637. }
  1638. init();