HTML5视频播放工具

三大功能 。启用HTML5播放;万能网页全屏;添加快捷键:快进、快退、暂停/播放、音量、下一集、切换(网页)全屏、上下帧、播放速度。支持视频站点:油管、TED、优.土、QQ、B站、西瓜视频、爱奇艺、A站、PPTV、芒果TV、咪咕视频、新浪、微博、网易[娱乐、云课堂、新闻]、搜狐、风行、百度云视频等;直播:斗鱼、YY、虎牙、龙珠、战旗。可增加自定义站点

As of 01.02.2022. See ბოლო ვერსია.

  1. // ==UserScript==
  2. // @name HTML5视频播放工具
  3. // @description 三大功能 。启用HTML5播放;万能网页全屏;添加快捷键:快进、快退、暂停/播放、音量、下一集、切换(网页)全屏、上下帧、播放速度。支持视频站点:油管、TED、优.土、QQ、B站、西瓜视频、爱奇艺、A站、PPTV、芒果TV、咪咕视频、新浪、微博、网易[娱乐、云课堂、新闻]、搜狐、风行、百度云视频等;直播:斗鱼、YY、虎牙、龙珠、战旗。可增加自定义站点
  4. // @homepage https://bbs.kafan.cn/thread-2093014-1-1.html
  5. // @include https://*.qq.com/*
  6. // @exclude https://user.qzone.qq.com/*
  7. // @include https://www.weiyun.com/video_*
  8. // @include https://v.youku.com/v_show/id_*
  9. // @include https://vku.youku.com/live/*
  10. // @include https://video.tudou.com/v/*
  11. // @include https://www.iqiyi.com/*
  12. // @include https://live.bilibili.com/*
  13. // @include https://www.bilibili.com/*
  14. // @include https://www.ixigua.com/*
  15. // @include https://www.acfun.cn/*
  16. // @include http://v.pptv.com/show/*
  17. // @include https://v.pptv.com/show/*
  18. // @include https://www.miguvideo.com/*
  19. // @include https://tv.sohu.com/*
  20. // @include https://film.sohu.com/album/*
  21. // @include https://www.mgtv.com/*
  22. // @version 1.8.8
  23. // @include https://pan.baidu.com/*
  24. // @include https://yun.baidu.com/*
  25. // @include https://*.163.com/*
  26. // @include https://*.icourse163.org/*
  27. // @include https://*.sina.com.cn/*
  28. // @include https://video.sina.cn/*
  29. // @include http://k.sina.*
  30. // @include https://k.sina.*
  31. // @include https://weibo.com/*
  32. // @include https://*.weibo.com/*
  33. // @include https://pan.baidu.com/*
  34. // @include https://yun.baidu.com/*
  35. // @include http://v.ifeng.com/*
  36. // @include https://v.ifeng.com/*
  37. // @include http://news.mtime.com/*
  38. // @include http://video.mtime.com/*
  39. // @GM_info
  40. // @include https://www.youtube.com/watch?v=*
  41. // @include https://www.ted.com/talks/*
  42. // @include https://www.yy.com/*
  43. // @include https://www.huya.com/*
  44. // @include https://v.douyu.com/*
  45. // @include https://www.douyu.com/*
  46. // @include http://star.longzhu.com/*
  47. // @include https://star.longzhu.com/*
  48. // @include https://www.zhanqi.tv/*
  49. // @run-at document-start
  50. // @include */play*
  51. // @include *play/*
  52. // @include https://www.imeiju.pro/ckplayerx/m3u8.php*
  53. // @include *://ww4.hanjutvaa.com/index.php*
  54. // @include *://olevod.com/static/player/*
  55. // @grant GM_addStyle
  56. // @exclude https://www.imeiju.pro/Play/*
  57. // @exclude https://www.imeiju.pro/new/Play/*
  58. // @exclude *://www.hanjutvaa.com/player/*
  59. // @exclude *://www.olevod.com/index.php/vod/play/id/*
  60. // @exclude *://www.olevod.tv/index.php/vod/play/id/*
  61. // @exclude *://olevod.com/index.php/vod/play/id/*
  62. // @exclude *://olevod.tv/index.php/vod/play/id/*
  63. // @grant window.onurlchange
  64. // @grant unsafeWindow
  65. // @grant GM_registerMenuCommand
  66. // @grant GM_setValue
  67. // @grant GM_getValue
  68. // @namespace https://greasyfork.org/users/7036
  69. // ==/UserScript==
  70.  
  71. 'use strict';
  72. const w = unsafeWindow || window;
  73. const { host, pathname: path } = location;
  74. const d = document, find = [].find;
  75. let v, _fp, _fs, by; // document.body
  76. const observeOpt = {childList : true, subtree : true};
  77. const noopFn = function(){};
  78. const validEl = e => e && e.offsetParent;
  79. const q = (css, p = d) => p.querySelector(css);
  80. const delElem = e => e.remove();
  81. const $$ = function(c, cb = delElem, doc = d) {
  82. if (!c || !c.length) return;
  83. if (typeof c === 'string') c = doc.querySelectorAll(c);
  84. if (!cb) return c;
  85. for (let e of c) if (e && cb(e)=== !1) break;
  86. };
  87. const r1 = (regp, s) => regp.test(s) && RegExp.$1;
  88. const log = console.log.bind(
  89. console,
  90. '%c脚本[%s] 反馈:%s\n%s',
  91. 'color:#c3c;font-size:1.2em',
  92. GM_info.script.name,
  93. GM_info.script.homepage
  94. );
  95. const gmFuncOfCheckMenu = (title, saveName, defaultVal = true) => {
  96. const r = GM_getValue(saveName, defaultVal);
  97. if (r) title = '√ '+ title;
  98. GM_registerMenuCommand(title, () => {
  99. GM_setValue(saveName, !r);
  100. location.reload();
  101. });
  102. return r;
  103. };
  104. const sleep = ms => new Promise(resolve => { setTimeout(resolve, ms) });
  105. /*
  106. const cookie = new Proxy(noopFn, {
  107. apply(target, ctx, args) { //清理cookie
  108. const keys = document.cookie.match(/[^ =;]+(?=\=)/g);
  109. if (keys) {
  110. const val = '=; expires=' + new Date(0).toUTCString() +'; domain=.; path=/';
  111. for (const k of keys) document.cookie = k + val;
  112. }
  113. // return Reflect.apply(...arguments);
  114. },
  115. get(target, name) { // 读取cookie
  116. const r = r1(new RegExp(name +'=([^;]*)'), document.cookie);
  117. if (r) return decodeURIComponent(r);
  118. },
  119. set(target, name, value, receiver) { // 写入cookie
  120. let s, v, expires,
  121. oneParam = typeof value == 'string';
  122. if (oneParam) {
  123. expires = 6;
  124. v = value;
  125. } else {
  126. v = value.val;
  127. expires = value.expires || 6;
  128. delete value.expires;
  129. }
  130. s = name + '=' + encodeURIComponent(v);
  131.  
  132. if (expires && (typeof expires == 'number' || expires.toUTCString)) {
  133. let date;
  134. if (typeof expires == 'number') {
  135. date = new Date();
  136. date.setTime(expires * 24 * 3600000 + date.getTime());
  137. } else {
  138. date = expires;
  139. }
  140. s += '; expires=' + date.toUTCString();
  141. }
  142. if (!oneParam) for (const k in value) s += '; ' + k + '=' + value[k];
  143. document.cookie = s;
  144. return true;
  145. },
  146. deleteProperty(target, name, descriptor) {// 删除cookie
  147. document.cookie = name + '=; path=/; expires='+ new Date(0).toUTCString();
  148. return true;
  149. }
  150. });
  151. const onceEvent = (ctx, eName) => new Promise(resolve => ctx.addEventListener(eName, resolve));
  152. const promisify = (fn) => (...args) => new Promise((resolve, reject) => {
  153. args.push(resolve);
  154. fn.apply(this, args);
  155. }); */
  156. const hookAttachShadow = (cb) => {
  157. try {
  158. const _attachShadow = Element.prototype.attachShadow;
  159. Element.prototype.attachShadow = function(opt) {
  160. opt.mode = 'open';
  161. const shadowRoot = _attachShadow.call(this, opt);
  162. cb(shadowRoot);
  163. return shadowRoot;
  164. };
  165. } catch (e) {
  166. console.error('Hack attachShadow error', e);
  167. }
  168. };
  169. const getStyle = (o, s) => {
  170. if (o.style[s]) return o.style[s];
  171. if (getComputedStyle) {
  172. const x = getComputedStyle(o, '');
  173. s = s.replace(/([A-Z])/g,'-$1').toLowerCase();
  174. return x && x.getPropertyValue(s);
  175. }
  176. };
  177. const doClick = e => {
  178. if (typeof e === 'string') e = q(e);
  179. if (e) { e.click ? e.click() : e.dispatchEvent(new MouseEvent('click')) };
  180. };
  181. const clickDualButton = btn => { // 2合1 按钮
  182. !btn.nextSibling || getStyle(btn, 'display') !== 'none' ? doClick(btn) : doClick(btn.nextSibling);
  183. };
  184. const intervalQuery = (cb, condition, stop = true) => {
  185. const fn = typeof condition === 'string' ? q.bind(null, condition) : condition;
  186. const t = setInterval(() => {
  187. const r = fn();
  188. if (r) {
  189. stop && clearInterval(t);
  190. cb(r);
  191. }
  192. }, 300);
  193. return t;
  194. };
  195. const goNextMV = () => {
  196. const s = location.pathname;
  197. const m = s.match(/(\d+)(\D*)$/);
  198. const d = +m[1] + 1;
  199. location.assign(s.slice(0, m.index) + d + m[2]);
  200. };
  201. const firefoxVer = r1(/Firefox\/(\d+)/, navigator.userAgent);
  202. const isEdge = / Edge?\//.test(navigator.userAgent);
  203. const fakeUA = ua => Object.defineProperty(navigator, 'userAgent', {
  204. value: ua,
  205. writable: false,
  206. configurable: false,
  207. enumerable: true
  208. });
  209. const getMainDomain = host => {
  210. const a = host.split('.');
  211. let i = a.length - 2;
  212. if (/^(com?|cc|tv|net|org|gov|edu)$/.test(a[i])) i--;
  213. return a[i];
  214. };
  215. const ua_chrome = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3626.121 Safari/537.36';
  216.  
  217. class FullScreen {
  218. constructor(e) {
  219. let fn = d.exitFullscreen || d.webkitExitFullscreen || d.mozCancelFullScreen || d.msExitFullscreen || noopFn;
  220. this.exit = fn.bind(d);
  221. fn = e.requestFullscreen || e.webkitRequestFullScreen || e.mozRequestFullScreen || e.msRequestFullScreen || noopFn;
  222. this.enter = fn.bind(e);
  223. }
  224. static isFull() {
  225. return !!(d.fullscreen || d.webkitIsFullScreen || d.mozFullScreen ||
  226. d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement);
  227. }
  228. toggle() {
  229. FullScreen.isFull() ? this.exit() : this.enter();
  230. }
  231. }
  232.  
  233. //万能网页全屏, 参考了:https://github.com/gooyie/ykh5p
  234. class FullPage {
  235. constructor(container, onSwitch) {
  236. this._isFull = !1;
  237. this._onSwitch = onSwitch;
  238. this.container = container || FullPage.getPlayerContainer(v);
  239. GM_addStyle(
  240. `.gm-fp-body .gm-fp-zTop {
  241. position: relative !important;
  242. z-index: 2147483647 !important;
  243. }
  244. .gm-fp-wrapper, .gm-fp-body{ overflow:hidden !important; }
  245. .gm-fp-wrapper .gm-fp-innerBox {
  246. width: 100% !important;
  247. height: 100% !important;
  248. }
  249. .gm-fp-wrapper {
  250. display: block !important;
  251. position: fixed !important;
  252. width: 100% !important;
  253. height: 100% !important;
  254. top: 0 !important;
  255. left: 0 !important;
  256. background: #000 !important;
  257. z-index: 2147483647 !important;
  258. }`
  259. );
  260. }
  261. static getPlayerContainer(video) {
  262. let e = video, p = e.parentNode;
  263. const { clientWidth: wid, clientHeight: h } = e;
  264. do {
  265. e = p;
  266. p = e.parentNode;
  267. } while (e.nodeName == 'DIV' && p.clientWidth-wid < 5 && p.clientHeight-h < 5);
  268. //e 为返回值,在此之后不能变了
  269. while (p !== by) p = p.parentNode || p.host;
  270. return e;
  271. }
  272. static isFull(e) {
  273. return w.innerWidth - e.clientWidth < 5 && w.innerHeight - e.clientHeight < 5;
  274. }
  275. toggle() {
  276. // assert(this.container);
  277. if (!this.container.contains(v)) this.container = FullPage.getPlayerContainer(v);
  278. const cb = this._onSwitch;
  279. if (!this._isFull && cb) cb(true);
  280. by.classList.toggle('gm-fp-body');
  281. let e = v;
  282. while (e != this.container) {
  283. e.classList.toggle('gm-fp-innerBox');
  284. e = e.parentNode;
  285. }
  286. e.classList.toggle('gm-fp-wrapper');
  287. e = e.parentNode;
  288. while (e != by) {
  289. e.classList.toggle('gm-fp-zTop');
  290. e = e.parentNode;
  291. }
  292. this._isFull = !this._isFull;
  293. if (!this._isFull && cb) setTimeout(cb, 199, !1);
  294. }
  295. }
  296.  
  297. const u = getMainDomain(host);
  298. const events = {
  299. on(name, fn) {
  300. this[name] = fn;
  301. },
  302. undo(name) {
  303. delete this[name];
  304. },
  305. handle(name, ...a) {
  306. return this[name] && this[name].apply(this, a);
  307. }
  308. };
  309. const app = {
  310. isLive: !1,
  311. disableDBLClick: !1,
  312. isClickOnVideo: !1,
  313. multipleV: !1, //多视频页面
  314. isNumURL: !1, //网址数字分集
  315. disableKeys: [],
  316. shellEvent() {
  317. const fn = ev => {
  318. if (ev.target.closest('button')) return;
  319. ev.stopPropagation(); // preventDefault
  320. ev.stopImmediatePropagation();
  321. this.toggleFP();
  322. };
  323. const e = this.isClickOnVideo ? v : this.mvShell;
  324. !this.isLive && e.addEventListener('mousedown', ev => {
  325. if (1 == ev.button) {
  326. ev.stopPropagation();
  327. v.currentTime += 5;
  328. }
  329. });
  330. !this.disableDBLClick && e.addEventListener('dblclick', fn);
  331. },
  332. setShell() {
  333. const e = this.getDPlayer() || this.getVjsPlayer() ||
  334. (this.shellCSS && q(this.shellCSS)) ||
  335. (top != self ? by : FullPage.getPlayerContainer(v));
  336. if (e && this.mvShell !== e) {
  337. this.mvShell = e;
  338. this.shellEvent();
  339. }
  340. },
  341. checkMV() {
  342. if (this.vList) {
  343. const e = this.findMV();
  344. if (e != v) {
  345. v = e;
  346. this.btnPlay = this.btnNext = this.btnFP = this.btnFS = _fs = _fp = null;
  347. if (!this.isLive) {
  348. v.playbackRate = localStorage.mvPlayRate || 1;
  349. v.addEventListener('ratechange', ev => {
  350. localStorage.mvPlayRate = v.playbackRate;
  351. });
  352. }
  353. this.setShell();
  354. }
  355. }
  356. if (!validEl(this.mvShell)) {
  357. this.mvShell = null;
  358. this.setShell();
  359. }
  360. this.checkUI();
  361. return v;
  362. },
  363. getDPlayer() {
  364. const e = v.closest('.dplayer');
  365. if (e) {
  366. this.btnFP = q('.dplayer-full-in-icon > span', e);
  367. this.btnFS = q('.dplayer-full-icon', e);
  368. e.closest('body > *').classList.add('gm-dp-zTop');
  369. }
  370. return e;
  371. },
  372. getVjsPlayer() {
  373. const e = v.closest('.video-js');
  374. if (e) {
  375. this.btnFS = q('.vjs-control-bar > button.vjs-button:nth-last-of-type(1)');
  376. this.webfullCSS = '.vjs-control-bar > button.vjs-button[title$="全屏"]:nth-last-of-type(2)';
  377. }
  378. return e;
  379. },
  380. toggleFP() {
  381. self != top ? top.postMessage({id: 'gm-h5-toggle-iframeWebFull'}, '*')
  382. : _fp ? _fp.toggle() : clickDualButton(this.btnFP);
  383. },
  384. hotKey(e) {
  385. const t = e.target;
  386. if (e.ctrlKey || e.altKey || t.contentEditable=='true' ||
  387. /INPUT|TEXTAREA|SELECT/.test(t.nodeName)) return;
  388. if (e.shiftKey && ![13,37,39].includes(e.keyCode)) return;
  389. if (this.disableKeys.includes(e.keyCode)) return;
  390. if (this.isLive && [37,39,78,88,67,90].includes(e.keyCode)) return;
  391. if (!this.checkMV()) return;
  392. if (events.handle('keydown', e)) return;
  393. if (!e.shiftKey && this.mvShell && this.mvShell.contains(t) && [32,37,39].includes(e.keyCode)) return;
  394. e.stopImmediatePropagation();
  395. let n;
  396. switch (e.keyCode) {
  397. case 32: //space
  398. if (this.btnPlay) clickDualButton(this.btnPlay);
  399. else v.paused ? v.play() : v.pause();
  400. e.preventDefault();
  401. break;
  402. case 37: n = e.shiftKey ? -20 : -5; //left 快退5秒,shift加速
  403. //right 快进5秒,shift加速
  404. case 39: n = n || (e.shiftKey ? 20 : 5);
  405. v.currentTime += n;
  406. break;
  407. case 78: // N 下一集
  408. if (self != top) top.postMessage({id: 'gm-h5-play-next'}, '*');
  409. else if (this.btnNext) doClick(this.btnNext);
  410. else if (this.isNumURL) goNextMV();
  411. break;
  412. case 38: n = 0.1; //加音量
  413. case 40: n = n || -0.1; //降音量
  414. n += v.volume;
  415. if (0 <= n && n <= 1) v.volume = +n.toFixed(1);
  416. e.preventDefault();
  417. break;
  418. case 13: //回车键。 全屏
  419. if (e.shiftKey) {
  420. this.toggleFP();
  421. } else {
  422. _fs ? _fs.toggle() : clickDualButton(this.btnFS);
  423. }
  424. break;
  425. case 27: //esc
  426. if (FullScreen.isFull()) {
  427. _fs ? _fs.exit() : clickDualButton(this.btnFS);
  428. } else if (self != top) {
  429. top.postMessage({id: 'gm-h5-is-iframeWebFull'}, '*');
  430. } else if (FullPage.isFull(v)) {
  431. _fp ? _fp.toggle() : clickDualButton(this.btnFP);
  432. }
  433. break;
  434. case 67: n = 0.1; //按键C:加速播放 +0.1
  435. case 88: n = n || -0.1; //按键X:减速播放 -0.1
  436. n += v.playbackRate;
  437. if (0 < n && n <= 16) v.playbackRate = +n.toFixed(2);
  438. break;
  439. case 90: //按键Z:正常速度播放
  440. v.playbackRate = 1;
  441. break;
  442. case 70: n = 0.03; //按键F:下一帧
  443. case 68: n = n || -0.03; //按键D:上一帧
  444. v.currentTime += n;
  445. v.pause();
  446. break;
  447. default: return;
  448. }
  449. e.stopPropagation();
  450. },
  451. checkUI() {
  452. if (this.webfullCSS && !validEl(this.btnFP)) this.btnFP = q(this.webfullCSS);
  453. if (this.btnFP) _fp = null;
  454. else if (!_fp && self == top) _fp = new FullPage(this.mvShell, this.switchFP);
  455.  
  456. if (this.fullCSS && !validEl(this.btnFS)) this.btnFS = q(this.fullCSS);
  457. if (this.btnFS) _fs = null;
  458. else if (!_fs) _fs = new FullScreen(v);
  459.  
  460. if (this.nextCSS && !validEl(this.btnNext)) this.btnNext = q(this.nextCSS);
  461. if (this.playCSS && !validEl(this.btnPlay)) this.btnPlay = q(this.playCSS);
  462. },
  463. switchFP(toFull) {
  464. if (toFull) {
  465. for (let e of this.vSet) this.viewObserver.unobserve(e);
  466. } else {
  467. for (let e of this.vList) this.viewObserver.observe(e);
  468. }
  469. },
  470. onGrowVList() {
  471. if (this.vList.length == this.vCount) return;
  472. if (this.viewObserver) {
  473. for (let e of this.vList) {
  474. if (!this.vSet.has(e)) this.viewObserver.observe(e);
  475. }
  476. } else {
  477. const config = {
  478. rootMargin: '0px',
  479. threshold: 0.9
  480. };
  481. this.viewObserver = new IntersectionObserver(this.onIntersection.bind(this), config);
  482. for (let e of this.vList) this.viewObserver.observe(e);
  483. }
  484. this.vSet = new Set(this.vList);
  485. this.vCount = this.vList.length;
  486. },
  487. onIntersection(entries) {
  488. if (this.vList.length < 2) return;
  489. const entry = find.call(entries, k => k.isIntersecting);
  490. if (!entry || v == entry.target) return;
  491. v = entry.target;
  492. _fs = new FullScreen(v);
  493. _fp = new FullPage(null, this.switchFP);
  494. events.handle('switchMV');
  495. },
  496. bindEvent() {
  497. $$(this.adsCSS);
  498. by = d.body;
  499. log('bind event\n', v);
  500. events.handle('foundMV');
  501. window.addEventListener('urlchange', info => this.checkMV() ); //TM event: info.url
  502. if (top != self) {
  503. top.postMessage({id: 'gm-h5-init-MVframe'}, '*');
  504. window.addEventListener("message", ev => {
  505. if (!ev.source || !ev.data || !ev.data.id) return;
  506. switch (ev.data.id) {
  507. case 'gm-h5-toggle-fullScreen':
  508. _fs ? _fs.toggle() : clickDualButton(this.btnFS);
  509. break;
  510. }
  511. }, false);
  512. }
  513. if (!this.isLive) {
  514. v.playbackRate = localStorage.mvPlayRate || 1;
  515. v.addEventListener('ratechange', ev => {
  516. localStorage.mvPlayRate = v.playbackRate;
  517. });
  518. }
  519. const fn = ev => {
  520. events.handle('canplay');
  521. v.removeEventListener('canplaythrough', fn);
  522. };
  523. v.addEventListener('canplaythrough', fn);
  524. by.addEventListener('keydown', this.hotKey.bind(this));
  525.  
  526. this.mvShell ? this.shellEvent() : this.setShell();
  527. this.checkUI();
  528. if (this.multipleV) {
  529. new MutationObserver(this.onGrowVList.bind(this)).observe(by, observeOpt);
  530. this.vCount = 0;
  531. this.onGrowVList();
  532. }
  533. },
  534. init() {
  535. this.switchFP = this.multipleV ? this.switchFP.bind(this) : null;
  536. this.vList = d.getElementsByTagName('video');
  537. const fn = e => this.cssMV ? e.matches(this.cssMV) : e.offsetWidth > 9;
  538. this.findMV = find.bind(this.vList, fn);
  539. const timer = intervalQuery(e => {
  540. v = e;
  541. this.bindEvent();
  542. }, this.findMV);
  543.  
  544. hookAttachShadow(async shadowRoot => {
  545. await sleep(600);
  546. events.handle('addShadowRoot', shadowRoot);
  547. if (v) return;
  548. if (v = q('video', shadowRoot)) { // v.getRootNode() == shadowRoot
  549. log('Found MV in ShadowRoot\n', v, shadowRoot);
  550. if (!this.shellCSS) this.mvShell = shadowRoot.host;
  551. clearInterval(timer);
  552. this.bindEvent();
  553.  
  554. this.vList = null;
  555. this.findMV = noopFn;
  556. }
  557. });
  558. }
  559. };
  560.  
  561. let router = {
  562. ted() {
  563. app.fullCSS = 'button[title="Enter Fullscreen"]';
  564. app.playCSS = 'button[title="play video"]';
  565. if (!gmFuncOfCheckMenu('TED强制高清', 'ted_forceHD')) return;
  566. const getHDSource = async () => {
  567. const pn = r1(/^(\/talks\/\w+)/, path);
  568. const resp = await fetch(pn + '/metadata.json');
  569. const data = await resp.json();
  570. return data.talks[0].downloads.nativeDownloads.high;
  571. };
  572. const check = async (rs) => {
  573. if (!v.src || v.src.startsWith('http')) return;
  574. $$(app.vList, e => { e.removeAttribute('src') }); // 取消多余的媒体资源请求
  575. try {
  576. v.src = await getHDSource();
  577. } catch(ex) {
  578. console.error(ex);
  579. }
  580. };
  581. events.on('foundMV', () => {
  582. new MutationObserver(check).observe(v, {
  583. attributes: true,
  584. attributeFilter: ['src']
  585. });
  586. check();
  587. });
  588. },
  589. youtube() {
  590. app.playCSS = 'button.ytp-play-button';
  591. app.fullCSS = 'button.ytp-fullscreen-button';
  592. app.disableKeys = [32,70];
  593. app.isClickOnVideo = true;
  594. events.on('keydown', ev => {
  595. switch (ev.keyCode) {
  596. case 69: //E,替换F键
  597. v.currentTime += 0.03;
  598. v.pause();
  599. return true;
  600. }
  601. });
  602. },
  603. qq() {
  604. app.disableKeys = [32];
  605. app.nextCSS = '.txp_btn_next';
  606. app.webfullCSS = '.txp_btn_fake';
  607. app.fullCSS = '.txp_btn_fullscreen';
  608. events.on('canplay', () => {
  609. // if (!q(app.webfullCSS)) app.shellCSS = '.txp_player';
  610. if (v.muted) {
  611. doClick('.txp_btn_volume');
  612. $('.txp_popup_volume').hide();
  613. }
  614. });
  615. },
  616. youku() {
  617. events.on('keydown', ev => {
  618. switch (ev.keyCode) {
  619. case 32: //空格
  620. ev.preventDefault();
  621. v.paused ? v.play() : v.pause();
  622. return true;
  623. }
  624. });
  625. if (host.startsWith('vku.')) {
  626. events.on('canplay', () => {
  627. app.isLive = !q('.spv_progress');
  628. });
  629. app.fullCSS = '.live_icon_full';
  630. } else {
  631. events.on('foundMV',() => {
  632. by.addEventListener('keyup', e => e.stopPropagation());
  633. });
  634. // localStorage.removeItem('cna');
  635. // delete cookie.cna; //全部清除: cookie(); 写入cookie: cookie.cna = 'xxxxx---xxx';
  636. app.shellCSS = '#ykPlayer';
  637. app.webfullCSS = '.kui-webfullscreen-icon-0';
  638. app.fullCSS = '.kui-fullscreen-icon-0';
  639. app.nextCSS = '.kui-next-icon-0';
  640. }
  641. },
  642. bilibili() {
  643. app.isLive = host.startsWith('live.');
  644. if (app.isLive) return;
  645. const isSquirtle = path.startsWith('/bangumi');
  646. if (!isSquirtle) app.disableKeys = [32];
  647. // app.shellCSS = 'div[aria-label="哔哩哔哩播放器"]'; // .player #bilibili-player
  648. app.nextCSS = isSquirtle ? '.squirtle-video-next' : '.bilibili-player-video-btn-next';
  649. app.webfullCSS = isSquirtle ? '.squirtle-video-pagefullscreen' : '.bilibili-player-video-web-fullscreen';
  650. app.fullCSS = isSquirtle ? '.squirtle-video-fullscreen' : '.bilibili-player-video-btn-fullscreen';
  651. events.on('foundMV', () => {
  652. v.getRootNode().nodeName !== 'HTML' && events.on('keydown', ev => {
  653. let n;
  654. switch (ev.keyCode) {
  655. case 37: n = e.shiftKey ? -20 : -5; //left 快退5秒,shift加速
  656. case 39: n = n || (e.shiftKey ? 20 : 5);
  657. n += v.currentTime;
  658. w.player.seek(n);
  659. return true;
  660. }
  661. });
  662. const s = isSquirtle ? '.squirtle-speed-select-result' : '.bilibili-player-video-btn-speed-name';
  663. v.addEventListener('ratechange', ev => { const e = q(s); if (e) e.textContent = v.playbackRate + 'X' });
  664. });
  665. },
  666. iqiyi() {
  667. app.fullCSS = '.iqp-btn-fullscreen:not(.fake__click)';
  668. app.webfullCSS = '.iqp-btn-webscreen:not(.fake__click)';
  669. app.nextCSS = '.iqp-btn-next';
  670. },
  671. pptv() {
  672. app.fullCSS = '.w-zoom-container > div';
  673. app.webfullCSS = '.w-expand-container > div';
  674. app.nextCSS = '.w-next';
  675. },
  676. mgtv() {
  677. app.fullCSS = 'mango-screen';
  678. app.webfullCSS = 'mango-webscreen > a';
  679. app.nextCSS = 'mango-control-playnext-btn';
  680. },
  681. ixigua() {
  682. app.fullCSS = '.xgplayer-fullscreen';
  683. app.webfullCSS = '.xgplayer-cssfullscreen';
  684. app.nextCSS = '.xgplayer-playNext';
  685. events.on('foundMV', () => {
  686. v.addEventListener('keydown', app.hotKey.bind(app));
  687. });
  688. },
  689. miguvideo() {
  690. app.playCSS = '.play-btn';
  691. app.nextCSS = '.next-btn';
  692. },
  693. weibo() {
  694. app.multipleV = path.startsWith('/u/');
  695. },
  696. acfun() {
  697. app.nextCSS = '.btn-next-part .control-btn';
  698. app.webfullCSS = '.fullscreen-web';
  699. app.fullCSS = '.fullscreen-screen';
  700. },
  701. ['163']() {
  702. app.multipleV = host.startsWith('news.');
  703. GM_addStyle('div.video,video{max-height: 100% !important;}');
  704. return host.split('.').length > 3;
  705. },
  706. sohu() {
  707. app.nextCSS = 'li.on[data-vid]+li a';
  708. app.fullCSS = '.x-fullscreen-btn';
  709. app.webfullCSS = '.x-pagefs-btn';
  710. },
  711. fun() {
  712. app.nextCSS = '.btn-item.btn-next';
  713. },
  714. le() {
  715. GM_addStyle('.gm-fp-body .le_head{display:none!important}');
  716. app.cssMV = '#video video';
  717. app.shellCSS = '#video';
  718. app.nextCSS = '.hv_ico_next';
  719. setInterval(_ => {
  720. if (!v || v.offsetHeight) return;
  721. for (const k of Object.values(v.attributes)) if (v.getAttribute(k.name) == ''){
  722. v.removeAttribute(k.name);
  723. break;
  724. }
  725. }, 600);
  726. },
  727. agemys() {
  728. events.on('keydown', ev => {
  729. switch (ev.keyCode) {
  730. case 78: //N
  731. location.href = location.href.replace(/\d+$/, s => ++s);
  732. return true;
  733. }
  734. });
  735. },
  736. hmtv() {
  737. app.nextCSS = `a[href="${path}"]+a`;
  738. GM_addStyle('.dplayer-loaded{ background-color:orange !important; }');
  739. },
  740. olevod() {
  741. app.fullCSS = 'button[data-plyr=fullscreen]';
  742. GM_addStyle('.plyr__video-wrapper{ height:100%!important; padding-bottom:0!important;}');
  743. }
  744. };
  745.  
  746. if (!router[u]) { //直播站点
  747. router = {
  748. douyu() {
  749. app.adsCSS = 'a[href*="wan.douyu.com"]';
  750. app.isLive = !host.startsWith('v.');
  751. if (app.isLive) {
  752. app.cssMV = '.layout-Player video';
  753. app.webfullCSS = 'div[class|=wfs]';
  754. app.fullCSS = 'div[class|=fs]';
  755. app.playCSS = 'div[class|=play]';
  756. path != '/' && document.addEventListener('DOMContentLoaded', ev => {
  757. $$('.u-specialStateInput', e => {e.checked = true;})
  758. });
  759. } else events.on('addShadowRoot', function(r) {
  760. if (r.host.matches('#demandcontroller-bar')) {
  761. app.shellCSS = 'div[fullscreen].video';
  762. app.btnFP = q('.ControllerBar-PageFull', r);
  763. app.btnFS = q('.ControllerBar-WindowFull', r);
  764. this.undo('addShadowRoot');
  765. }
  766. });
  767. },
  768. yy() {
  769. app.isLive = !path.startsWith('/x/');
  770. if (app.isLive) {
  771. app.fullCSS = '.yc__fullscreen-btn';
  772. app.webfullCSS = '.yc__cinema-mode-btn';
  773. app.playCSS = '.yc__play-btn';
  774. }
  775. },
  776. huya() {
  777. if (firefoxVer && firefoxVer < 57) return true;
  778. app.disableDBLClick = !0;
  779. app.webfullCSS = '.player-fullpage-btn';
  780. app.fullCSS = '.player-fullscreen-btn';
  781. app.playCSS = '#player-btn';
  782. app.adsCSS = '#player-subscribe-wap,#wrap-income';
  783. intervalQuery(doClick, '.login-tips-close');
  784. localStorage['sidebar/ads'] = '{}';
  785. localStorage['sidebar/state'] = 0;
  786. localStorage.TT_ROOM_SHIELD_CFG_0_ = '{"10000":1,"20001":1,"20002":1,"20003":1,"30000":1}';
  787. },
  788. longzhu() {
  789. app.fullCSS = 'a.ya-screen-btn';
  790. },
  791. zhanqi() {
  792. localStorage.lastPlayer = 'h5';
  793. app.fullCSS = '.video-fullscreen';
  794. }
  795. };
  796. if (router[u]) {
  797. app.isLive = app.isLive || !host.startsWith('v.');
  798. (!w.chrome || isEdge) && fakeUA(ua_chrome);
  799. }
  800. }
  801.  
  802. Reflect.defineProperty(navigator, 'plugins', {
  803. get() { return { length: 0 } }
  804. });
  805. GM_registerMenuCommand('脚本功能快捷键表' , alert.bind(w,
  806. `双击:切换(网页)全屏
  807. 鼠标中键:快进5
  808.  
  809. 左右方向键:快退、快进5秒; + shift: 20
  810. 上下方向键:音量调节 ESC:退出(网页)全屏
  811. 空格键:暂停/播放 N:播放下一集
  812. 回车键:切换全屏; + shift: 切换网页全屏
  813. C:加速0.1倍播放 X:减速0.1倍播放 Z:正常速度播放
  814. D:上一帧 F:下一帧(youtube.comE键)`
  815. ));
  816. if (!router[u] || !router[u]()) app.init();
  817. if (!router[u] && !app.isNumURL) app.isNumURL = /[_\W]\d+(\/|\.[a-z]{3,8})?$/.test(path);