HTML5 视频增强脚本

脚本基于 Violentmonkey 开发,为 HTML5 视频,添加一些通用功能

  1. // ==UserScript==
  2. // @name HTML5 视频增强脚本
  3. // @version 1658069002
  4. // @description 脚本基于 Violentmonkey 开发,为 HTML5 视频,添加一些通用功能
  5. // @author So
  6. // @namespace https://github.com/Git-So/video-userscript
  7. // @homepageURL https://github.com/Git-So/video-userscript
  8. // @supportURL https://github.com/Git-So/video-userscript/issues
  9. // @match http://*/*
  10. // @match https://*/*
  11. // @grant GM_addStyle
  12. // @grant GM_openInTab
  13. // @grant unsafeWindow
  14. // ==/UserScript==
  15.  
  16. var __defProp = Object.defineProperty;
  17. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  18. var __publicField = (obj, key, value) => {
  19. __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  20. return value;
  21. };
  22. (function() {
  23. "use strict";
  24. GM_addStyle(`
  25. @charset "UTF-8";
  26. @keyframes toast-show {
  27. from {
  28. opacity: 0;
  29. }
  30. 25% {
  31. opacity: 1;
  32. }
  33. 75% {
  34. opacity: 1;
  35. }
  36. to {
  37. opacity: 0;
  38. }
  39. }
  40. .sooo--video {
  41. /**
  42. * 动作提示
  43. */
  44. /**
  45. * 关灯影院模式
  46. */
  47. /**
  48. * 视频镜像
  49. */
  50. /**
  51. * 视频解析
  52. */
  53. }
  54. .sooo--video-action-toast {
  55. position: absolute !important;
  56. top: 50%;
  57. left: 50%;
  58. transform: translate(-50%, -50%);
  59. text-align: center;
  60. padding: 10px 15px;
  61. font-size: 18px;
  62. color: whitesmoke;
  63. background-color: rgba(0, 0, 0, 0.555);
  64. z-index: 7777777;
  65. }
  66. .sooo--video-action-toast-animation {
  67. animation: toast-show 1.2s alternate forwards;
  68. }
  69. .sooo--video-movie-mode {
  70. z-index: 99999999 !important;
  71. }
  72. .sooo--video-movie-mode-parent {
  73. z-index: auto !important;
  74. }
  75. .sooo--video-movie-mode-modal {
  76. inset: 0;
  77. width: 100%;
  78. height: 100%;
  79. position: fixed !important;
  80. background: rgba(0, 0, 0, 0.9);
  81. z-index: 55555;
  82. }
  83. .sooo--video-mirror video {
  84. transform: rotateX(0deg) rotateY(180deg);
  85. }
  86. .sooo--video-iframe {
  87. inset: 0;
  88. width: 100%;
  89. height: 100%;
  90. position: absolute !important;
  91. display: block;
  92. z-index: 55555;
  93. border: 0;
  94. } `);
  95. var style = "";
  96. const tagName = {
  97. div: "DIV",
  98. iframe: "IFRAME"
  99. };
  100. function reanimation(func) {
  101. window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
  102. func();
  103. }));
  104. }
  105. function isActiveElementEditable() {
  106. const activeElement = document.activeElement;
  107. if (!activeElement)
  108. return false;
  109. if (activeElement.isContentEditable)
  110. return true;
  111. if ("value" in activeElement)
  112. return true;
  113. return false;
  114. }
  115. function between(value2, min = 0, max = 1) {
  116. if (value2 < min)
  117. return min;
  118. if (value2 > max)
  119. return max;
  120. return value2;
  121. }
  122. function topWindow() {
  123. return unsafeWindow.top;
  124. }
  125. function actionOfAllParent(el, action, level = 0) {
  126. let parent = el.parentElement;
  127. if (!parent)
  128. return el;
  129. const currWindow = parent.ownerDocument.defaultView;
  130. if (parent.tagName == "BODY") {
  131. if (currWindow == currWindow.top)
  132. return el;
  133. const iframeArr = currWindow.parent.document.querySelectorAll("iframe");
  134. for (const iframe of iframeArr) {
  135. if (currWindow != iframe.contentWindow)
  136. continue;
  137. parent = iframe;
  138. break;
  139. }
  140. }
  141. if (level < 1 && action.self)
  142. action.self(el);
  143. if (parent.tagName == tagName.iframe) {
  144. if (action.iframe)
  145. action.iframe(parent);
  146. } else {
  147. if (!action.parent(parent))
  148. return el;
  149. }
  150. return actionOfAllParent(parent, action, level + 1);
  151. }
  152. function actionOfAllSubWindow(action, isIncludeSelf = true, win = topWindow()) {
  153. const iframeArr = win.document.querySelectorAll("iframe");
  154. for (const iframe of iframeArr) {
  155. if (!iframe.contentDocument || !iframe.contentWindow)
  156. continue;
  157. actionOfAllSubWindow(action, true, iframe.contentWindow);
  158. }
  159. if (isIncludeSelf)
  160. action(win);
  161. }
  162. const value = [
  163. {
  164. match: `^https?://www.bilibili.com/video/`,
  165. player: "#bilibili-player .bpx-player-container"
  166. },
  167. {
  168. match: `^https?://haokan.baidu.com/v?`,
  169. player: "#mse .art-video-player"
  170. }
  171. ];
  172. class Config {
  173. constructor() {
  174. __publicField(this, "initConfig", {
  175. video: {
  176. enable: true,
  177. lastElement: null,
  178. isPirate: false
  179. }
  180. });
  181. }
  182. get window() {
  183. return topWindow();
  184. }
  185. get value() {
  186. if (!this.window.UserscriptConfig)
  187. this.window.UserscriptConfig = this.initConfig;
  188. return new Proxy(this.window.UserscriptConfig.video, {});
  189. }
  190. }
  191. const _Video = class {
  192. constructor() {
  193. __publicField(this, "config");
  194. this.config = new Config().value;
  195. }
  196. static get instance() {
  197. if (!_Video._instance) {
  198. _Video._instance = new _Video();
  199. }
  200. return this._instance;
  201. }
  202. set lastElement(el) {
  203. this.config.lastElement = el;
  204. }
  205. get lastElement() {
  206. return this.config.lastElement;
  207. }
  208. rule() {
  209. for (const rule of value) {
  210. const rg = new RegExp(rule.match);
  211. if (location.href.search(rg) > -1)
  212. return rule;
  213. }
  214. return null;
  215. }
  216. static isExistPlayer() {
  217. return !!_Video.instance.player();
  218. }
  219. static isNotExistPlayer() {
  220. return !_Video.isExistPlayer();
  221. }
  222. static isEnable() {
  223. return _Video.instance.config.enable;
  224. }
  225. static isDisable() {
  226. return !_Video.instance.config.enable;
  227. }
  228. getAllVideoElement(doc = document) {
  229. const videoArr = doc.querySelectorAll("video");
  230. let allVideo = [...videoArr];
  231. const iframeArr = doc.querySelectorAll("iframe");
  232. for (const iframe of iframeArr) {
  233. if (!iframe.contentDocument)
  234. continue;
  235. allVideo = [
  236. ...allVideo,
  237. ...this.getAllVideoElement(iframe.contentDocument)
  238. ];
  239. }
  240. return allVideo;
  241. }
  242. element() {
  243. var _a;
  244. const allMedia = this.getAllVideoElement();
  245. for (const media of allMedia) {
  246. if (!media.paused) {
  247. this.config.lastElement = media;
  248. break;
  249. }
  250. }
  251. if (!this.config.lastElement) {
  252. this.config.lastElement = (_a = allMedia[0]) != null ? _a : null;
  253. }
  254. return this.config.lastElement;
  255. }
  256. player(videoElement = this.element()) {
  257. const rule = this.rule();
  258. if (rule)
  259. return document.querySelector(rule.player);
  260. if (!videoElement)
  261. return null;
  262. return actionOfAllParent(videoElement, {
  263. parent: (el) => el.clientHeight == videoElement.clientHeight && el.clientWidth == videoElement.clientWidth
  264. });
  265. }
  266. toast(text) {
  267. const player = this.player();
  268. if (!player)
  269. return;
  270. const className = "sooo--video-action-toast";
  271. const animationClassName = "sooo--video-action-toast-animation";
  272. if (!player.querySelector(`.${className}`)) {
  273. const element = document.createElement("DIV");
  274. element.classList.add(className);
  275. player.append(element);
  276. }
  277. const toast = player.querySelector(`.${className}`);
  278. toast.classList.remove(animationClassName);
  279. toast.innerHTML = "";
  280. toast.append(text);
  281. reanimation(() => {
  282. toast.classList.add(animationClassName);
  283. });
  284. }
  285. };
  286. let Video = _Video;
  287. __publicField(Video, "_instance");
  288. class Action {
  289. constructor() {
  290. __publicField(this, "_name", "");
  291. }
  292. get name() {
  293. return this._name;
  294. }
  295. get video() {
  296. return Video.instance;
  297. }
  298. get media() {
  299. return this.video.element();
  300. }
  301. get player() {
  302. return this.video.player();
  303. }
  304. get window() {
  305. return topWindow();
  306. }
  307. get document() {
  308. return this.window.document;
  309. }
  310. safeAction(action, that = this) {
  311. if (!this.media)
  312. return;
  313. action.apply(that);
  314. }
  315. }
  316. class SwitchAction extends Action {
  317. get isEnable() {
  318. return false;
  319. }
  320. enableAction() {
  321. }
  322. enable() {
  323. this.safeAction(this.enableAction);
  324. this.video.toast(`${this.name}: \u5F00`);
  325. }
  326. disableAction() {
  327. }
  328. disable() {
  329. this.safeAction(this.disableAction);
  330. this.video.toast(`${this.name}: \u5173`);
  331. }
  332. toggle() {
  333. this.isEnable ? this.disable() : this.enable();
  334. }
  335. }
  336. class StepAction extends Action {
  337. constructor() {
  338. super(...arguments);
  339. __publicField(this, "step", 1);
  340. }
  341. setValue(_value, _isStep = true) {
  342. }
  343. add(step = this.step) {
  344. this.setValue(+step);
  345. }
  346. sub(step = this.step) {
  347. this.setValue(-step);
  348. }
  349. }
  350. class Fullscreen extends SwitchAction {
  351. constructor() {
  352. super(...arguments);
  353. __publicField(this, "_name", "\u89C6\u9891\u5168\u5C4F");
  354. }
  355. get isEnable() {
  356. return !!this.document.fullscreenElement;
  357. }
  358. enableAction() {
  359. var _a;
  360. (_a = this.player) == null ? void 0 : _a.requestFullscreen();
  361. }
  362. disableAction() {
  363. this.document.exitFullscreen();
  364. }
  365. }
  366. class PlayState extends SwitchAction {
  367. constructor() {
  368. super(...arguments);
  369. __publicField(this, "_name", "\u89C6\u9891\u64AD\u653E");
  370. }
  371. get isEnable() {
  372. var _a;
  373. return !((_a = this.media) == null ? void 0 : _a.paused);
  374. }
  375. enableAction() {
  376. var _a;
  377. (_a = this.media) == null ? void 0 : _a.play();
  378. }
  379. disableAction() {
  380. var _a;
  381. (_a = this.media) == null ? void 0 : _a.pause();
  382. }
  383. }
  384. class PictureInPicture extends SwitchAction {
  385. constructor() {
  386. super(...arguments);
  387. __publicField(this, "_name", "\u753B\u4E2D\u753B");
  388. }
  389. get isEnable() {
  390. var _a;
  391. return !!((_a = this.media) == null ? void 0 : _a.ownerDocument.pictureInPictureElement);
  392. }
  393. enableAction() {
  394. var _a;
  395. (_a = this.media) == null ? void 0 : _a.requestPictureInPicture();
  396. }
  397. disableAction() {
  398. var _a;
  399. if (!this.isEnable)
  400. return;
  401. (_a = this.media) == null ? void 0 : _a.ownerDocument.exitPictureInPicture();
  402. }
  403. }
  404. class CurrentTime extends StepAction {
  405. constructor() {
  406. super(...arguments);
  407. __publicField(this, "_name", "\u89C6\u9891\u8FDB\u5EA6");
  408. __publicField(this, "step", 10);
  409. }
  410. setValue(value2, isStep = true) {
  411. this.safeAction(() => {
  412. const currentTime = isStep ? this.media.currentTime + value2 : value2;
  413. this.media.currentTime = currentTime;
  414. this.video.toast(`${this.name}: ${value2 < 0 ? "" : "+"}${value2}\u79D2`);
  415. });
  416. }
  417. }
  418. class Volume extends StepAction {
  419. constructor() {
  420. super(...arguments);
  421. __publicField(this, "_name", "\u97F3\u91CF");
  422. __publicField(this, "step", 0.1);
  423. }
  424. setValue(value2, isStep = true) {
  425. this.safeAction(() => {
  426. const volume = isStep ? this.media.volume + value2 : value2;
  427. this.media.volume = between(volume, 0, 1);
  428. this.video.toast(`${this.name}:${this.media.volume * 100 | 0}% `);
  429. });
  430. }
  431. }
  432. class PlaybackRate extends StepAction {
  433. constructor() {
  434. super(...arguments);
  435. __publicField(this, "_name", "\u500D\u6570\u64AD\u653E");
  436. __publicField(this, "step", 1);
  437. __publicField(this, "playbackRate", [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 5]);
  438. __publicField(this, "defaultIdx", 3);
  439. }
  440. get currIdx() {
  441. if (!this.media)
  442. return this.defaultIdx;
  443. const idx = this.playbackRate.indexOf(this.media.playbackRate);
  444. return idx < 0 ? this.defaultIdx : idx;
  445. }
  446. setValue(value2, isStep = true) {
  447. this.safeAction(() => {
  448. value2 = isStep ? this.currIdx + value2 : value2;
  449. const idx = between(value2, 0, this.playbackRate.length - 1);
  450. const rate = this.playbackRate[idx];
  451. this.media.playbackRate = rate;
  452. this.video.toast(`${this.name}: ${rate}x`);
  453. });
  454. }
  455. restart() {
  456. this.setValue(this.defaultIdx, false);
  457. }
  458. }
  459. class MovieMode extends SwitchAction {
  460. constructor() {
  461. super(...arguments);
  462. __publicField(this, "_name", "\u5F71\u9662\u6A21\u5F0F");
  463. __publicField(this, "className", "sooo--video-movie-mode");
  464. __publicField(this, "parentClassName", "sooo--video-movie-mode-parent");
  465. __publicField(this, "modalClassName", "sooo--video-movie-mode-modal");
  466. }
  467. get isEnable() {
  468. var _a;
  469. return !!((_a = this.player) == null ? void 0 : _a.classList.contains(this.className));
  470. }
  471. enableAction() {
  472. const action = (el) => {
  473. el.classList.add(this.className);
  474. el.ownerDocument.body.append((() => {
  475. const modal = el.ownerDocument.createElement("DIV");
  476. modal.className = this.modalClassName;
  477. return modal;
  478. })());
  479. };
  480. actionOfAllParent(this.player, {
  481. parent: (el) => {
  482. el.classList.add(this.parentClassName);
  483. return true;
  484. },
  485. iframe: action,
  486. self: action
  487. });
  488. }
  489. disableAction() {
  490. var _a;
  491. (_a = this.player) == null ? void 0 : _a.classList.remove(this.className);
  492. actionOfAllSubWindow((win) => {
  493. var _a2;
  494. (_a2 = win.document.querySelector(`.${this.modalClassName}`)) == null ? void 0 : _a2.remove();
  495. win.document.querySelectorAll(`.${this.parentClassName}`).forEach((el) => {
  496. el.classList.remove(this.parentClassName);
  497. });
  498. });
  499. }
  500. }
  501. class Mirror extends SwitchAction {
  502. constructor() {
  503. super(...arguments);
  504. __publicField(this, "_name", "\u89C6\u9891\u955C\u50CF");
  505. __publicField(this, "className", "sooo--video-mirror");
  506. }
  507. get isEnable() {
  508. var _a;
  509. return !!((_a = this.player) == null ? void 0 : _a.classList.contains(this.className));
  510. }
  511. enableAction() {
  512. var _a;
  513. (_a = this.player) == null ? void 0 : _a.classList.add(this.className);
  514. }
  515. disableAction() {
  516. var _a;
  517. (_a = this.player) == null ? void 0 : _a.classList.remove(this.className);
  518. }
  519. }
  520. class Loop extends SwitchAction {
  521. constructor() {
  522. super(...arguments);
  523. __publicField(this, "_name", "\u5FAA\u73AF\u64AD\u653E");
  524. }
  525. get isEnable() {
  526. var _a;
  527. return !!((_a = this.media) == null ? void 0 : _a.loop);
  528. }
  529. enableAction() {
  530. this.media.loop = true;
  531. }
  532. disableAction() {
  533. this.media.loop = false;
  534. }
  535. }
  536. class Muted extends SwitchAction {
  537. constructor() {
  538. super(...arguments);
  539. __publicField(this, "_name", "\u89C6\u9891\u9759\u97F3");
  540. }
  541. get isEnable() {
  542. var _a;
  543. return !!((_a = this.media) == null ? void 0 : _a.muted);
  544. }
  545. enableAction() {
  546. this.media.muted = true;
  547. }
  548. disableAction() {
  549. this.media.muted = false;
  550. }
  551. }
  552. class Pirate extends Action {
  553. constructor() {
  554. super(...arguments);
  555. __publicField(this, "_name", "\u89C6\u9891\u89E3\u6790");
  556. __publicField(this, "ruleArr", [
  557. "https://z1.m1907.cn/?jx=",
  558. "https://jsap.attakids.com/?url=",
  559. "https://jx.bozrc.com:4433/player/?url=",
  560. "https://okjx.cc/?url=",
  561. "https://jx.blbo.cc:4433/?url=",
  562. "https://www.yemu.xyz/?url=",
  563. "https://jx.aidouer.net/?url=",
  564. "https://jx.xmflv.com/?url=",
  565. "https://jx.m3u8.tv/jiexi/?url="
  566. ]);
  567. }
  568. open(idx) {
  569. new PlayState().disable();
  570. GM_openInTab(this.ruleArr[between(idx, 0, this.ruleArr.length - 1)] + location.href);
  571. }
  572. }
  573. class ScriptState extends SwitchAction {
  574. constructor() {
  575. super(...arguments);
  576. __publicField(this, "_name", "\u89C6\u9891\u811A\u672C");
  577. }
  578. get isEnable() {
  579. return this.video.config.enable;
  580. }
  581. enableAction() {
  582. this.video.config.enable = true;
  583. }
  584. disableAction() {
  585. this.video.config.enable = false;
  586. }
  587. }
  588. document.addEventListener("keydown", (e) => {
  589. if (isActiveElementEditable() || Video.isNotExistPlayer())
  590. return;
  591. const defer = () => {
  592. e.stopPropagation();
  593. e.stopImmediatePropagation();
  594. e.preventDefault();
  595. };
  596. if (e.shiftKey && e.code == "KeyU") {
  597. new ScriptState().toggle();
  598. }
  599. if (Video.isDisable())
  600. return defer();
  601. let hasAction = true;
  602. switch (true) {
  603. case e.code == "Enter":
  604. new Fullscreen().toggle();
  605. break;
  606. case e.code == "Space":
  607. new PlayState().toggle();
  608. break;
  609. case (e.shiftKey && e.code == "KeyA"):
  610. new CurrentTime().sub();
  611. break;
  612. case (e.shiftKey && e.code == "KeyD"):
  613. new CurrentTime().add();
  614. break;
  615. case (e.shiftKey && e.code == "KeyW"):
  616. new Volume().add();
  617. break;
  618. case (e.shiftKey && e.code == "KeyS"):
  619. new Volume().sub();
  620. break;
  621. case (e.shiftKey && e.code == "KeyZ"):
  622. new PlaybackRate().sub();
  623. break;
  624. case (e.shiftKey && e.code == "KeyX"):
  625. new PlaybackRate().restart();
  626. break;
  627. case (e.shiftKey && e.code == "KeyC"):
  628. new PlaybackRate().add();
  629. break;
  630. case (e.ctrlKey && e.shiftKey && e.code == "BracketRight"):
  631. new PictureInPicture().toggle();
  632. break;
  633. case (e.shiftKey && e.code == "KeyO"):
  634. new MovieMode().toggle();
  635. break;
  636. case (e.shiftKey && e.code == "KeyH"):
  637. new Mirror().toggle();
  638. break;
  639. case (e.shiftKey && e.code == "KeyL"):
  640. new Loop().toggle();
  641. break;
  642. case (e.shiftKey && e.code == "KeyM"):
  643. new Muted().toggle();
  644. break;
  645. case (e.shiftKey && e.code == "Digit1"):
  646. new Pirate().open(1);
  647. break;
  648. case (e.shiftKey && e.code == "Digit2"):
  649. new Pirate().open(2);
  650. break;
  651. case (e.shiftKey && e.code == "Digit3"):
  652. new Pirate().open(3);
  653. break;
  654. case (e.shiftKey && e.code == "Digit4"):
  655. new Pirate().open(4);
  656. break;
  657. case (e.shiftKey && e.code == "Digit5"):
  658. new Pirate().open(5);
  659. break;
  660. case (e.shiftKey && e.code == "Digit6"):
  661. new Pirate().open(6);
  662. break;
  663. case (e.shiftKey && e.code == "Digit7"):
  664. new Pirate().open(7);
  665. break;
  666. case (e.shiftKey && e.code == "Digit8"):
  667. new Pirate().open(8);
  668. break;
  669. case (e.shiftKey && e.code == "Digit9"):
  670. new Pirate().open(9);
  671. break;
  672. default:
  673. hasAction = false;
  674. }
  675. if (!hasAction)
  676. return;
  677. defer();
  678. });
  679. })();

QingJ © 2025

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