视频网站自动网页全屏|倍速播放

支持哔哩哔哩、B站直播、腾讯视频、优酷视频、爱奇艺、芒果TV、搜狐视频、AcFun弹幕网自动网页全屏;快捷键切换:全屏(F)、网页全屏(P)、下一个视频(N)、弹幕开关(D);支持任意视频倍速播放,提示记忆倍速;B站播放完自动退出网页全屏和取消连播。

Installer dette scriptet?
Skaperens foreslåtte skript

Du vil kanskje også like 关闭影视动漫网站的公告弹窗.

Installer dette scriptet
  1. // ==UserScript==
  2. // @name 视频网站自动网页全屏|倍速播放
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.5.1
  5. // @author Feny
  6. // @description 支持哔哩哔哩、B站直播、腾讯视频、优酷视频、爱奇艺、芒果TV、搜狐视频、AcFun弹幕网自动网页全屏;快捷键切换:全屏(F)、网页全屏(P)、下一个视频(N)、弹幕开关(D);支持任意视频倍速播放,提示记忆倍速;B站播放完自动退出网页全屏和取消连播。
  7. // @license GPL-3.0-only
  8. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAqdJREFUWEftl91LFFEYxp/3jB9ESZjtSl51F1RUSgRCF/kHlF1IhiFhF65dqEQkBUErdJMStBukGwQre2NZUiCRqUiURkW65mIfqGUFsW6Ii0jY7p4Tc3Rqd5zaGVldAudynve8z28e3jMzh5Dmi1R/V0vQyRRWxgWG6x22SrcnOAhQcQIbwVtXba8y1EANSpS1xzJin5c/Dz+jRDPvGWoErwRw35zuh8ChpcXXFjbwi9k/WADA9viGgovGnxtFs6EmcApMvCdBA3oIIirl4N8NNQngmRYJiwTOE7EHHLERAmXFawQ6AdCQkRbjsZIMUvIFoV0HMSsEDjCgSK8tJqAHAEDAMWLKLOexx8tiVVDEhLLVQAtzRPcwKOUANSWCw1/rsBe6PcFz8dpfAdTFgtF+EmIvBG7pID7mZNl2zkVCFQbahzqHfYerddpNhFpdsnfqauzl8ZoEuO4JXdIKOefynnZlimxXhBbqjTZL/el8pzrAVjTGmKh12Bq1ddJs974abQDXfFMuAhQ6EodwDTHWAf6/BAoK8nD0cDEKtuVhyD+OzvvLXnyWJshyApedJ1F65M9n4tlAAF5fL168fGfJWCu2DDA61GpodLvjCdp8vfjyNWQJJGUAquvMzBzafD0yEc65KZCUAmiOo4FPEqS753VSiFUB0FxbPF244en6J8SqAoTD8zhYcjZ9AP6RCVRWNacHYPD5GJqudmBi8tvaAkxNBeUuuNv5NOkAqgUpm4FIJCrfA+r0z4bnTZmvCKCv+wrsts0JBg8fvZLGY28NfoqToFhOoOJ4CS40lMu2I28mpXFP37DpJ9YXWgZQG+Tm5mBL7qakA2aGakUAZhqbrVkH0BLoB34fzcyml5K6pd/yaicRlQlgV0q6mmwitMOpyfpVKfsFya4w73cz9xQAAAAASUVORK5CYII=
  9. // @homepage https://github.com/xFeny/monkey-web-fullscreen
  10. // @match *://tv.sohu.com/v/*
  11. // @match *://www.mgtv.com/b/*
  12. // @match *://www.acfun.cn/v/*
  13. // @match *://www.iqiyi.com/v_*
  14. // @match *://v.qq.com/x/page/*
  15. // @match *://v.qq.com/x/cover/*
  16. // @match *://haokan.baidu.com/v*
  17. // @match *://live.bilibili.com/*
  18. // @match *://v.youku.com/v_show/*
  19. // @match *://live.acfun.cn/live/*
  20. // @match *://www.acfun.cn/bangumi/*
  21. // @match *://www.bilibili.com/list/*
  22. // @match *://www.bilibili.com/video/*
  23. // @match *://v.qq.com/live/p/newtopic/*
  24. // @match *://www.bilibili.com/festival/*
  25. // @match *://www.bilibili.com/cheese/play/*
  26. // @match *://www.bilibili.com/bangumi/play/*
  27. // @match *://*bimiacg*.net/bangumi/*/play/*
  28. // @match *://*bimiacg*.net/static/danmu/play*
  29. // @match *://www.ezdmw.site/Index/video/*
  30. // @match *://player.ezdmw.com/danmuku/*
  31. // @match *://v.douyu.com/show/*
  32. // @grant GM_addStyle
  33. // @grant GM_info
  34. // @grant unsafeWindow
  35. // @note *://*/*
  36. // ==/UserScript==
  37.  
  38. (t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const o=document.createElement("style");o.textContent=t,document.head.append(o)})(' @charset "UTF-8";.showToast{color:#fff!important;font-size:14px!important;padding:5px 15px!important;border-radius:5px!important;position:absolute!important;z-index:2147483647!important;transition:opacity .5s ease-in;background:#000000bf!important}.showToast .playbackRate{margin:0 3px!important;color:#ff6101!important}#bilibili-player .bpx-player-toast-wrap,#bilibili-player .bpx-player-cmd-dm-wrap,#bilibili-player .bpx-player-dialog-wrap,.live-room-app #sidebar-vm,.live-room-app #prehold-nav-vm,.live-room-app #shop-popover-vm,.login-tip{display:none!important} ');
  39.  
  40. (function () {
  41. 'use strict';
  42.  
  43. const positions = Object.freeze({
  44. bottomLeft: "bottom: 20%; left: 10px;",
  45. center: "top: 50%; left: 50%; transform: translate(-50%, -50%);"
  46. });
  47. const ONE_SECOND = 1e3;
  48. const constants = Object.freeze({
  49. EMPTY: "",
  50. ASTERISK: "*",
  51. INC_SYMBOL: "+",
  52. DEC_SYMBOL: "-",
  53. MUL_SYMBOL: "×",
  54. DIV_SYMBOL: "÷",
  55. DEF_PLAY_RATE: 1,
  56. MAX_PLAY_RATE: 16,
  57. ONE_SEC: ONE_SECOND,
  58. PLAY_RATE_STEP: 0.25,
  59. SHOW_TOAST_TIME: ONE_SECOND * 5,
  60. SHOW_TOAST_POSITION: positions.bottomLeft,
  61. MSG_SOURCE: "FENY_SCRIPTS_AUTO_WEB_FULLSCREEN",
  62. CACHED_PLAY_RATE_KEY: "FENY_SCRIPTS_V_PLAYBACK_RATE",
  63. QQ_VID_REG: /v.qq.com\/x/,
  64. ACFUN_VID_REG: /acfun.cn\/v/,
  65. IQIYI_VID_REG: /iqiyi.com\/v_*/,
  66. BILI_VID_REG: /bilibili.com\/video/
  67. });
  68. const selectorConfig = {
  69. "live.bilibili.com": { webfull: "#businessContainerElement" },
  70. "live.acfun.cn": { full: ".fullscreen-screen", webfull: ".fullscreen-web", danmaku: ".danmaku-enabled" },
  71. "tv.sohu.com": { full: ".x-fullscreen-btn", webfull: ".x-pagefs-btn", danmaku: ".tm-tmbtn", next: ".x-next-btn" },
  72. "haokan.baidu.com": { full: ".art-icon-fullscreen", webfull: ".art-control-fullscreenWeb", next: ".art-control-next" },
  73. "www.iqiyi.com": { full: ".iqp-btn-fullscreen", webfull: ".iqp-btn-webscreen", danmaku: "#barrage_switch", next: ".iqp-btn-next" },
  74. "www.mgtv.com": { full: ".fullscreenBtn i", webfull: ".webfullscreenBtn i", danmaku: "div[class*='danmuSwitch']", next: ".icon-next" },
  75. "v.qq.com": { full: ".txp_btn_fullscreen", webfull: "div[aria-label='网页全屏']", danmaku: ".barrage-switch", next: ".txp_btn_next_u" },
  76. "v.pptv.com": { full: ".w-zoom-container > div", webfull: ".w-expand-container > div", danmaku: ".w-barrage", next: ".w-next-container" },
  77. "www.acfun.cn": { full: ".fullscreen-screen", webfull: ".fullscreen-web", danmaku: ".danmaku-enabled", next: ".btn-next-part .control-btn" },
  78. "www.bilibili.com": { full: "div[aria-label='全屏']", webfull: "div[aria-label='网页全屏']", danmaku: ".bui-area", next: ".bpx-player-ctrl-next" },
  79. "v.youku.com": { full: "#fullscreen-icon", webfull: "#webfullscreen-icon", danmaku: "div[class*='switch-img_12hDa turn-']", next: ".kui-next-icon-0" }
  80. };
  81. const { DEF_PLAY_RATE: DEF_PLAY_RATE$1, BILI_VID_REG: BILI_VID_REG$1, ACFUN_VID_REG } = constants;
  82. const VideoListenerHandler = {
  83. loadedmetadata() {
  84. this.volume = 1;
  85. this.isToast = false;
  86. },
  87. loadeddata() {
  88. this.isToast = false;
  89. },
  90. timeupdate() {
  91. if (this.duration === NaN) return;
  92. const cachePlayRate = App.getCachePlayRate();
  93. if (!cachePlayRate || DEF_PLAY_RATE$1 === cachePlayRate) return;
  94. if (cachePlayRate === this.playbackRate) return;
  95. const reuslt = App.setPlayRate(cachePlayRate);
  96. if (!reuslt) return;
  97. if (this.isToast) return;
  98. App.showRateTip();
  99. this.isToast = true;
  100. },
  101. play() {
  102. this.isEnded = false;
  103. App.webFullScreen(this);
  104. },
  105. ended() {
  106. this.isEnded = true;
  107. this.isToast = false;
  108. const href = location.href;
  109. if (!BILI_VID_REG$1.test(href) && !ACFUN_VID_REG.test(href)) return;
  110. const pod = App.query(".video-pod");
  111. const pods = App.querys('.video-pod .switch-btn:not(.on), .video-pod__item:last-of-type[data-scrolled="true"]');
  112. if (!pod || pods.length > 0) App.exitWebFullScreen();
  113. }
  114. };
  115. const douyu = {
  116. getRoot() {
  117. return document.querySelector("demand-video").shadowRoot;
  118. },
  119. getControllerBar() {
  120. return this.getRoot().querySelector("#demandcontroller-bar").shadowRoot;
  121. },
  122. getVideo() {
  123. return this.getRoot().querySelector("video");
  124. },
  125. getWebfullIcon() {
  126. return this.getControllerBar().querySelector(".ControllerBar-PageFull-Icon");
  127. },
  128. getFullIcon() {
  129. return this.getControllerBar().querySelector(".ControllerBar-WindowFull-Icon");
  130. },
  131. getDanmakuIcon() {
  132. return document.querySelector("demand-player-extension").shadowRoot.querySelector(".BarrageSwitch-icon");
  133. },
  134. play() {
  135. this.getControllerBar().querySelector(".ControllerBarPlay").click();
  136. },
  137. pause() {
  138. this.getControllerBar().querySelector(".ControllerBarStop").click();
  139. },
  140. addStyle() {
  141. this.getRoot().querySelectorAll(".style").forEach((el) => el.remove());
  142. const style = document.createElement("style");
  143. style.setAttribute("class", "style");
  144. style.textContent = `
  145. .showToast {
  146. color: #fff !important;
  147. font-size: 14px !important;
  148. padding: 5px 15px !important;
  149. border-radius: 5px !important;
  150. position: absolute !important;
  151. z-index: 2147483647 !important;
  152. transition: opacity 500ms ease-in;
  153. background: rgba(0, 0, 0, 0.75) !important;
  154. }
  155. .showToast .playbackRate {
  156. margin: 0 3px !important;
  157. color: #ff6101 !important;
  158. }
  159. `;
  160. this.getRoot().appendChild(style);
  161. this.getRoot().querySelectorAll(".showToast").forEach((el) => el.remove());
  162. }
  163. };
  164. var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
  165. var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
  166. const { EMPTY, ONE_SEC: ONE_SEC$1, MSG_SOURCE: MSG_SOURCE$1, SHOW_TOAST_TIME, SHOW_TOAST_POSITION } = constants;
  167. const matches = _GM_info.script.matches.map((url) => url.replace(/\*/g, EMPTY));
  168. const App = {
  169. init() {
  170. this.setupHoverListener();
  171. this.setupVisibleListener();
  172. this.setupKeydownListener();
  173. this.setupMutationObserver();
  174. this.setupUrlChangeListener();
  175. },
  176. isDouyu: () => location.host === "v.douyu.com",
  177. isLivePage: () => location.href.includes("live"),
  178. isBiliLive: () => location.host === "live.bilibili.com",
  179. query: (selector, context) => (context || document).querySelector(selector),
  180. querys: (selector, context) => (context || document).querySelectorAll(selector),
  181. validVideoDur: (video) => !isNaN(video.duration) && video.duration !== Infinity,
  182. inMatches: () => matches.some((matche) => location.href.includes(matche)),
  183. getVideo() {
  184. return this.isDouyu() ? douyu.getVideo() : document.querySelector("video[src]") || document.querySelector("video");
  185. },
  186. getElement() {
  187. return this.isDouyu() ? douyu.getWebfullIcon() : document.querySelector(selectorConfig[location.host]?.webfull);
  188. },
  189. debounce(fn, delay = ONE_SEC$1) {
  190. let timer;
  191. return function() {
  192. if (timer) clearTimeout(timer);
  193. timer = setTimeout(() => fn.apply(this, arguments), delay);
  194. };
  195. },
  196. setupVisibleListener() {
  197. window.addEventListener("visibilitychange", () => {
  198. const state = document.visibilityState;
  199. const video = this.isLivePage() ? this.getVideo() : this.video;
  200. if (video?.isEnded) return;
  201. Object.is(state, "visible") ? video?.play() : video?.pause();
  202. });
  203. },
  204. setupHoverListener() {
  205. if (this.inMatches()) return;
  206. document.addEventListener("mouseover", (event) => {
  207. const x = event.clientX;
  208. const y = event.clientY;
  209. const videos = this.querys("video");
  210. for (const video of videos) {
  211. const rect = video.getBoundingClientRect();
  212. const isInRect = rect.left <= x && rect.right >= x && rect.top <= y && rect.bottom >= y;
  213. if (!isInRect) continue;
  214. if (this.video === video) return;
  215. if (this.validVideoDur(video)) return this.rebindVideoEvtListener(video);
  216. }
  217. });
  218. },
  219. setupUrlChangeListener() {
  220. const _wr = (method) => {
  221. const original = history[method];
  222. history[method] = function() {
  223. original.apply(history, arguments);
  224. window.dispatchEvent(new Event(method));
  225. };
  226. };
  227. const handler = this.debounce(() => this.setupMutationObserver());
  228. ["popstate", "pushState", "replaceState"].forEach((t) => _wr(t) & window.addEventListener(t, handler));
  229. },
  230. setupMutationObserver() {
  231. this.videoListenerCycles = 0;
  232. const observer = new MutationObserver(() => {
  233. const video = this.getVideo();
  234. this.element = this.getElement();
  235. if (video?.play) this.setupVideoListener();
  236. if (video?.play && this.element) {
  237. const result = this.webFullScreen(video);
  238. if (!result) return;
  239. observer.disconnect();
  240. this.webFullScreenExtras();
  241. }
  242. });
  243. observer.observe(document.body, { childList: true, subtree: true });
  244. setTimeout(() => observer.disconnect(), ONE_SEC$1 * 10);
  245. },
  246. video: null,
  247. rebindVideo: false,
  248. videoListenerCycles: 0,
  249. videoBoundListeners: [],
  250. setupVideoListener() {
  251. if (this.isLivePage()) return;
  252. if (this.videoListenerCycles >= 5) return;
  253. this.addVideoEvtListener(this.getVideo());
  254. this.videoListenerCycles++;
  255. },
  256. addVideoEvtListener(video) {
  257. this.video = video;
  258. this.setVideoGeo(video);
  259. this.removeVideoEvtListener();
  260. for (const type of Object.keys(VideoListenerHandler)) {
  261. const handler = VideoListenerHandler[type];
  262. this.video.addEventListener(type, handler);
  263. this.videoBoundListeners.push([this.video, type, handler]);
  264. }
  265. },
  266. removeVideoEvtListener() {
  267. this.videoBoundListeners.forEach((listener) => {
  268. const [target, type, handler] = listener;
  269. target.removeEventListener(type, handler);
  270. });
  271. this.videoBoundListeners = [];
  272. },
  273. rebindVideoEvtListener(video) {
  274. this.rebindVideo = true;
  275. this.addVideoEvtListener(video);
  276. },
  277. setVideoGeo(video) {
  278. try {
  279. const rect = video.getBoundingClientRect();
  280. const x = rect.left + rect.width / 2;
  281. const y = rect.top + rect.height / 2;
  282. const videoGeo = this.videoGeo = { x, y };
  283. if (window.top !== window) window.parent.postMessage({ source: MSG_SOURCE$1, videoGeo }, "*");
  284. } catch (e) {
  285. }
  286. },
  287. showToast(content, duration = SHOW_TOAST_TIME) {
  288. this.query(".showToast")?.remove();
  289. if (this.isDouyu()) douyu.addStyle();
  290. const el = document.createElement("div");
  291. if (content instanceof HTMLElement) el.appendChild(content);
  292. if (Object.is(typeof content, "string")) el.textContent = content;
  293. el.setAttribute("class", "showToast");
  294. el.setAttribute("style", SHOW_TOAST_POSITION);
  295. this.video?.parentElement?.parentElement?.appendChild(el);
  296. setTimeout(() => {
  297. el.style.opacity = 0;
  298. setTimeout(() => el.remove(), ONE_SEC$1 / 2);
  299. }, duration);
  300. }
  301. };
  302. const { MSG_SOURCE, ASTERISK, INC_SYMBOL: INC_SYMBOL$1, DEC_SYMBOL: DEC_SYMBOL$1, MUL_SYMBOL: MUL_SYMBOL$1, DIV_SYMBOL: DIV_SYMBOL$1 } = constants;
  303. const KeydownHandler = {
  304. setupKeydownListener() {
  305. const handler = (event) => this.keydownHandler.call(this, event);
  306. window.addEventListener("keydown", handler, true);
  307. window.addEventListener("message", (event) => {
  308. const { data } = event;
  309. if (!data?.source) return;
  310. if (!data.source.includes(MSG_SOURCE)) return;
  311. if (data?.videoGeo) this.videoGeo = data.videoGeo;
  312. if (data?.hotKey) this.execHotKeyActions(data.hotKey);
  313. if (!this.video) this.postMsgToAllFrames(data);
  314. });
  315. },
  316. keydownHandler(event) {
  317. const activeTagName = document.activeElement.tagName;
  318. if (["INPUT", "TEXTAREA"].includes(activeTagName)) return;
  319. let hotKey = event.key.toUpperCase();
  320. if (event.shiftKey && hotKey === INC_SYMBOL$1) hotKey = MUL_SYMBOL$1;
  321. if (event.shiftKey && hotKey === DEC_SYMBOL$1) hotKey = DIV_SYMBOL$1;
  322. this.execHotKeyActions(hotKey);
  323. if (window.top === window && !this.video) this.postMsgToAllFrames({ hotKey });
  324. },
  325. execHotKeyActions(key) {
  326. const clickEl = (name, index) => {
  327. if (!this.isBiliLive()) return this.query(selectorConfig[location.host]?.[name])?.click();
  328. const control = this.getBiliLiveIcons();
  329. if (control) control[index]?.click();
  330. };
  331. const actions = {
  332. N: () => clickEl("next"),
  333. F: () => this.isDouyu() ? douyu.getFullIcon().click() : clickEl("full", 0),
  334. D: () => this.isDouyu() ? douyu.getDanmakuIcon().click() : clickEl("danmaku", 3),
  335. A: () => this.adjustPlayRate(INC_SYMBOL$1),
  336. S: () => this.adjustPlayRate(DEC_SYMBOL$1),
  337. Z: () => this.setPlayRate(1) && this.showToast("已恢复正常倍速播放"),
  338. 0: () => this.video ? this.video.currentTime = this.video.currentTime + 30 : null,
  339. ".": () => {
  340. if (this.isDouyu()) return this.video ? this.video.paused ? douyu.play() : douyu.pause() : null;
  341. this.video ? this.video.paused ? this.video.play() : this.video.pause() : null;
  342. },
  343. [ASTERISK]: () => this.getPlayingVideo(),
  344. [INC_SYMBOL$1]: () => this.adjustPlayRate(INC_SYMBOL$1),
  345. [DEC_SYMBOL$1]: () => this.adjustPlayRate(DEC_SYMBOL$1),
  346. [MUL_SYMBOL$1]: () => this.adjustPlayRate(MUL_SYMBOL$1),
  347. [DIV_SYMBOL$1]: () => this.adjustPlayRate(DIV_SYMBOL$1)
  348. };
  349. if (actions[key]) actions[key]();
  350. if (/^[1-9]$/.test(key)) this.setPlayRate(key) && this.showRateTip();
  351. if (Object.is("P", key)) {
  352. this.inMatches() ? this.isDouyu() ? douyu.getWebfullIcon().click() : clickEl("webfull", 1) : this.enhance();
  353. }
  354. },
  355. getPlayingVideo() {
  356. const videos = this.querys("video");
  357. for (const video of videos) {
  358. if (this.video === video || video.paused || !this.validVideoDur(video)) continue;
  359. this.rebindVideoEvtListener(video);
  360. return;
  361. }
  362. },
  363. getBiliLiveIcons() {
  364. const video = this.getVideo();
  365. if (!video) return;
  366. this.simuMousemove(video);
  367. return this.querys("#web-player-controller-wrap-el .right-area .icon");
  368. },
  369. postMsgToAllFrames(data) {
  370. const ifrs = this.querys("iframe");
  371. ifrs.forEach((ifr) => ifr?.contentWindow?.postMessage({ source: MSG_SOURCE, ...data }, "*"));
  372. },
  373. simuMousemove(target) {
  374. const y = target.offsetHeight / 2;
  375. const w = target.offsetWidth;
  376. const moveEvt = (x) => {
  377. const evt = new MouseEvent("mousemove", { clientX: x, clientY: y, bubbles: true });
  378. target.dispatchEvent(evt);
  379. };
  380. for (let i = 0; i < w; i += 100) moveEvt(i);
  381. }
  382. };
  383. const { ONE_SEC, QQ_VID_REG, BILI_VID_REG } = constants;
  384. const WebFullScreenHandler = {
  385. isFull() {
  386. return window.innerWidth === this.video.offsetWidth;
  387. },
  388. webFullScreen(video) {
  389. const w = video.offsetWidth;
  390. if (0 === w) return false;
  391. if (window.innerWidth === w) return true;
  392. if (this.isBiliLive()) return this.biliLiveWebFullScreen();
  393. this.element?.click();
  394. return true;
  395. },
  396. exitWebFullScreen() {
  397. if (window.innerWidth === this.video.offsetWidth) this.getElement()?.click();
  398. const cancelButton = this.query(".bpx-player-ending-related-item-cancel");
  399. if (cancelButton) setTimeout(() => cancelButton.click(), 100);
  400. console.log("已退出网页全屏!!");
  401. },
  402. biliLiveWebFullScreen() {
  403. try {
  404. const win = _unsafeWindow.top;
  405. win.scrollTo({ top: 70 });
  406. const el = Object.is(win, window) ? this.query("#player-ctnr") : this.query(":is(.lite-room, #player-ctnr)", win.document);
  407. win.scrollTo({ top: el?.getBoundingClientRect()?.top || 0 });
  408. this.element.dispatchEvent(new Event("dblclick", { bubbles: true }));
  409. localStorage.setItem("FULLSCREEN-GIFT-PANEL-SHOW", 0);
  410. document.body.classList.add("hide-asida-area", "hide-aside-area");
  411. win?.livePlayer?.volume(100);
  412. win?.livePlayer?.switchQuality("10000");
  413. } catch (error) {
  414. console.error("B站直播自动网页全屏异常:", error);
  415. }
  416. return true;
  417. },
  418. webFullScreenExtras() {
  419. this.biliVideoExtras();
  420. this.tencentVideoExtras();
  421. },
  422. tencentVideoExtras() {
  423. if (!QQ_VID_REG.test(location.href)) return;
  424. const observer = new MutationObserver((mutations) => {
  425. mutations.forEach((mutation) => {
  426. if (mutation.addedNodes.length === 0) return;
  427. mutation.addedNodes.forEach((node) => {
  428. if (node.nodeType !== Node.ELEMENT_NODE) return;
  429. if (!node.matches(".login-dialog-wrapper")) return;
  430. this.query(".main-login-wnd-module_close-button__mt9WU")?.click();
  431. observer.disconnect();
  432. });
  433. });
  434. });
  435. observer.observe(this.query("#login_win"), { attributes: true, childList: true, subtree: true });
  436. },
  437. biliVideoExtras() {
  438. if (!BILI_VID_REG.test(location.href)) return;
  439. if (document.cookie.includes("DedeUserID")) return player?.requestQuality(80);
  440. setTimeout(() => {
  441. _unsafeWindow.__BiliUser__.isLogin = true;
  442. _unsafeWindow.__BiliUser__.cache.data.isLogin = true;
  443. _unsafeWindow.__BiliUser__.cache.data.mid = Date.now();
  444. }, ONE_SEC * 3);
  445. }
  446. };
  447. const ScriptsEnhanceHandler = {
  448. enhance() {
  449. const target = this.getHoverEl();
  450. this.simuMouseover(target);
  451. this.triggerKeydownEvt();
  452. },
  453. getHoverEl() {
  454. if (this.hoverEl) return this.hoverEl;
  455. if (this.video) {
  456. this.hoverEl = this.video?.parentElement?.parentElement;
  457. return this.hoverEl;
  458. }
  459. const iframes = this.querys("iframe[src]");
  460. const { x, y } = this.videoGeo;
  461. for (const element of iframes) {
  462. const rect = element.getBoundingClientRect();
  463. const isInRect = x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
  464. if (!isInRect) continue;
  465. return this.hoverEl = element;
  466. }
  467. },
  468. simuMouseover(element) {
  469. if (!element) return;
  470. const x = element.offsetWidth / 2;
  471. const y = element.offsetHeight / 2;
  472. const evt = new MouseEvent("mouseover", { clientX: x, clientY: y, bubbles: true });
  473. element.dispatchEvent(evt);
  474. },
  475. triggerKeydownEvt() {
  476. if (!this.video) return;
  477. document.dispatchEvent(new KeyboardEvent("keydown", { keyCode: 27, bubbles: true }));
  478. }
  479. };
  480. const {
  481. INC_SYMBOL,
  482. DEC_SYMBOL,
  483. MUL_SYMBOL,
  484. DIV_SYMBOL,
  485. DEF_PLAY_RATE,
  486. MAX_PLAY_RATE,
  487. PLAY_RATE_STEP,
  488. CACHED_PLAY_RATE_KEY
  489. } = constants;
  490. const strategy = {
  491. [MUL_SYMBOL]: (playRate) => playRate * 2,
  492. [DIV_SYMBOL]: (playRate) => playRate / 2,
  493. [INC_SYMBOL]: (playRate) => playRate + PLAY_RATE_STEP,
  494. [DEC_SYMBOL]: (playRate) => playRate - PLAY_RATE_STEP
  495. };
  496. const VideoPlaybackRateHandler = {
  497. checkVideoUsable() {
  498. if (this.isLivePage()) return false;
  499. if (!this.video) return false;
  500. if (this.rebindVideo) return true;
  501. if (this.video === this.getVideo()) return true;
  502. this.setupVideoListener();
  503. return false;
  504. },
  505. setPlayRate(playRate) {
  506. if (!this.checkVideoUsable()) return;
  507. this.video.playbackRate = playRate;
  508. this.cachePlayRate();
  509. return true;
  510. },
  511. adjustPlayRate(_symbol) {
  512. if (!this.checkVideoUsable()) return;
  513. let playRate = this.video.playbackRate;
  514. playRate = strategy[_symbol](playRate);
  515. playRate = Math.max(PLAY_RATE_STEP, playRate);
  516. this.video.playbackRate = Math.min(MAX_PLAY_RATE, playRate);
  517. this.cachePlayRate();
  518. this.showRateTip();
  519. },
  520. cachePlayRate() {
  521. localStorage.setItem(CACHED_PLAY_RATE_KEY, this.video.playbackRate);
  522. },
  523. getCachePlayRate() {
  524. const cachePlayRate = localStorage.getItem(CACHED_PLAY_RATE_KEY);
  525. return parseFloat(cachePlayRate || DEF_PLAY_RATE);
  526. },
  527. showRateTip() {
  528. const span = document.createElement("span");
  529. span.appendChild(document.createTextNode("正在以"));
  530. const child = span.cloneNode(true);
  531. child.textContent = `${this.video.playbackRate}x`;
  532. child.setAttribute("class", "playbackRate");
  533. span.appendChild(child);
  534. span.appendChild(document.createTextNode("倍速播放"));
  535. this.showToast(span);
  536. }
  537. };
  538. const logicHandlers = [
  539. { handler: KeydownHandler },
  540. { handler: WebFullScreenHandler },
  541. { handler: VideoPlaybackRateHandler },
  542. { handler: ScriptsEnhanceHandler }
  543. ];
  544. logicHandlers.forEach(({ handler }) => {
  545. for (const method of Object.keys(handler)) {
  546. App[method] = handler[method].bind(App);
  547. }
  548. });
  549. App.init();
  550.  
  551. })();