视频网HTML5播放小工具

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

As of 2021-06-14. See the latest version.

  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 http://www.miguvideo.com/*
  19. // @include https://www.miguvideo.com/*
  20. // @include https://tv.sohu.com/*
  21. // @include https://film.sohu.com/album/*
  22. // @include https://www.mgtv.com/*
  23. // @version 1.7.8
  24. // @include https://pan.baidu.com/*
  25. // @include https://yun.baidu.com/*
  26. // @include https://*.163.com/*
  27. // @include https://*.icourse163.org/*
  28. // @include https://*.sina.com.cn/*
  29. // @include https://video.sina.cn/*
  30. // @include https://k.sina.cn/*
  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. // @noframes
  43. // @include https://www.yy.com/*
  44. // @include https://www.huya.com/*
  45. // @include https://v.douyu.com/*
  46. // @include https://www.douyu.com/*
  47. // @include http://star.longzhu.com/*
  48. // @include https://star.longzhu.com/*
  49. // @include https://www.zhanqi.tv/*
  50. // @run-at document-start
  51. // @include */play*
  52. // @include *play/*
  53. // @grant unsafeWindow
  54. // @grant GM_addStyle
  55. // @grant GM_registerMenuCommand
  56. // @grant GM_setValue
  57. // @grant GM_getValue
  58. // @namespace https://greasyfork.org/users/7036
  59. // ==/UserScript==
  60.  
  61. 'use strict';
  62. const w = unsafeWindow || window;
  63. const { host, pathname: path } = location;
  64. const d = document, find = [].find;
  65. let v, _fp, _fs, by; // document.body
  66. const observeOpt = {childList : true, subtree : true};
  67. const noopFn = () => {};
  68. const q = (css, p = d) => p.querySelector(css);
  69. const delElem = e => e.remove();
  70. const $$ = function(c, cb = delElem, doc = d) {
  71. if (!c || !c.length) return;
  72. if (typeof c === 'string') c = doc.querySelectorAll(c);
  73. if (!cb) return c;
  74. for (let e of c) if (e && cb(e)=== !1) break;
  75. };
  76. const gmFuncOfCheckMenu = (title, saveName, defaultVal = true) => {
  77. const r = GM_getValue(saveName, defaultVal);
  78. if (r) title = '√ '+ title;
  79. GM_registerMenuCommand(title, () => {
  80. GM_setValue(saveName, !r);
  81. location.reload();
  82. });
  83. return r;
  84. };
  85. const r1 = (regp, s) => regp.test(s) && RegExp.$1;
  86. const log = console.log.bind(console, '%c脚本[%s] 反馈:%s\n%s', 'color:#c3c;font-size:1.5em',
  87. GM_info.script.name, GM_info.script.homepage);
  88. const sleep = ms => new Promise(resolve => { setTimeout(resolve, ms) });
  89. const pevent = (ctx, eName) => new Promise(resolve => ctx.addEventListener(eName, resolve) );
  90. const promisify = (fn) => (...args) => new Promise((resolve, reject) => {
  91. args.push(resolve);
  92. fn.apply(this, args);
  93. });
  94. const hookAttachShadow = () => {
  95. if (w._hasHackAttachShadow_) return;
  96. try {
  97. const _attachShadow = Element.prototype.attachShadow;
  98. Element.prototype.attachShadow = function(opt) {
  99. if (opt) opt.mode = 'open';
  100. const shadowRoot = _attachShadow.call(this, opt);
  101. /* 让shadowRoot里面的元素有机会访问shadowHost */
  102. shadowRoot._shadowHost = this;
  103. // 在document下面添加 addShadowRoot 自定义事件
  104. const shadowEvent = new CustomEvent('addShadowRoot', {
  105. shadowRoot,
  106. detail: {
  107. shadowRoot,
  108. message: 'addShadowRoot',
  109. time: new Date()
  110. },
  111. bubbles: true,
  112. cancelable: true
  113. });
  114. d.dispatchEvent(shadowEvent);
  115.  
  116. return shadowRoot
  117. };
  118. w._hasHackAttachShadow_ = true;
  119. } catch (e) {
  120. console.error('hackAttachShadow error by h5player plug-in', e);
  121. }
  122. };
  123. const getStyle = (o, s) => {
  124. if (o.style[s]) return o.style[s];
  125. if (getComputedStyle) {
  126. const x = getComputedStyle(o, '');
  127. s = s.replace(/([A-Z])/g,'-$1').toLowerCase();
  128. return x && x.getPropertyValue(s);
  129. }
  130. };
  131. const doClick = e => {
  132. if (typeof e === 'string') e = q(e);
  133. if (e) { e.click ? e.click() : e.dispatchEvent(new MouseEvent('click')) };
  134. };
  135. const clickDualButton = btn => { // 2合1 按钮
  136. !btn.nextSibling || getStyle(btn, 'display') !== 'none' ? doClick(btn) : doClick(btn.nextSibling);
  137. };
  138. const intervalQuery = (cb, condition, stop = true) => {
  139. const fn = typeof condition === 'string' ? q.bind(null, condition) : condition;
  140. const t = setInterval(() => {
  141. const r = fn();
  142. if (r) {
  143. stop && clearInterval(t);
  144. cb(r);
  145. }
  146. }, 300);
  147. };
  148. const goNextMV = () => {
  149. const s = location.pathname;
  150. const m = s.match(/(\d+)(\D*)$/);
  151. const d = +m[1] + 1;
  152. location.assign(s.slice(0, m.index) + d + m[2]);
  153. };
  154. const firefoxVer = r1(/Firefox\/(\d+)/, navigator.userAgent);
  155. const isEdge = / Edge?\//.test(navigator.userAgent);
  156. const fakeUA = ua => Object.defineProperty(navigator, 'userAgent', {
  157. value: ua,
  158. writable: false,
  159. configurable: false,
  160. enumerable: true
  161. });
  162. const getMainDomain = host => {
  163. const a = host.split('.');
  164. let i = a.length - 2;
  165. if (/^(com?|cc|tv|net|org|gov|edu)$/.test(a[i])) i--;
  166. return a[i];
  167. };
  168. 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';
  169.  
  170. class FullScreen {
  171. constructor(e) {
  172. let fn = d.exitFullscreen || d.webkitExitFullscreen || d.mozCancelFullScreen || d.msExitFullscreen || noopFn;
  173. this.exit = fn.bind(d);
  174. fn = e.requestFullscreen || e.webkitRequestFullScreen || e.mozRequestFullScreen || e.msRequestFullScreen || noopFn;
  175. this.enter = fn.bind(e);
  176. }
  177.  
  178. static isFull() {
  179. return !!(d.fullscreen || d.webkitIsFullScreen || d.mozFullScreen ||
  180. d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement);
  181. }
  182.  
  183. toggle() {
  184. FullScreen.isFull() ? this.exit() : this.enter();
  185. }
  186. }
  187.  
  188. //万能网页全屏, 参考了:https://github.com/gooyie/ykh5p
  189. class FullPage {
  190. constructor(video, onSwitch) {
  191. this._video = video;
  192. this._isFull = !1;
  193. this._onSwitch = onSwitch;
  194. this._checkContainer();
  195. GM_addStyle(
  196. `.gm-fp-body .gm-fp-zTop {
  197. position: relative !important;
  198. z-index: 2147483647 !important;
  199. }
  200. .gm-fp-wrapper, .gm-fp-body{ overflow:hidden !important; }
  201. .gm-fp-wrapper .gm-fp-innerBox {
  202. width: 100% !important;
  203. height: 100% !important;
  204. }
  205. .gm-fp-wrapper {
  206. display: block !important;
  207. position: fixed !important;
  208. width: 100% !important;
  209. height: 100% !important;
  210. top: 0 !important;
  211. left: 0 !important;
  212. background: #000 !important;
  213. z-index: 2147483647 !important;
  214. }`
  215. );
  216. }
  217.  
  218. static getPlayerContainer(video, isAddClass) {
  219. let e = video, p = e.parentNode;
  220. const { clientWidth: wid, clientHeight: h } = e;
  221. do {
  222. isAddClass && e.classList.add('gm-fp-innerBox');
  223. e = p;
  224. p = e.parentNode;
  225. } while (e.nodeName == 'DIV' && p.clientWidth-wid < 5 && p.clientHeight-h < 5);
  226. //e 为返回值,在此之后不能变了
  227. while (p !== by) {
  228. isAddClass && p.classList.add('gm-fp-zTop');
  229. p = p.parentNode;
  230. }
  231. return e;
  232. }
  233.  
  234. _checkContainer() {
  235. this._container = this._container || FullPage.getPlayerContainer(this._video, true);
  236. }
  237.  
  238. get container() {
  239. this._checkContainer();
  240. return this._container;
  241. }
  242.  
  243. static isFull(e) {
  244. return w.innerWidth - e.clientWidth < 5 && w.innerHeight - e.clientHeight < 5;
  245. }
  246.  
  247. toggle() {
  248. const cb = this._onSwitch;
  249. if (!this._isFull && cb) cb(true);
  250. by.classList.toggle('gm-fp-body');
  251. this.container.classList.toggle('gm-fp-wrapper');
  252. this._isFull = !this._isFull;
  253. if (!this._isFull && cb) setTimeout(cb, 199, !1);
  254. }
  255. }
  256.  
  257. const u = getMainDomain(host);
  258. const events = {
  259. on(name, fn) {
  260. this[name] = fn;
  261. }
  262. };
  263. const app = {
  264. isLive: !1,
  265. disableDBLClick: !1,
  266. multipleV: !1, //多视频页面
  267. isNumURL: !1, //网址数字分集
  268. disableKeys: [],
  269. setShell() {
  270. const fn = ev => {
  271. ev.stopPropagation();
  272. ev.preventDefault();
  273. _fp ? _fp.toggle() : clickDualButton(this.btnFP);
  274. };
  275. const e = this.getDPlayer() || this.getVjsPlayer() ||
  276. (this.shellCSS && q(this.shellCSS)) || FullPage.getPlayerContainer(v, !1);
  277. if (e && this.mvShell != e) {
  278. !this.disableDBLClick && this.mvShell && this.mvShell.removeEventListener('dblclick',fn);
  279. this.mvShell = e;
  280. e.addEventListener('mousedown', ev => {
  281. this.checkMV();
  282. if (!this.isLive && 1==ev.button) {
  283. ev.stopPropagation();
  284. v.currentTime += 5;
  285. }
  286. });
  287. !this.disableDBLClick && e.addEventListener('dblclick',fn);
  288. }
  289. },
  290. checkMV() {
  291. if (!this.vList) return;
  292. const e = this.findMV();
  293. if (e != v) {
  294. v = e;
  295. this.btnPlay = this.btnNext = this.btnFP = this.btnFS = _fs = _fp = null;
  296. if (!this.isLive) {
  297. v.playbackRate = localStorage.mvPlayRate || 1;
  298. v.addEventListener('ratechange', ev => {
  299. localStorage.mvPlayRate = v.playbackRate;
  300. });
  301. }
  302. this.setShell();
  303. }
  304. this.checkUI();
  305. return v;
  306. },
  307. getDPlayer() {
  308. const e = v.closest('.dplayer');
  309. if (e) {
  310. this.btnFP = q('.dplayer-full-in-icon > span', e);
  311. this.btnFS = q('.dplayer-full-icon', e);
  312. e.closest('body > *').classList.add('gm-dp-zTop');
  313. }
  314. return e;
  315. },
  316. getVjsPlayer() {
  317. const e = v.closest('.video-js');
  318. if (e) {
  319. this.btnFS = q('.vjs-control-bar > button.vjs-button:nth-last-of-type(1)');
  320. this.webfullCSS = '.vjs-control-bar > button.vjs-button[title$="全屏"]:nth-last-of-type(2)';
  321. }
  322. return e;
  323. },
  324. hotKey(e) {
  325. const t = e.target;
  326. if (e.ctrlKey || e.altKey || t.contentEditable=='true' ||
  327. /INPUT|TEXTAREA|SELECT/.test(t.nodeName)) return;
  328. if (e.shiftKey && ![13,37,39].includes(e.keyCode)) return;
  329. if (this.disableKeys.includes(e.keyCode)) return;
  330. if (this.isLive && [37,39,78,88,67,90].includes(e.keyCode)) return;
  331. if (!this.checkMV()) return;
  332. if (events.keydown && events.keydown(e)) return;
  333. if (!e.shiftKey && this.mvShell && this.mvShell.contains(t) && [32,37,39].includes(e.keyCode)) return;
  334. let n;
  335. switch (e.keyCode) {
  336. case 32: //space
  337. if (this.btnPlay) clickDualButton(this.btnPlay);
  338. else v.paused ? v.play() : v.pause();
  339. e.preventDefault();
  340. break;
  341. case 37: n = e.shiftKey ? -20 : -5; //left 快退5秒,shift加速
  342. //right 快进5秒,shift加速
  343. case 39: n = n || (e.shiftKey ? 20 : 5);
  344. v.currentTime += n;
  345. break;
  346. case 78: // N 下一首
  347. if (this.btnNext) doClick(this.btnNext);
  348. else if (this.isNumURL) goNextMV();
  349. break;
  350. case 38: n = 0.1; //加音量
  351. case 40: n = n || -0.1; //降音量
  352. n += v.volume;
  353. if (0 <= n && n <= 1) v.volume = +n.toFixed(1);
  354. e.preventDefault();
  355. break;
  356. case 13: //回车键。 全屏
  357. if (e.shiftKey) {
  358. _fp ? _fp.toggle() : clickDualButton(this.btnFP);
  359. } else {
  360. _fs ? _fs.toggle() : clickDualButton(this.btnFS);
  361. }
  362. break;
  363. case 27: //esc
  364. if (FullScreen.isFull()) {
  365. _fs ? _fs.exit() : clickDualButton(this.btnFS);
  366. } else if (FullPage.isFull(v)) {
  367. _fp ? _fp.toggle() : clickDualButton(this.btnFP);
  368. }
  369. break;
  370. case 67: n = 0.1; //按键C:加速播放 +0.1
  371. case 88: n = n || -0.1; //按键X:减速播放 -0.1
  372. n += v.playbackRate;
  373. if (0 < n && n <= 16) v.playbackRate = +n.toFixed(2);
  374. break;
  375. case 90: //按键Z:正常速度播放
  376. v.playbackRate = 1;
  377. break;
  378. case 70: n = 0.03; //按键F:下一帧
  379. case 68: n = n || -0.03; //按键D:上一帧
  380. v.currentTime += n;
  381. v.pause();
  382. break;
  383. default: return;
  384. }
  385. e.stopPropagation();
  386. },
  387. checkUI() {
  388. if (this.webfullCSS && !this.btnFP) this.btnFP = q(this.webfullCSS);
  389. if (this.btnFP) _fp = null;
  390. else if (!_fp) _fp = new FullPage(v, this.switchFP);
  391.  
  392. if (this.fullCSS && !this.btnFS) this.btnFS = q(this.fullCSS);
  393. if (this.btnFS) _fs = null;
  394. else if (!_fs) _fs = new FullScreen(v);
  395.  
  396. if (this.nextCSS && !this.btnNext) this.btnNext = q(this.nextCSS);
  397. if (this.playCSS && !this.btnPlay) this.btnPlay = q(this.playCSS);
  398. },
  399. switchFP(toFull) {
  400. if (toFull) {
  401. for (let e of this.vSet) this.viewObserver.unobserve(e);
  402. } else {
  403. for (let e of this.vList) this.viewObserver.observe(e);
  404. }
  405. },
  406. onGrowVList() {
  407. if (this.vList.length == this.vCount) return;
  408. if (this.viewObserver) {
  409. for (let e of this.vList) {
  410. if (!this.vSet.has(e)) this.viewObserver.observe(e);
  411. }
  412. } else {
  413. const config = {
  414. rootMargin: '0px',
  415. threshold: 0.9
  416. };
  417. this.viewObserver = new IntersectionObserver(this.onIntersection.bind(this), config);
  418. for (let e of this.vList) this.viewObserver.observe(e);
  419. }
  420. this.vSet = new Set(this.vList);
  421. this.vCount = this.vList.length;
  422. },
  423. onIntersection(entries) {
  424. if (this.vList.length < 2) return;
  425. const entry = find.call(entries, k => k.isIntersecting);
  426. if (!entry || v == entry.target) return;
  427. v = entry.target;
  428. _fs = new FullScreen(v);
  429. _fp = new FullPage(v, this.switchFP);
  430. events.switchMV && events.switchMV();
  431. },
  432. bindEvent() {
  433. by = d.body;
  434. log('bind event\n', v);
  435. events.foundMV && events.foundMV();
  436. if (!this.isLive) {
  437. v.playbackRate = localStorage.mvPlayRate || 1;
  438. v.addEventListener('ratechange', ev => {
  439. localStorage.mvPlayRate = v.playbackRate;
  440. });
  441. }
  442. const fn = ev => {
  443. events.canplay && events.canplay();
  444. v.removeEventListener('canplaythrough', fn);
  445. };
  446. if (v.readyState > 3) fn();
  447. else v.addEventListener('canplaythrough', fn);
  448. by.addEventListener('keydown', this.hotKey);
  449.  
  450. this.setShell();
  451. this.checkUI();
  452. if (this.multipleV) {
  453. new MutationObserver(this.onGrowVList.bind(this)).observe(by, observeOpt);
  454. this.vCount = 0;
  455. this.onGrowVList();
  456. }
  457. },
  458. init() {
  459. this.hotKey = this.hotKey.bind(this);
  460. this.switchFP = this.multipleV ? this.switchFP.bind(this) : null;
  461. this.vList = d.getElementsByTagName('video');
  462. const fn = e => this.cssMV ? e.matches(this.cssMV) : e.offsetWidth > 9;
  463. this.findMV = find.bind(this.vList, fn);
  464. intervalQuery(e => {
  465. v = e;
  466. $$(this.adsCSS);
  467. this.bindEvent();
  468. }, this.findMV);
  469. }
  470. };
  471.  
  472. let router = {
  473. ted() {
  474. app.fullCSS = 'button[title="Enter Fullscreen"]';
  475. app.playCSS = 'button[title="play video"]';
  476. if (!gmFuncOfCheckMenu('TED强制高清', 'ted_forceHD')) return;
  477. const getHDSource = async () => {
  478. const pn = r1(/^(\/talks\/\w+)/, path);
  479. const resp = await fetch(pn + '/metadata.json');
  480. const data = await resp.json();
  481. return data.talks[0].downloads.nativeDownloads.high;
  482. };
  483. const check = async (rs) => {
  484. if (!v.src || v.src.startsWith('http')) return;
  485. $$(app.vList, e => { e.removeAttribute('src') }); // 取消多余的媒体资源请求
  486. try {
  487. v.src = await getHDSource();
  488. } catch(ex) {
  489. console.error(ex);
  490. }
  491. };
  492. events.on('foundMV', () => {
  493. new MutationObserver(check).observe(v, {
  494. attributes: true,
  495. attributeFilter: ['src']
  496. });
  497. check();
  498. });
  499. },
  500. youtube() {
  501. app.playCSS = 'button.ytp-play-button';
  502. app.fullCSS = 'button.ytp-fullscreen-button';
  503. // app.shellCSS = '#ytd-player';
  504. app.disableKeys = [32,70];
  505. events.on('keydown', ev => {
  506. switch (ev.keyCode) {
  507. case 69: //E,替换F键
  508. v.currentTime += 0.03;
  509. v.pause();
  510. return true;
  511. }
  512. });
  513. },
  514. qq() {
  515. app.disableKeys = [32];
  516. app.nextCSS = '.txp_btn_next';
  517. app.webfullCSS = '.txp_btn_fake';
  518. app.fullCSS = '.txp_btn_fullscreen';
  519. },
  520. youku() {
  521. events.on('keydown', ev => {
  522. switch (ev.keyCode) {
  523. case 32: //空格
  524. ev.preventDefault();
  525. v.paused ? v.play() : v.pause();
  526. return true;
  527. }
  528. });
  529. if (host.startsWith('vku.')) {
  530. events.on('canplay', () => {
  531. app.isLive = !q('.spv_progress');
  532. });
  533. app.fullCSS = '.live_icon_full';
  534. } else {
  535. events.on('foundMV',() => {
  536. by.addEventListener('keyup', e => e.stopPropagation());
  537. });
  538. app.shellCSS = '#ykPlayer';
  539. app.webfullCSS = '.control-webfullscreen-icon';
  540. app.fullCSS = '.control-fullscreen-icon';
  541. app.nextCSS = 'span.icon-next';
  542. }
  543. },
  544. bilibili() {
  545. app.isLive = host.startsWith('live.');
  546. if (app.isLive) return;
  547. app.nextCSS = '.bilibili-player-video-btn-next';
  548. app.webfullCSS = '.bilibili-player-video-web-fullscreen';
  549. app.fullCSS = '.bilibili-player-video-btn-fullscreen';
  550. const danmu = gmFuncOfCheckMenu('弹幕', 'bili_danmu');
  551. const danmuCSS = '.bilibili-player-video-danmaku-switch input';
  552. events.on('canplay', () => {
  553. intervalQuery(e => {if (e.checked != danmu) e.click()}, danmuCSS);
  554. });
  555. const fn = ev => {
  556. events.canplay();
  557. v = null;
  558. };
  559.  
  560. const fName= path.startsWith('/bangumi/') ? 'replaceState' : 'pushState';
  561. const rawFn = history[fName]; //二方法不触发onpopstate事件
  562. history[fName] = function(...args) {
  563. rawFn.apply(this, args);
  564. fn();
  565. };
  566. w.addEventListener("pageshow", fn);
  567. w.addEventListener("popstate", fn);
  568. },
  569. pptv() {
  570. app.fullCSS = '.w-zoom-container > div';
  571. app.webfullCSS = '.w-expand-container > div';
  572. app.nextCSS = '.w-next';
  573. },
  574. mgtv() {
  575. app.fullCSS = 'mango-screen';
  576. app.webfullCSS = 'mango-webscreen > a';
  577. app.nextCSS = 'mango-control-playnext-btn';
  578. },
  579. ixigua() {
  580. app.fullCSS = '.xgplayer-fullscreen';
  581. app.webfullCSS = '.xgplayer-cssfullscreen';
  582. app.nextCSS = '.xgplayer-playNext';
  583. events.on('foundMV', () => {
  584. v.addEventListener('keydown', app.hotKey);
  585. });
  586. },
  587. miguvideo() {
  588. app.playCSS = '.play-btn';
  589. app.fullCSS = '.zoom-btn';
  590. app.nextCSS = '.next-btn';
  591. },
  592. weibo() {
  593. app.multipleV = path.startsWith('/u/');
  594. },
  595. baidu() {
  596. hookAttachShadow();
  597. const h = async ev => {
  598. const r = ev.detail.shadowRoot;
  599. await sleep(999);
  600. v = q('video', r);
  601. if (v) {
  602. d.removeEventListener('addShadowRoot', h);
  603. log('addShadowRoot, found MV\n',v,r);
  604. app.bindEvent();
  605. }
  606. };
  607. d.addEventListener('addShadowRoot', h);
  608. app.init = app.checkMV = ()=>true;
  609. app.hotKey = app.hotKey.bind(app);
  610. },
  611. acfun() {
  612. app.nextCSS = '.btn-next-part .control-btn';
  613. app.webfullCSS = '.fullscreen-web';
  614. app.fullCSS = '.fullscreen-screen';
  615. },
  616. ['163']() {
  617. app.multipleV = host.startsWith('news.');
  618. GM_addStyle('div.video,video{max-height: 100% !important;}');
  619. return host.split('.').length > 3;
  620. },
  621. sohu() {
  622. app.nextCSS = 'li.on[data-vid]+li a';
  623. app.fullCSS = '.x-fullscreen-btn';
  624. app.webfullCSS = '.x-pagefs-btn';
  625. },
  626. fun() {
  627. app.nextCSS = '.btn-item.btn-next';
  628. },
  629. le() {
  630. events.canplay = events.foundMV = ev => {
  631. if (v.offsetWidth>9) return;
  632. const ss = v.attributes;
  633. for (let i=ss.length - 1;i>=0;i--) if (!v.getAttribute(ss[i].name)){
  634. v.removeAttribute(ss[i].name);
  635. break;
  636. }
  637. };
  638. const rawFn = history.pushState;
  639. history.pushState = function(...args) {
  640. rawFn.apply(this, args);
  641. setTimeout(events.foundMV, 1e3);
  642. };
  643. },
  644. agefans() {
  645. events.on('keydown', ev => {
  646. switch (ev.keyCode) {
  647. case 78: //N
  648. location.href = location.href.replace(/\d+$/, s => ++s);
  649. return true;
  650. }
  651. });
  652. },
  653. dililitv() {
  654. app.nextCSS = `a[href="${path}"]+a`;
  655. GM_addStyle('.dplayer-loaded{ background-color:orange !important; }');
  656. }
  657. };
  658. // www.dililitv.cc,www.hmtv.me,imeiju.io,www.lzvod.net
  659. router.lzvod = router.hmtv = router.imeiju = router.dililitv;
  660.  
  661. if (!router[u]) { //直播站点
  662. router = {
  663. douyu() {
  664. app.adsCSS = 'a[href*="wan.douyu.com"]';
  665. app.isLive = !host.startsWith('v.');
  666. if (!app.isLive) app.init = function() {
  667. app.checkMV = ()=>true;
  668. app.hotKey = app.hotKey.bind(app);
  669. hookAttachShadow();
  670. d.addEventListener('addShadowRoot', async ev => {
  671. const r = ev.detail.shadowRoot;
  672. if (r.host.matches('demand-video')) {
  673. $$(app.adsCSS);
  674. await sleep(555);
  675. v = q('video', r);
  676. log('addShadowRoot',r,v);
  677. app.setShell = function() {
  678. this.mvShell = v.closest('#playerBox');
  679. this.mvShell.addEventListener('dblclick', ev => app.btnFP.click() );
  680. this.mvShell.addEventListener('mousedown', ev => {
  681. if (1==ev.button) v.currentTime += 5;
  682. });
  683. };
  684. }
  685. else if (r.host.matches('#demandcontroller-bar')) {
  686. await sleep(900);
  687. app.btnFP = q('.ControllerBar-PageFull', r);
  688. app.btnFS = q('.ControllerBar-WindowFull', r);
  689. app.bindEvent();
  690. }
  691. });
  692. }; else {
  693. app.cssMV = '.layout-Player video';
  694. app.webfullCSS = 'div[class|=wfs]';
  695. app.fullCSS = 'div[class|=fs]';
  696. app.playCSS = 'div[class|=play]';
  697. path != '/' && events.on('foundMV', () => {
  698. q('#js-player-aside-state').checked = true;
  699. });
  700. }
  701. },
  702. yy() {
  703. app.isLive = !path.startsWith('/x/');
  704. if (app.isLive) {
  705. app.fullCSS = '.yc__fullscreen-btn';
  706. app.webfullCSS = '.yc__cinema-mode-btn';
  707. app.playCSS = '.yc__play-btn';
  708. }
  709. },
  710. huya() {
  711. if (firefoxVer && firefoxVer < 57) return true;
  712. app.disableDBLClick = !0;
  713. app.webfullCSS = '.player-fullpage-btn';
  714. app.fullCSS = '.player-fullscreen-btn';
  715. app.playCSS = '#player-btn';
  716. app.adsCSS = '#player-subscribe-wap,#wrap-income';
  717. intervalQuery(doClick, '.login-tips-close');
  718. localStorage['sidebar/ads'] = '{}';
  719. localStorage['sidebar/state'] = 0;
  720. localStorage.TT_ROOM_SHIELD_CFG_0_ = '{"10000":1,"20001":1,"20002":1,"20003":1,"30000":1}';
  721. },
  722. longzhu() {
  723. app.fullCSS = 'a.ya-screen-btn';
  724. },
  725. zhanqi() {
  726. localStorage.lastPlayer = 'h5';
  727. app.fullCSS = '.video-fullscreen';
  728. }
  729. };
  730. if (router[u]) {
  731. app.isLive = app.isLive || !host.startsWith('v.');
  732. (!w.chrome || isEdge) && fakeUA(ua_chrome);
  733. }
  734. }
  735.  
  736. Reflect.defineProperty(navigator, 'plugins', {
  737. get() { return { length: 0 } }
  738. });
  739. GM_registerMenuCommand('脚本功能快捷键表' , alert.bind(w,
  740. `双击:切换网页全屏(部分站点有效)
  741. 鼠标中键:快进5
  742.  
  743. 左右方向键:快退、快进5秒; +shift: 20
  744. 上下方向键:音量调节 ESC:退出(网页)全屏
  745. 空格键:暂停/播放 N:播放下一集
  746. 回车键:切换全屏; +shift: 切换网页全屏
  747. C:加速0.1倍播放 X:减速0.1倍播放 Z:正常速度播放
  748. D:上一帧 F:下一帧(youtube.comE键)`
  749. ));
  750. if (!router[u] || !router[u]()) app.init();
  751. if (!router[u] && !app.isNumURL) app.isNumURL = /[-_\W]\d+(\/|\.[a-z]{3,8})?$/.test(path);