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

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

安裝腳本?
作者推薦腳本

您可能也會喜歡 M站_哔咪动漫脚本

安裝腳本
  1. // ==UserScript==
  2. // @name 视频网站自动网页全屏|倍速播放
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.6.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. // @include *://pages.iqiyi.com/p/zy/*
  11. // @include *://www.ezdmw.site/Index/video/*
  12. // @include *://player.ezdmw.com/danmuku/*
  13. // @include *://*bimiacg*.net/*/play*
  14. // @include *://acgfta.com/play*
  15. // @include *://ppoft.com/play*
  16. // @match *://tv.sohu.com/v/*
  17. // @match *://www.mgtv.com/b/*
  18. // @match *://www.acfun.cn/v/*
  19. // @match *://www.iqiyi.com/v_*
  20. // @match *://v.qq.com/x/page/*
  21. // @match *://v.qq.com/x/cover/*
  22. // @match *://haokan.baidu.com/v*
  23. // @match *://live.bilibili.com/*
  24. // @match *://v.youku.com/video?*
  25. // @match *://v.youku.com/v_show/*
  26. // @match *://live.acfun.cn/live/*
  27. // @match *://www.acfun.cn/bangumi/*
  28. // @match *://www.bilibili.com/list/*
  29. // @match *://www.bilibili.com/video/*
  30. // @match *://www.bilibili.com/*/play/*
  31. // @match *://v.qq.com/live/p/newtopic/*
  32. // @match *://www.bilibili.com/festival/*
  33. // @match *://v.douyu.com/show/*
  34. // @grant GM_addStyle
  35. // @grant GM_getValue
  36. // @grant GM_info
  37. // @grant GM_registerMenuCommand
  38. // @grant GM_setValue
  39. // @grant GM_unregisterMenuCommand
  40. // @grant unsafeWindow
  41. // @note *://*/*
  42. // ==/UserScript==
  43.  
  44. (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:13.5px!important;padding:5px 15px!important;border-radius:5px!important;position:absolute!important;z-index:2147483647!important;font-weight:400!important;transition:opacity .5s ease-in;background:#000000bf!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} ');
  45.  
  46. (function () {
  47. 'use strict';
  48.  
  49. var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
  50. var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
  51. var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
  52. var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
  53. var _GM_unregisterMenuCommand = /* @__PURE__ */ (() => typeof GM_unregisterMenuCommand != "undefined" ? GM_unregisterMenuCommand : void 0)();
  54. var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
  55. const positions = Object.freeze({
  56. bottomLeft: "bottom: 17%; left: 10px;",
  57. center: "top: 50%; left: 50%; transform: translate(-50%, -50%);"
  58. });
  59. const keyCode = (() => {
  60. const result = {};
  61. for (let i = 65; i <= 90; i++) {
  62. const letter = String.fromCharCode(i);
  63. result[letter] = letter;
  64. }
  65. return result;
  66. })();
  67. const ONE_SECOND = 1e3;
  68. const constants = Object.freeze({
  69. EMPTY: "",
  70. ASTERISK: "*",
  71. INC_SYMBOL: "+",
  72. DEC_SYMBOL: "-",
  73. MUL_SYMBOL: "×",
  74. DIV_SYMBOL: "÷",
  75. DEF_PLAY_RATE: 1,
  76. MAX_PLAY_RATE: 16,
  77. ONE_SEC: ONE_SECOND,
  78. SHOW_TOAST_TIME: ONE_SECOND * 5,
  79. SHOW_TOAST_POSITION: positions.bottomLeft,
  80. MSG_SOURCE: "FENY_SCRIPTS_AUTO_WEB_FULLSCREEN",
  81. CACHED_PLAY_RATE_KEY: "FENY_SCRIPTS_V_PLAYBACK_RATE",
  82. CLOSE_AUTO_WEB_FULL_KEY: "CLOSE_AUTO_WEB_FULL_SCREEN",
  83. KEYBOARD_COMMAND_KEY: "KEYBOARD_COMMAND",
  84. QQ_VID_REG: /v.qq.com\/x/,
  85. ACFUN_VID_REG: /acfun.cn\/v/,
  86. IQIYI_VID_REG: /iqiyi.com\/v_*/,
  87. BILI_VID_REG: /bilibili.com\/video/,
  88. KEYCODE: Object.freeze({ ...keyCode, SPACE: " " }),
  89. VIDEO_FASTFORWARD_DURATION: {
  90. name: "VIDEO_FASTFORWARD_DURATION",
  91. value() {
  92. return Number.parseInt(_GM_getValue(this.name, 30));
  93. }
  94. },
  95. VIDEO_TIME_STEP: {
  96. name: "VIDEO_TIME_STEP",
  97. value() {
  98. return Number.parseInt(_GM_getValue(this.name, 5));
  99. }
  100. },
  101. PLAY_RATE_STEP: {
  102. name: "PLAY_RATE_STEP",
  103. value() {
  104. return Number.parseFloat(_GM_getValue(this.name, 0.25));
  105. }
  106. }
  107. });
  108. const selectorConfig = {
  109. "live.bilibili.com": { webfull: "#businessContainerElement" },
  110. "www.bilibili.com": { webfull: "div[aria-label='网页全屏']", next: ".bpx-player-ctrl-next" },
  111. "live.acfun.cn": { full: ".fullscreen-screen", webfull: ".fullscreen-web", danmaku: ".danmaku-enabled" },
  112. "tv.sohu.com": { full: ".x-fullscreen-btn", webfull: ".x-pagefs-btn", danmaku: ".tm-tmbtn", next: ".x-next-btn" },
  113. "haokan.baidu.com": { full: ".art-icon-fullscreen", webfull: ".art-control-fullscreenWeb", next: ".art-control-next" },
  114. "www.iqiyi.com": { full: ".iqp-btn-fullscreen", webfull: ".iqp-btn-webscreen", danmaku: "#barrage_switch", next: ".iqp-btn-next" },
  115. "www.mgtv.com": { full: ".fullscreenBtn i", webfull: ".webfullscreenBtn i", danmaku: "div[class*='danmuSwitch']", next: ".icon-next" },
  116. "v.qq.com": { full: ".txp_btn_fullscreen", webfull: "div[aria-label='网页全屏']", danmaku: ".barrage-switch", next: ".txp_btn_next_u" },
  117. "v.pptv.com": { full: ".w-zoom-container > div", webfull: ".w-expand-container > div", danmaku: ".w-barrage", next: ".w-next-container" },
  118. "www.acfun.cn": { full: ".fullscreen-screen", webfull: ".fullscreen-web", danmaku: ".danmaku-enabled", next: ".btn-next-part .control-btn" },
  119. "v.youku.com": { full: "#fullscreen-icon", webfull: "#webfullscreen-icon", danmaku: "div[class*='switch-img_12hDa turn-']", next: ".kui-next-icon-0" }
  120. };
  121. const { DEF_PLAY_RATE: DEF_PLAY_RATE$1, BILI_VID_REG: BILI_VID_REG$1, ACFUN_VID_REG } = constants;
  122. const VideoListenerHandler = {
  123. loadedmetadata() {
  124. this.volume = 1;
  125. this.isToast = false;
  126. },
  127. loadeddata() {
  128. this.volume = 1;
  129. this.isToast = false;
  130. },
  131. timeupdate() {
  132. if (this.duration === NaN) return;
  133. const cachePlayRate = App.getCachePlayRate();
  134. if (!cachePlayRate || DEF_PLAY_RATE$1 === cachePlayRate) return;
  135. if (cachePlayRate === this.playbackRate) return;
  136. const reuslt = App.setPlayRate(cachePlayRate);
  137. if (!reuslt) return;
  138. if (this.isToast) return;
  139. App.showRateTip();
  140. this.isToast = true;
  141. },
  142. play() {
  143. this.isEnded = false;
  144. App.webFullScreen(this);
  145. },
  146. ended() {
  147. this.isEnded = true;
  148. this.isToast = false;
  149. const href = location.href;
  150. if (!BILI_VID_REG$1.test(href) && !ACFUN_VID_REG.test(href)) return;
  151. const pod = App.query(".video-pod");
  152. const pods = App.querys('.video-pod .switch-btn:not(.on), .video-pod__item:last-of-type[data-scrolled="true"]');
  153. if (!pod || pods.length > 0) App.exitWebFullScreen();
  154. }
  155. };
  156. const douyu = {
  157. getRoot() {
  158. return document.querySelector("demand-video").shadowRoot;
  159. },
  160. getControllerBar() {
  161. return this.getRoot().querySelector("#demandcontroller-bar").shadowRoot;
  162. },
  163. getVideo() {
  164. return this.getRoot().querySelector("video");
  165. },
  166. play() {
  167. this.getControllerBar().querySelector(".ControllerBarPlay").click();
  168. },
  169. pause() {
  170. this.getControllerBar().querySelector(".ControllerBarStop").click();
  171. },
  172. getWebfullIcon() {
  173. return this.getControllerBar().querySelector(".ControllerBar-PageFull-Icon");
  174. },
  175. getFullIcon() {
  176. return this.getControllerBar().querySelector(".ControllerBar-WindowFull-Icon");
  177. },
  178. getDanmakuIcon() {
  179. return document.querySelector("demand-player-extension").shadowRoot.querySelector(".BarrageSwitch-icon");
  180. },
  181. addStyle() {
  182. const root = this.getRoot();
  183. let style = root.querySelector("style");
  184. if (style) return;
  185. style = document.createElement("style");
  186. style.textContent = `
  187. .showToast {
  188. color: #fff !important;
  189. font-size: 13.5px !important;
  190. padding: 5px 15px !important;
  191. border-radius: 5px !important;
  192. position: absolute !important;
  193. z-index: 2147483647 !important;
  194. font-weight: normal !important;
  195. transition: opacity 500ms ease-in;
  196. background: rgba(0, 0, 0, 0.75) !important;
  197. }
  198. `;
  199. root.prepend(style);
  200. }
  201. };
  202. const { ONE_SEC: ONE_SEC$1, QQ_VID_REG, MSG_SOURCE: MSG_SOURCE$1, SHOW_TOAST_TIME, SHOW_TOAST_POSITION } = constants;
  203. const matches = _GM_info.script.matches.filter((match) => match !== "*://*/*").map((match) => match.replace(/\*/g, "\\S+"));
  204. const App = {
  205. init() {
  206. this.setupHoverListener();
  207. this.registerMenuCommand();
  208. this.setupVisibleListener();
  209. this.setupKeydownListener();
  210. this.setupMutationObserver();
  211. this.setupUrlChangeListener();
  212. },
  213. isTopWin: () => window.top === window,
  214. isDouyu: () => location.host === "v.douyu.com",
  215. isTencent: () => QQ_VID_REG.test(location.href),
  216. isLivePage: () => location.href.includes("live"),
  217. isBiliLive: () => location.host === "live.bilibili.com",
  218. query: (selector, context) => (context || document).querySelector(selector),
  219. querys: (selector, context) => (context || document).querySelectorAll(selector),
  220. validVideoDur: (video) => !isNaN(video.duration) && video.duration !== Infinity,
  221. inMatches: () => matches.some((matche) => new RegExp(matche).test(location.href)),
  222. postMessage: (win = null, data) => win?.postMessage({ source: MSG_SOURCE$1, ...data }, "*"),
  223. getVideo() {
  224. return this.isDouyu() ? douyu.getVideo() : this.query("video:not([src=''])") || this.querys("video");
  225. },
  226. getElement() {
  227. return this.isDouyu() ? douyu.getWebfullIcon() : document.querySelector(selectorConfig[location.host]?.webfull);
  228. },
  229. getVideoIframe() {
  230. if (!this.videoGeo.frameSrc) return null;
  231. const url = new URL(this.videoGeo.frameSrc);
  232. const src = decodeURI(url.pathname + url.search);
  233. return this.query(`iframe[src*="${src}"]`);
  234. },
  235. debounce(fn, delay = ONE_SEC$1) {
  236. let timer;
  237. return function() {
  238. if (timer) clearTimeout(timer);
  239. timer = setTimeout(() => fn.apply(this, arguments), delay);
  240. };
  241. },
  242. setupVisibleListener() {
  243. window.addEventListener("visibilitychange", () => {
  244. window.top.focus();
  245. const video = this.isLivePage() ? this.getVideo() : this.video;
  246. if (video?.isEnded) return;
  247. document.hidden ? video?.pause() : video?.play();
  248. });
  249. },
  250. setupHoverListener() {
  251. if (this.inMatches()) return;
  252. document.addEventListener("mouseover", (event) => {
  253. const x = event.clientX;
  254. const y = event.clientY;
  255. const videos = this.querys("video");
  256. for (const video of videos) {
  257. const rect = video.getBoundingClientRect();
  258. const isInRect = rect.left <= x && rect.right >= x && rect.top <= y && rect.bottom >= y;
  259. if (!isInRect) continue;
  260. if (this.video === video) return;
  261. if (this.validVideoDur(video)) return this.rebindVideoEvtListener(video);
  262. }
  263. });
  264. },
  265. setupUrlChangeListener() {
  266. const _wr = (method) => {
  267. const original = history[method];
  268. history[method] = function() {
  269. original.apply(history, arguments);
  270. window.dispatchEvent(new Event(method));
  271. };
  272. };
  273. const handler = this.debounce(() => this.setupMutationObserver());
  274. ["popstate", "pushState", "replaceState"].forEach((t) => _wr(t) & window.addEventListener(t, handler));
  275. },
  276. setupMutationObserver() {
  277. const observer = new MutationObserver(() => {
  278. const video = this.getVideo();
  279. this.element = this.getElement();
  280. if (video?.play) this.setupVideoListener();
  281. if (video?.play && this.element) {
  282. const result = this.webFullScreen(video);
  283. if (!result) return;
  284. observer.disconnect();
  285. this.webFullScreenExtras();
  286. }
  287. });
  288. observer.observe(document.body, { childList: true, subtree: true });
  289. setTimeout(() => observer.disconnect(), ONE_SEC$1 * 10);
  290. },
  291. video: null,
  292. rebindVideo: false,
  293. videoBoundListeners: [],
  294. setupVideoListener() {
  295. if (this.isLivePage()) return;
  296. this.addVideoEvtListener(this.getVideo());
  297. this.heartbeatCurrentVideo();
  298. },
  299. addVideoEvtListener(video) {
  300. this.video = video;
  301. this.setVideoGeo(video);
  302. this.removeVideoEvtListener();
  303. for (const type of Object.keys(VideoListenerHandler)) {
  304. const handler = VideoListenerHandler[type];
  305. this.video.addEventListener(type, handler);
  306. this.videoBoundListeners.push([this.video, type, handler]);
  307. }
  308. },
  309. removeVideoEvtListener() {
  310. this.videoBoundListeners.forEach((listener) => {
  311. const [target, type, handler] = listener;
  312. target.removeEventListener(type, handler);
  313. });
  314. this.videoBoundListeners = [];
  315. },
  316. rebindVideoEvtListener(video) {
  317. this.rebindVideo = true;
  318. this.addVideoEvtListener(video);
  319. },
  320. getPlayingVideo() {
  321. const videos = this.querys("video");
  322. for (const video of videos) {
  323. if (this.video === video || video.paused || !this.validVideoDur(video)) continue;
  324. return this.rebindVideoEvtListener(video);
  325. }
  326. },
  327. heartbeatCurrentVideo() {
  328. setInterval(() => this.getPlayingVideo(), ONE_SEC$1);
  329. },
  330. setVideoGeo(video) {
  331. try {
  332. const rect = video.getBoundingClientRect();
  333. const x = rect.left + rect.width / 2;
  334. const y = rect.top + rect.height / 2;
  335. const videoGeo = this.videoGeo = { x, y, frameSrc: !this.isTopWin() ? location.href : null };
  336. if (!this.isTopWin()) parent.postMessage({ source: MSG_SOURCE$1, videoGeo }, "*");
  337. } catch (e) {
  338. }
  339. },
  340. showToast(content, duration = SHOW_TOAST_TIME) {
  341. if (this.isDouyu()) douyu.addStyle();
  342. const el = document.createElement("div");
  343. if (content instanceof HTMLElement) el.appendChild(content);
  344. if (Object.is(typeof content, "string")) el.textContent = content;
  345. el.setAttribute("class", "showToast");
  346. el.setAttribute("style", SHOW_TOAST_POSITION);
  347. const target = this.video?.parentElement?.parentElement;
  348. this.query(".showToast", target)?.remove();
  349. target?.appendChild(el);
  350. setTimeout(() => {
  351. el.style.opacity = 0;
  352. setTimeout(() => el.remove(), ONE_SEC$1 / 2);
  353. }, duration);
  354. }
  355. };
  356. const {
  357. EMPTY,
  358. KEYCODE,
  359. ASTERISK,
  360. MSG_SOURCE,
  361. INC_SYMBOL: INC_SYMBOL$1,
  362. DEC_SYMBOL: DEC_SYMBOL$1,
  363. MUL_SYMBOL: MUL_SYMBOL$1,
  364. DIV_SYMBOL: DIV_SYMBOL$1,
  365. VIDEO_TIME_STEP: VIDEO_TIME_STEP$1,
  366. VIDEO_FASTFORWARD_DURATION: VIDEO_FASTFORWARD_DURATION$1
  367. } = constants;
  368. const KeydownHandler = {
  369. preventDefault(event) {
  370. if (this.isCloseKeyboard()) return;
  371. if (![" ", "ArrowLeft", "ArrowRight"].includes(event.key)) return;
  372. event.preventDefault();
  373. event.stopPropagation();
  374. event.stopImmediatePropagation();
  375. },
  376. setupKeydownListener() {
  377. window.addEventListener("focus", () => this.isFocused = true);
  378. window.addEventListener("blur", () => this.isFocused = false);
  379. window.addEventListener("keyup", (event) => this.preventDefault(event), true);
  380. window.addEventListener("keydown", (event) => this.keydownHandler.call(this, event), true);
  381. window.addEventListener("message", (event) => {
  382. const { data } = event;
  383. if (!data?.source) return;
  384. if (!data.source.includes(MSG_SOURCE)) return;
  385. if (data?.videoGeo) return this.videoGeo = data.videoGeo;
  386. if (data?.reloadVideoFrame) return this.reloadVideoFrame();
  387. if (data?.isCloseKeyboard) return this.switchKeyboard(data?.isCloseKeyboard);
  388. this.processEvent(data);
  389. });
  390. },
  391. keydownHandler(event) {
  392. if (!this.video && !this.videoGeo) return;
  393. this.preventDefault(event);
  394. const { shiftKey } = event;
  395. let key = event.key.toUpperCase();
  396. let code = event.code.toUpperCase();
  397. if (key === KEYCODE.SPACE) key = code;
  398. if (shiftKey && key === INC_SYMBOL$1) key = MUL_SYMBOL$1;
  399. if (shiftKey && key === DEC_SYMBOL$1) key = DIV_SYMBOL$1;
  400. if (["INPUT", "TEXTAREA", "DEMAND-SEARCH-BOX"].includes(event.target.tagName)) return;
  401. if (!this.isTopWin() && !this.inMatches() && key === KEYCODE.P) return this.postMessage(window.top, { key });
  402. this.processEvent({ key });
  403. },
  404. processEvent(data) {
  405. if (!this.video) this.postMsgToFrames(data);
  406. if (data?.key) this.execHotKeyActions(data.key);
  407. },
  408. execHotKeyActions(key) {
  409. const clickEl = (name, index) => {
  410. if (!this.isBiliLive()) return this.query(selectorConfig[location.host]?.[name])?.click();
  411. const control = this.getBiliLiveIcons();
  412. if (control) control[index]?.click();
  413. };
  414. const actions = {
  415. N: () => clickEl("next"),
  416. A: () => this.adjustPlayRate(INC_SYMBOL$1),
  417. S: () => this.adjustPlayRate(DEC_SYMBOL$1),
  418. [INC_SYMBOL$1]: () => this.adjustPlayRate(INC_SYMBOL$1),
  419. [DEC_SYMBOL$1]: () => this.adjustPlayRate(DEC_SYMBOL$1),
  420. [MUL_SYMBOL$1]: () => this.adjustPlayRate(MUL_SYMBOL$1),
  421. [DIV_SYMBOL$1]: () => this.adjustPlayRate(DIV_SYMBOL$1),
  422. Z: () => this.setPlayRate(1) && this.showToast("已恢复正常倍速播放"),
  423. F: () => this.isDouyu() ? douyu.getFullIcon().click() : clickEl("full", 0),
  424. D: () => this.isDouyu() ? douyu.getDanmakuIcon().click() : clickEl("danmaku", 3),
  425. ARROWLEFT: () => !this.isCloseKeyboard() ? this.adjustVideoTime(DEC_SYMBOL$1) : null,
  426. ARROWRIGHT: () => !this.isCloseKeyboard() ? this.adjustVideoTime() : null,
  427. 0: () => this.adjustVideoTime(VIDEO_FASTFORWARD_DURATION$1.value()),
  428. SPACE: () => {
  429. if (!this.video || this.isCloseKeyboard()) return;
  430. if (this.isDouyu()) return this.video.paused ? douyu.play() : douyu.pause();
  431. this.video.paused ? this.video.play().catch(() => {
  432. if (!this.videoGeo.frameSrc) return this.showToast("请手动点击播放");
  433. this.query(":is(*[class*='icon-play'], *[class*='play-icon'])")?.click();
  434. this.postMessage(window.parent, { reloadVideoFrame: true });
  435. }) : this.video.pause();
  436. },
  437. P: () => {
  438. if (!this.inMatches()) return this.enhance();
  439. this.isDouyu() ? douyu.getWebfullIcon().click() : clickEl("webfull", 1);
  440. }
  441. };
  442. if (actions[key]) actions[key]();
  443. if (/^[1-9]$/.test(key)) this.setPlayRate(key) && this.showRateTip();
  444. },
  445. adjustVideoTime(second = VIDEO_TIME_STEP$1.value(), _symbol) {
  446. if (!this.video) return;
  447. if (_symbol && ![INC_SYMBOL$1, DEC_SYMBOL$1].includes(_symbol)) return;
  448. if (Object.is(typeof second, typeof EMPTY) && !_symbol) {
  449. _symbol = second;
  450. second = VIDEO_TIME_STEP$1.value();
  451. }
  452. second = Object.is(DEC_SYMBOL$1, _symbol) ? -second : second;
  453. const currentTime = this.video.currentTime + second;
  454. this.video.currentTime = Math.max(0, currentTime);
  455. },
  456. reloadVideoFrame() {
  457. if (!this.isTopWin() || this.hasReloaded) return;
  458. const iframe = this.getVideoIframe();
  459. if (!iframe) return;
  460. iframe.setAttribute("src", iframe.src);
  461. this.hasReloaded = true;
  462. },
  463. getBiliLiveIcons() {
  464. const video = this.getVideo();
  465. if (!video) return;
  466. this.simuMousemove(video);
  467. return this.querys("#web-player-controller-wrap-el .right-area .icon");
  468. },
  469. postMsgToFrames(data) {
  470. this.querys("iframe:not([src=''])").forEach((iframe) => this.postMessage(iframe.contentWindow, data));
  471. },
  472. simuMousemove(target) {
  473. const y = target.offsetHeight / 2;
  474. const w = target.offsetWidth;
  475. const moveEvt = (x) => {
  476. const evt = new MouseEvent("mousemove", { clientX: x, clientY: y, bubbles: true });
  477. target.dispatchEvent(evt);
  478. };
  479. for (let i = 0; i < w; i += 100) moveEvt(i);
  480. }
  481. };
  482. const { CLOSE_AUTO_WEB_FULL_KEY, PLAY_RATE_STEP: PLAY_RATE_STEP$1, VIDEO_TIME_STEP, KEYBOARD_COMMAND_KEY, VIDEO_FASTFORWARD_DURATION } = constants;
  483. const MenuCommandHandler = {
  484. isCloseAuto: () => _GM_getValue(CLOSE_AUTO_WEB_FULL_KEY, false),
  485. isCloseKeyboard: () => _GM_getValue(KEYBOARD_COMMAND_KEY, true),
  486. registerMenuCommand() {
  487. this.setupPlayRateStepCommand();
  488. this.setupVideoTimeStepCommand();
  489. this.setupVideoFastforwardCommand();
  490. this.setupWebFullScreenCommand();
  491. this.setupKeyboardCommand();
  492. },
  493. setupPlayRateStepCommand() {
  494. const title = "设置倍速步进";
  495. _GM_registerMenuCommand(title, () => {
  496. const input = prompt(title, _GM_getValue(PLAY_RATE_STEP$1.name, 0.25));
  497. if (!isNaN(input) && Number.parseFloat(input)) _GM_setValue(PLAY_RATE_STEP$1.name, input);
  498. });
  499. },
  500. setupVideoTimeStepCommand() {
  501. const title = "设置快进/快退时长";
  502. _GM_registerMenuCommand(title, () => {
  503. const input = prompt(title, _GM_getValue(VIDEO_TIME_STEP.name, 5));
  504. if (!isNaN(input) && Number.parseInt(input)) _GM_setValue(VIDEO_TIME_STEP.name, input);
  505. });
  506. },
  507. setupVideoFastforwardCommand() {
  508. const title = "设置数字零键快进时长";
  509. _GM_registerMenuCommand(title, () => {
  510. const input = prompt(title, _GM_getValue(VIDEO_FASTFORWARD_DURATION.name, 30));
  511. if (!isNaN(input) && Number.parseInt(input)) _GM_setValue(VIDEO_FASTFORWARD_DURATION.name, input);
  512. });
  513. },
  514. setupWebFullScreenCommand() {
  515. const isClose = _GM_getValue(CLOSE_AUTO_WEB_FULL_KEY, false);
  516. const web_full_id = _GM_registerMenuCommand(isClose ? "开启自动网页全屏" : "关闭自动网页全屏", () => {
  517. _GM_setValue(CLOSE_AUTO_WEB_FULL_KEY, !isClose);
  518. _GM_unregisterMenuCommand(web_full_id);
  519. this.setupWebFullScreenCommand();
  520. });
  521. },
  522. setupKeyboardCommand() {
  523. const isClose = this.isCloseKeyboard();
  524. const title = isClose ? "开启 空格 ◀▶ 键盘控制" : "关闭 空格 ◀▶ 键盘控制";
  525. const keyboard_id = _GM_registerMenuCommand(title, () => {
  526. _GM_setValue(KEYBOARD_COMMAND_KEY, !isClose);
  527. _GM_unregisterMenuCommand(keyboard_id);
  528. this.setupKeyboardCommand();
  529. });
  530. }
  531. };
  532. const { ONE_SEC, BILI_VID_REG } = constants;
  533. const WebFullScreenHandler = {
  534. isFull() {
  535. return window.innerWidth === this.video.offsetWidth;
  536. },
  537. webFullScreen(video) {
  538. const w = video.offsetWidth;
  539. if (Object.is(0, w)) return false;
  540. if (this.isCloseAuto()) return true;
  541. if (window.innerWidth === w) return true;
  542. if (this.isBiliLive()) return this.biliLiveWebFullScreen();
  543. this.element?.click();
  544. return true;
  545. },
  546. exitWebFullScreen() {
  547. if (window.innerWidth === this.video.offsetWidth) this.getElement()?.click();
  548. const cancelButton = this.query(".bpx-player-ending-related-item-cancel");
  549. if (cancelButton) setTimeout(() => cancelButton.click(), 100);
  550. },
  551. biliLiveWebFullScreen() {
  552. try {
  553. const win = _unsafeWindow.top;
  554. win.scrollTo({ top: 70 });
  555. const el = Object.is(win, window) ? this.query("#player-ctnr") : this.query(":is(.lite-room, #player-ctnr)", win.document);
  556. win.scrollTo({ top: el?.getBoundingClientRect()?.top || 0 });
  557. this.element.dispatchEvent(new Event("dblclick", { bubbles: true }));
  558. localStorage.setItem("FULLSCREEN-GIFT-PANEL-SHOW", 0);
  559. document.body.classList.add("hide-asida-area", "hide-aside-area");
  560. win?.livePlayer?.volume(100);
  561. win?.livePlayer?.switchQuality("10000");
  562. } catch (error) {
  563. console.error("B站直播自动网页全屏异常:", error);
  564. }
  565. return true;
  566. },
  567. webFullScreenExtras() {
  568. this.biliVideoExtras();
  569. this.tencentVideoExtras();
  570. },
  571. tencentVideoExtras() {
  572. if (!this.isTencent()) return;
  573. const observer = new MutationObserver((mutations) => {
  574. mutations.forEach((mutation) => {
  575. if (mutation.addedNodes.length === 0) return;
  576. mutation.addedNodes.forEach((node) => {
  577. if (node.nodeType !== Node.ELEMENT_NODE) return;
  578. if (!node.matches(".login-dialog-wrapper")) return;
  579. this.query(".main-login-wnd-module_close-button__mt9WU")?.click();
  580. observer.disconnect();
  581. });
  582. });
  583. });
  584. observer.observe(this.query("#login_win"), { attributes: true, childList: true, subtree: true });
  585. },
  586. biliVideoExtras() {
  587. if (!BILI_VID_REG.test(location.href)) return;
  588. if (document.cookie.includes("DedeUserID")) return player?.requestQuality(80);
  589. setTimeout(() => {
  590. _unsafeWindow.__BiliUser__.isLogin = true;
  591. _unsafeWindow.__BiliUser__.cache.data.isLogin = true;
  592. _unsafeWindow.__BiliUser__.cache.data.mid = Date.now();
  593. }, ONE_SEC * 3);
  594. }
  595. };
  596. const ScriptsEnhanceHandler = {
  597. enhance() {
  598. this.simuMouseover(this.getHoverEl());
  599. this.triggerKeydownEvt();
  600. },
  601. getHoverEl() {
  602. if (!this.videoGeo) return;
  603. if (this.hoverEl) return this.hoverEl;
  604. if (this.video) return this.hoverEl = this.video?.parentElement?.parentElement;
  605. const { x, y } = this.videoGeo;
  606. const iframe = this.getVideoIframe();
  607. if (iframe) return this.hoverEl = iframe;
  608. const iframes = this.querys("iframe:not([src=''])");
  609. for (const element of iframes) {
  610. const rect = element.getBoundingClientRect();
  611. const isInRect = x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
  612. if (!isInRect) continue;
  613. return this.hoverEl = element;
  614. }
  615. },
  616. simuMouseover(element) {
  617. console.log("鼠标悬停网页全屏元素:", element);
  618. if (!element) return;
  619. const x = element.offsetWidth / 2;
  620. const y = element.offsetHeight / 2;
  621. element?.dispatchEvent(new MouseEvent("mouseover", { clientX: x, clientY: y, bubbles: true }));
  622. },
  623. triggerKeydownEvt() {
  624. if (!this.video) return;
  625. document?.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", keyCode: 27, bubbles: true }));
  626. }
  627. };
  628. const {
  629. INC_SYMBOL,
  630. DEC_SYMBOL,
  631. MUL_SYMBOL,
  632. DIV_SYMBOL,
  633. DEF_PLAY_RATE,
  634. MAX_PLAY_RATE,
  635. PLAY_RATE_STEP,
  636. CACHED_PLAY_RATE_KEY
  637. } = constants;
  638. const strategy = {
  639. [MUL_SYMBOL]: (playRate) => playRate * 2,
  640. [DIV_SYMBOL]: (playRate) => playRate / 2,
  641. [INC_SYMBOL]: (playRate) => playRate + PLAY_RATE_STEP.value(),
  642. [DEC_SYMBOL]: (playRate) => playRate - PLAY_RATE_STEP.value()
  643. };
  644. const VideoPlaybackRateHandler = {
  645. checkVideoUsable() {
  646. if (!this.video) return false;
  647. if (this.rebindVideo) return true;
  648. if (this.isLivePage()) return false;
  649. if (this.video === this.getVideo()) return true;
  650. return false;
  651. },
  652. setPlayRate(playRate) {
  653. if (!this.checkVideoUsable()) return;
  654. this.video.playbackRate = playRate;
  655. this.cachePlayRate();
  656. return true;
  657. },
  658. adjustPlayRate(_symbol) {
  659. if (!this.checkVideoUsable()) return;
  660. let playRate = this.video.playbackRate;
  661. playRate = strategy[_symbol](playRate);
  662. playRate = Math.max(PLAY_RATE_STEP.value(), playRate);
  663. this.video.playbackRate = Math.min(MAX_PLAY_RATE, playRate);
  664. this.cachePlayRate();
  665. this.showRateTip();
  666. },
  667. cachePlayRate() {
  668. localStorage.setItem(CACHED_PLAY_RATE_KEY, this.video.playbackRate);
  669. },
  670. getCachePlayRate() {
  671. const cachePlayRate = localStorage.getItem(CACHED_PLAY_RATE_KEY);
  672. return parseFloat(cachePlayRate || DEF_PLAY_RATE);
  673. },
  674. showRateTip() {
  675. const span = document.createElement("span");
  676. span.appendChild(document.createTextNode("正在以"));
  677. const child = span.cloneNode(true);
  678. child.textContent = `${this.video.playbackRate.toFixed(2).replace(/\.?0+$/, "")}x`;
  679. child.setAttribute("style", "margin:0 3px!important;color:#ff6101!important;");
  680. span.appendChild(child);
  681. span.appendChild(document.createTextNode("倍速播放"));
  682. this.showToast(span);
  683. }
  684. };
  685. const logicHandlers = [
  686. { handler: KeydownHandler },
  687. { handler: MenuCommandHandler },
  688. { handler: WebFullScreenHandler },
  689. { handler: VideoPlaybackRateHandler },
  690. { handler: ScriptsEnhanceHandler }
  691. ];
  692. logicHandlers.forEach(({ handler }) => {
  693. for (const key of Object.keys(handler)) {
  694. const method = handler[key];
  695. method instanceof Function ? App[key] = method.bind(App) : App[key] = method;
  696. }
  697. });
  698. App.init();
  699.  
  700. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址