Video Userscript

HTML5 视频增强脚本

目前为 2022-07-09 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Video Userscript
  3. // @version 1657354152
  4. // @description HTML5 视频增强脚本
  5. // @author So
  6. // @namespace site.sooo.userscript.video
  7. // @match http://*/*
  8. // @match https://*/*
  9. // @grant GM_addStyle
  10. // ==/UserScript==
  11.  
  12. var __defProp = Object.defineProperty;
  13. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  14. var __publicField = (obj, key, value) => {
  15. __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  16. return value;
  17. };
  18. (function() {
  19. "use strict";
  20. GM_addStyle(`
  21. @charset "UTF-8";
  22. @keyframes toast-show {
  23. from {
  24. opacity: 0;
  25. }
  26. 25% {
  27. opacity: 1;
  28. }
  29. 75% {
  30. opacity: 1;
  31. }
  32. to {
  33. opacity: 0;
  34. }
  35. }
  36. .sooo--video {
  37. /**
  38. * 动作提示
  39. */
  40. /**
  41. * 关灯影院模式
  42. */
  43. /**
  44. * 视频镜像
  45. */
  46. }
  47. .sooo--video-action-toast {
  48. position: absolute !important;
  49. top: 50%;
  50. left: 50%;
  51. transform: translate(-50%, -50%);
  52. text-align: center;
  53. padding: 10px 15px;
  54. font-size: 1.5em;
  55. color: whitesmoke;
  56. background-color: rgba(0, 0, 0, 0.555);
  57. z-index: 9000;
  58. }
  59. .sooo--video-action-toast-animation {
  60. animation: toast-show 1.2s alternate forwards;
  61. }
  62. .sooo--video-movie-mode {
  63. z-index: 99999999 !important;
  64. }
  65. .sooo--video-movie-mode-parent {
  66. z-index: auto !important;
  67. }
  68. .sooo--video-movie-mode-modal {
  69. top: 0;
  70. left: 0;
  71. width: 100%;
  72. height: 100%;
  73. position: fixed !important;
  74. background: rgba(0, 0, 0, 0.9);
  75. z-index: 1000000;
  76. }
  77. .sooo--video-mirror video {
  78. transform: rotateX(0deg) rotateY(180deg);
  79. } `);
  80. var style = "";
  81. const value = [
  82. {
  83. match: `^https?://www.bilibili.com/video/`,
  84. player: "#bilibili-player .bpx-player-container .bpx-player-video-area"
  85. }
  86. ];
  87. class Video {
  88. rule() {
  89. for (const rule of value) {
  90. const rg = new RegExp(rule.match);
  91. if (location.href.search(rg) > -1)
  92. return rule;
  93. }
  94. return null;
  95. }
  96. defaultMedia() {
  97. var _a;
  98. const items = document.querySelectorAll("video");
  99. let media = (_a = items[0]) != null ? _a : null;
  100. for (const item of items) {
  101. if (!item.paused)
  102. break;
  103. media = item;
  104. }
  105. return media;
  106. }
  107. defaultPlayer(media = null) {
  108. let player = media != null ? media : this.defaultMedia();
  109. if (!player)
  110. return null;
  111. return actionByAncestor(player, (parent) => {
  112. return parent.clientHeight == (player == null ? void 0 : player.clientHeight) && parent.clientWidth == (player == null ? void 0 : player.clientWidth);
  113. });
  114. }
  115. media() {
  116. const rule = this.rule();
  117. if (rule) {
  118. if (rule.media)
  119. return document.querySelector(rule.media);
  120. return document.querySelector(`${rule.player} video`);
  121. }
  122. return this.defaultMedia();
  123. }
  124. player(media = null) {
  125. const rule = this.rule();
  126. if (rule)
  127. return document.querySelector(rule.player);
  128. return this.defaultPlayer(media);
  129. }
  130. }
  131. function actionByAncestor(element, action) {
  132. for (let _i = 0; _i < 500; _i++) {
  133. const parent = element.parentElement;
  134. if (!parent || parent.tagName == "BODY")
  135. break;
  136. if (!action(parent))
  137. break;
  138. element = parent;
  139. }
  140. return element;
  141. }
  142. function reanimation(func) {
  143. window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
  144. func();
  145. }));
  146. }
  147. function toast(player, text) {
  148. if (!player)
  149. return;
  150. const className = "sooo--video-action-toast";
  151. const animationClassName = "sooo--video-action-toast-animation";
  152. if (!player.querySelector(`.${className}`)) {
  153. const element = document.createElement("DIV");
  154. element.classList.add(className);
  155. player.append(element);
  156. }
  157. const toast2 = player.querySelector(`.${className}`);
  158. toast2.classList.remove(animationClassName);
  159. toast2.innerHTML = "";
  160. toast2.append(text);
  161. reanimation(() => {
  162. toast2.classList.add(animationClassName);
  163. });
  164. }
  165. function isActiveElementEditable() {
  166. const activeElement = document.activeElement;
  167. if (!activeElement)
  168. return false;
  169. if (activeElement.isContentEditable)
  170. return true;
  171. if ("value" in activeElement)
  172. return true;
  173. return false;
  174. }
  175. function isExistMedia() {
  176. return !!new Video().media();
  177. }
  178. function between(value2, min = 0, max = 1) {
  179. if (value2 < min)
  180. return min;
  181. if (value2 > max)
  182. return max;
  183. return value2;
  184. }
  185. class Action {
  186. constructor() {
  187. __publicField(this, "_name", "");
  188. __publicField(this, "video", new Video());
  189. __publicField(this, "_media", null);
  190. __publicField(this, "_player", null);
  191. }
  192. get name() {
  193. return this._name;
  194. }
  195. get media() {
  196. if (!this._media)
  197. this._media = this.video.media();
  198. return this._media;
  199. }
  200. get player() {
  201. if (!this._player)
  202. this._player = this.video.player(this.media);
  203. return this._player;
  204. }
  205. safeAction(action, that = this) {
  206. if (!this.media)
  207. return;
  208. action.apply(that);
  209. }
  210. toast(text) {
  211. toast(this.player, text);
  212. }
  213. }
  214. class SwitchAction extends Action {
  215. get isEnable() {
  216. return false;
  217. }
  218. enableAction() {
  219. }
  220. enable() {
  221. this.safeAction(this.enableAction);
  222. this.toast(`${this.name}: \u5F00`);
  223. }
  224. disableAction() {
  225. }
  226. disable() {
  227. this.safeAction(this.disableAction);
  228. this.toast(`${this.name}: \u5173`);
  229. }
  230. toggle() {
  231. this.isEnable ? this.disable() : this.enable();
  232. }
  233. }
  234. class StepAction extends Action {
  235. constructor() {
  236. super(...arguments);
  237. __publicField(this, "step", 1);
  238. }
  239. setValue(_value, _isStep = true) {
  240. }
  241. add(step = this.step) {
  242. this.setValue(+step);
  243. }
  244. sub(step = this.step) {
  245. this.setValue(-step);
  246. }
  247. }
  248. class Fullscreen extends SwitchAction {
  249. constructor() {
  250. super(...arguments);
  251. __publicField(this, "_name", "\u89C6\u9891\u5168\u5C4F");
  252. }
  253. get isEnable() {
  254. return !!document.fullscreenElement;
  255. }
  256. enableAction() {
  257. var _a;
  258. (_a = this.player) == null ? void 0 : _a.requestFullscreen();
  259. }
  260. disableAction() {
  261. document.exitFullscreen();
  262. }
  263. }
  264. class PlayState extends SwitchAction {
  265. constructor() {
  266. super(...arguments);
  267. __publicField(this, "_name", "\u89C6\u9891\u64AD\u653E");
  268. }
  269. get isEnable() {
  270. var _a;
  271. return !((_a = this.media) == null ? void 0 : _a.paused);
  272. }
  273. enableAction() {
  274. var _a;
  275. (_a = this.media) == null ? void 0 : _a.play();
  276. }
  277. disableAction() {
  278. var _a;
  279. (_a = this.media) == null ? void 0 : _a.pause();
  280. }
  281. }
  282. class PictureInPicture extends SwitchAction {
  283. constructor() {
  284. super(...arguments);
  285. __publicField(this, "_name", "\u753B\u4E2D\u753B");
  286. }
  287. get isEnable() {
  288. return !!document.pictureInPictureElement;
  289. }
  290. enableAction() {
  291. var _a;
  292. (_a = this.media) == null ? void 0 : _a.requestPictureInPicture();
  293. }
  294. disableAction() {
  295. if (!this.isEnable)
  296. return;
  297. document.exitPictureInPicture();
  298. }
  299. }
  300. class CurrentTime extends StepAction {
  301. constructor() {
  302. super(...arguments);
  303. __publicField(this, "_name", "\u89C6\u9891\u8FDB\u5EA6");
  304. __publicField(this, "step", 10);
  305. }
  306. setValue(value2, isStep = true) {
  307. this.safeAction(() => {
  308. const currentTime = isStep ? this.media.currentTime + value2 : value2;
  309. this.media.currentTime = currentTime;
  310. this.toast(`${this.name}: ${value2 < 0 ? "" : "+"}${value2}\u79D2`);
  311. });
  312. }
  313. }
  314. class Volume extends StepAction {
  315. constructor() {
  316. super(...arguments);
  317. __publicField(this, "_name", "\u97F3\u91CF");
  318. __publicField(this, "step", 0.1);
  319. }
  320. setValue(value2, isStep = true) {
  321. this.safeAction(() => {
  322. const volume = isStep ? this.media.volume + value2 : value2;
  323. this.media.volume = between(volume, 0, 1);
  324. this.toast(`${this.name}:${this.media.volume * 100 | 0}% `);
  325. });
  326. }
  327. }
  328. class PlaybackRate extends StepAction {
  329. constructor() {
  330. super(...arguments);
  331. __publicField(this, "_name", "\u500D\u6570\u64AD\u653E");
  332. __publicField(this, "step", 1);
  333. __publicField(this, "playbackRate", [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 5]);
  334. __publicField(this, "defaultIdx", 3);
  335. }
  336. get currIdx() {
  337. if (!this.media)
  338. return this.defaultIdx;
  339. const idx = this.playbackRate.indexOf(this.media.playbackRate);
  340. return idx < 0 ? this.defaultIdx : idx;
  341. }
  342. setValue(value2, isStep = true) {
  343. this.safeAction(() => {
  344. value2 = isStep ? this.currIdx + value2 : value2;
  345. const idx = between(value2, 0, this.playbackRate.length - 1);
  346. const rate = this.playbackRate[idx];
  347. this.media.playbackRate = rate;
  348. this.toast(`${this.name}: ${rate}x`);
  349. });
  350. }
  351. restart() {
  352. this.setValue(this.defaultIdx, false);
  353. }
  354. }
  355. class MovieMode extends SwitchAction {
  356. constructor() {
  357. super(...arguments);
  358. __publicField(this, "_name", "\u5F71\u9662\u6A21\u5F0F");
  359. __publicField(this, "className", "sooo--video-movie-mode");
  360. __publicField(this, "parentClassName", "sooo--video-movie-mode-parent");
  361. __publicField(this, "modalClassName", "sooo--video-movie-mode-modal");
  362. }
  363. get isEnable() {
  364. var _a;
  365. return !!((_a = this.player) == null ? void 0 : _a.classList.contains(this.className));
  366. }
  367. enableAction() {
  368. var _a;
  369. (_a = this.player) == null ? void 0 : _a.classList.add(this.className);
  370. document.body.append((() => {
  371. const modal = document.createElement("DIV");
  372. modal.className = this.modalClassName;
  373. return modal;
  374. })());
  375. actionByAncestor(this.player, (element) => {
  376. element.classList.add(this.parentClassName);
  377. return true;
  378. });
  379. }
  380. disableAction() {
  381. var _a, _b;
  382. (_a = this.player) == null ? void 0 : _a.classList.remove(this.className);
  383. (_b = document.querySelector(`.${this.modalClassName}`)) == null ? void 0 : _b.remove();
  384. document.querySelectorAll(`.${this.parentClassName}`).forEach((el) => {
  385. el.classList.remove(this.parentClassName);
  386. });
  387. }
  388. }
  389. class Mirror extends SwitchAction {
  390. constructor() {
  391. super(...arguments);
  392. __publicField(this, "_name", "\u89C6\u9891\u955C\u50CF");
  393. __publicField(this, "className", "sooo--video-mirror");
  394. }
  395. get isEnable() {
  396. var _a;
  397. return !!((_a = this.player) == null ? void 0 : _a.classList.contains(this.className));
  398. }
  399. enableAction() {
  400. var _a;
  401. (_a = this.player) == null ? void 0 : _a.classList.add(this.className);
  402. }
  403. disableAction() {
  404. var _a;
  405. (_a = this.player) == null ? void 0 : _a.classList.remove(this.className);
  406. }
  407. }
  408. class Loop extends SwitchAction {
  409. constructor() {
  410. super(...arguments);
  411. __publicField(this, "_name", "\u5FAA\u73AF\u64AD\u653E");
  412. }
  413. get isEnable() {
  414. var _a;
  415. return !!((_a = this.media) == null ? void 0 : _a.loop);
  416. }
  417. enableAction() {
  418. this.media.loop = true;
  419. }
  420. disableAction() {
  421. this.media.loop = false;
  422. }
  423. }
  424. document.addEventListener("keydown", (e) => {
  425. if (isActiveElementEditable() || !isExistMedia())
  426. return;
  427. let hasAction = true;
  428. switch (true) {
  429. case e.code == "Enter":
  430. new Fullscreen().toggle();
  431. break;
  432. case e.code == "Space":
  433. new PlayState().toggle();
  434. break;
  435. case (e.shiftKey && e.code == "KeyA"):
  436. new CurrentTime().sub();
  437. break;
  438. case (e.shiftKey && e.code == "KeyD"):
  439. new CurrentTime().add();
  440. break;
  441. case (e.shiftKey && e.code == "KeyW"):
  442. new Volume().add();
  443. break;
  444. case (e.shiftKey && e.code == "KeyS"):
  445. new Volume().sub();
  446. break;
  447. case (e.shiftKey && e.code == "KeyZ"):
  448. new PlaybackRate().sub();
  449. break;
  450. case (e.shiftKey && e.code == "KeyX"):
  451. new PlaybackRate().restart();
  452. break;
  453. case (e.shiftKey && e.code == "KeyC"):
  454. new PlaybackRate().add();
  455. break;
  456. case (e.ctrlKey && e.shiftKey && e.code == "BracketRight"):
  457. new PictureInPicture().toggle();
  458. break;
  459. case (e.shiftKey && e.code == "KeyO"):
  460. new MovieMode().toggle();
  461. break;
  462. case (e.shiftKey && e.code == "KeyH"):
  463. new Mirror().toggle();
  464. break;
  465. case (e.shiftKey && e.code == "KeyL"):
  466. new Loop().toggle();
  467. break;
  468. default:
  469. hasAction = false;
  470. }
  471. if (!hasAction)
  472. return;
  473. e.stopPropagation();
  474. e.stopImmediatePropagation();
  475. e.preventDefault();
  476. });
  477. })();

QingJ © 2025

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