HTML5 视频增强脚本

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

// ==UserScript==
// @name              HTML5 视频增强脚本
// @version           1658069002
// @description       脚本基于 Violentmonkey 开发,为 HTML5 视频,添加一些通用功能
// @author            So
// @namespace         https://github.com/Git-So/video-userscript
// @homepageURL       https://github.com/Git-So/video-userscript
// @supportURL        https://github.com/Git-So/video-userscript/issues
// @match             http://*/*
// @match             https://*/*
// @grant             GM_addStyle
// @grant             GM_openInTab
// @grant             unsafeWindow
// ==/UserScript==

var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  return value;
};
(function() {
  "use strict";
  GM_addStyle(`
@charset "UTF-8";
@keyframes toast-show {
  from {
    opacity: 0;
  }
  25% {
    opacity: 1;
  }
  75% {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
.sooo--video {
  /**
  * 动作提示
  */
  /**
  * 关灯影院模式
  */
  /**
  * 视频镜像
  */
  /**
  * 视频解析
  */
}
.sooo--video-action-toast {
  position: absolute !important;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  padding: 10px 15px;
  font-size: 18px;
  color: whitesmoke;
  background-color: rgba(0, 0, 0, 0.555);
  z-index: 7777777;
}
.sooo--video-action-toast-animation {
  animation: toast-show 1.2s alternate forwards;
}
.sooo--video-movie-mode {
  z-index: 99999999 !important;
}
.sooo--video-movie-mode-parent {
  z-index: auto !important;
}
.sooo--video-movie-mode-modal {
  inset: 0;
  width: 100%;
  height: 100%;
  position: fixed !important;
  background: rgba(0, 0, 0, 0.9);
  z-index: 55555;
}
.sooo--video-mirror video {
  transform: rotateX(0deg) rotateY(180deg);
}
.sooo--video-iframe {
  inset: 0;
  width: 100%;
  height: 100%;
  position: absolute !important;
  display: block;
  z-index: 55555;
  border: 0;
}  `);
  var style = "";
  const tagName = {
    div: "DIV",
    iframe: "IFRAME"
  };
  function reanimation(func) {
    window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
      func();
    }));
  }
  function isActiveElementEditable() {
    const activeElement = document.activeElement;
    if (!activeElement)
      return false;
    if (activeElement.isContentEditable)
      return true;
    if ("value" in activeElement)
      return true;
    return false;
  }
  function between(value2, min = 0, max = 1) {
    if (value2 < min)
      return min;
    if (value2 > max)
      return max;
    return value2;
  }
  function topWindow() {
    return unsafeWindow.top;
  }
  function actionOfAllParent(el, action, level = 0) {
    let parent = el.parentElement;
    if (!parent)
      return el;
    const currWindow = parent.ownerDocument.defaultView;
    if (parent.tagName == "BODY") {
      if (currWindow == currWindow.top)
        return el;
      const iframeArr = currWindow.parent.document.querySelectorAll("iframe");
      for (const iframe of iframeArr) {
        if (currWindow != iframe.contentWindow)
          continue;
        parent = iframe;
        break;
      }
    }
    if (level < 1 && action.self)
      action.self(el);
    if (parent.tagName == tagName.iframe) {
      if (action.iframe)
        action.iframe(parent);
    } else {
      if (!action.parent(parent))
        return el;
    }
    return actionOfAllParent(parent, action, level + 1);
  }
  function actionOfAllSubWindow(action, isIncludeSelf = true, win = topWindow()) {
    const iframeArr = win.document.querySelectorAll("iframe");
    for (const iframe of iframeArr) {
      if (!iframe.contentDocument || !iframe.contentWindow)
        continue;
      actionOfAllSubWindow(action, true, iframe.contentWindow);
    }
    if (isIncludeSelf)
      action(win);
  }
  const value = [
    {
      match: `^https?://www.bilibili.com/video/`,
      player: "#bilibili-player .bpx-player-container"
    },
    {
      match: `^https?://haokan.baidu.com/v?`,
      player: "#mse .art-video-player"
    }
  ];
  class Config {
    constructor() {
      __publicField(this, "initConfig", {
        video: {
          enable: true,
          lastElement: null,
          isPirate: false
        }
      });
    }
    get window() {
      return topWindow();
    }
    get value() {
      if (!this.window.UserscriptConfig)
        this.window.UserscriptConfig = this.initConfig;
      return new Proxy(this.window.UserscriptConfig.video, {});
    }
  }
  const _Video = class {
    constructor() {
      __publicField(this, "config");
      this.config = new Config().value;
    }
    static get instance() {
      if (!_Video._instance) {
        _Video._instance = new _Video();
      }
      return this._instance;
    }
    set lastElement(el) {
      this.config.lastElement = el;
    }
    get lastElement() {
      return this.config.lastElement;
    }
    rule() {
      for (const rule of value) {
        const rg = new RegExp(rule.match);
        if (location.href.search(rg) > -1)
          return rule;
      }
      return null;
    }
    static isExistPlayer() {
      return !!_Video.instance.player();
    }
    static isNotExistPlayer() {
      return !_Video.isExistPlayer();
    }
    static isEnable() {
      return _Video.instance.config.enable;
    }
    static isDisable() {
      return !_Video.instance.config.enable;
    }
    getAllVideoElement(doc = document) {
      const videoArr = doc.querySelectorAll("video");
      let allVideo = [...videoArr];
      const iframeArr = doc.querySelectorAll("iframe");
      for (const iframe of iframeArr) {
        if (!iframe.contentDocument)
          continue;
        allVideo = [
          ...allVideo,
          ...this.getAllVideoElement(iframe.contentDocument)
        ];
      }
      return allVideo;
    }
    element() {
      var _a;
      const allMedia = this.getAllVideoElement();
      for (const media of allMedia) {
        if (!media.paused) {
          this.config.lastElement = media;
          break;
        }
      }
      if (!this.config.lastElement) {
        this.config.lastElement = (_a = allMedia[0]) != null ? _a : null;
      }
      return this.config.lastElement;
    }
    player(videoElement = this.element()) {
      const rule = this.rule();
      if (rule)
        return document.querySelector(rule.player);
      if (!videoElement)
        return null;
      return actionOfAllParent(videoElement, {
        parent: (el) => el.clientHeight == videoElement.clientHeight && el.clientWidth == videoElement.clientWidth
      });
    }
    toast(text) {
      const player = this.player();
      if (!player)
        return;
      const className = "sooo--video-action-toast";
      const animationClassName = "sooo--video-action-toast-animation";
      if (!player.querySelector(`.${className}`)) {
        const element = document.createElement("DIV");
        element.classList.add(className);
        player.append(element);
      }
      const toast = player.querySelector(`.${className}`);
      toast.classList.remove(animationClassName);
      toast.innerHTML = "";
      toast.append(text);
      reanimation(() => {
        toast.classList.add(animationClassName);
      });
    }
  };
  let Video = _Video;
  __publicField(Video, "_instance");
  class Action {
    constructor() {
      __publicField(this, "_name", "");
    }
    get name() {
      return this._name;
    }
    get video() {
      return Video.instance;
    }
    get media() {
      return this.video.element();
    }
    get player() {
      return this.video.player();
    }
    get window() {
      return topWindow();
    }
    get document() {
      return this.window.document;
    }
    safeAction(action, that = this) {
      if (!this.media)
        return;
      action.apply(that);
    }
  }
  class SwitchAction extends Action {
    get isEnable() {
      return false;
    }
    enableAction() {
    }
    enable() {
      this.safeAction(this.enableAction);
      this.video.toast(`${this.name}: \u5F00`);
    }
    disableAction() {
    }
    disable() {
      this.safeAction(this.disableAction);
      this.video.toast(`${this.name}: \u5173`);
    }
    toggle() {
      this.isEnable ? this.disable() : this.enable();
    }
  }
  class StepAction extends Action {
    constructor() {
      super(...arguments);
      __publicField(this, "step", 1);
    }
    setValue(_value, _isStep = true) {
    }
    add(step = this.step) {
      this.setValue(+step);
    }
    sub(step = this.step) {
      this.setValue(-step);
    }
  }
  class Fullscreen extends SwitchAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u89C6\u9891\u5168\u5C4F");
    }
    get isEnable() {
      return !!this.document.fullscreenElement;
    }
    enableAction() {
      var _a;
      (_a = this.player) == null ? void 0 : _a.requestFullscreen();
    }
    disableAction() {
      this.document.exitFullscreen();
    }
  }
  class PlayState extends SwitchAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u89C6\u9891\u64AD\u653E");
    }
    get isEnable() {
      var _a;
      return !((_a = this.media) == null ? void 0 : _a.paused);
    }
    enableAction() {
      var _a;
      (_a = this.media) == null ? void 0 : _a.play();
    }
    disableAction() {
      var _a;
      (_a = this.media) == null ? void 0 : _a.pause();
    }
  }
  class PictureInPicture extends SwitchAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u753B\u4E2D\u753B");
    }
    get isEnable() {
      var _a;
      return !!((_a = this.media) == null ? void 0 : _a.ownerDocument.pictureInPictureElement);
    }
    enableAction() {
      var _a;
      (_a = this.media) == null ? void 0 : _a.requestPictureInPicture();
    }
    disableAction() {
      var _a;
      if (!this.isEnable)
        return;
      (_a = this.media) == null ? void 0 : _a.ownerDocument.exitPictureInPicture();
    }
  }
  class CurrentTime extends StepAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u89C6\u9891\u8FDB\u5EA6");
      __publicField(this, "step", 10);
    }
    setValue(value2, isStep = true) {
      this.safeAction(() => {
        const currentTime = isStep ? this.media.currentTime + value2 : value2;
        this.media.currentTime = currentTime;
        this.video.toast(`${this.name}: ${value2 < 0 ? "" : "+"}${value2}\u79D2`);
      });
    }
  }
  class Volume extends StepAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u97F3\u91CF");
      __publicField(this, "step", 0.1);
    }
    setValue(value2, isStep = true) {
      this.safeAction(() => {
        const volume = isStep ? this.media.volume + value2 : value2;
        this.media.volume = between(volume, 0, 1);
        this.video.toast(`${this.name}:${this.media.volume * 100 | 0}% `);
      });
    }
  }
  class PlaybackRate extends StepAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u500D\u6570\u64AD\u653E");
      __publicField(this, "step", 1);
      __publicField(this, "playbackRate", [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 5]);
      __publicField(this, "defaultIdx", 3);
    }
    get currIdx() {
      if (!this.media)
        return this.defaultIdx;
      const idx = this.playbackRate.indexOf(this.media.playbackRate);
      return idx < 0 ? this.defaultIdx : idx;
    }
    setValue(value2, isStep = true) {
      this.safeAction(() => {
        value2 = isStep ? this.currIdx + value2 : value2;
        const idx = between(value2, 0, this.playbackRate.length - 1);
        const rate = this.playbackRate[idx];
        this.media.playbackRate = rate;
        this.video.toast(`${this.name}: ${rate}x`);
      });
    }
    restart() {
      this.setValue(this.defaultIdx, false);
    }
  }
  class MovieMode extends SwitchAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u5F71\u9662\u6A21\u5F0F");
      __publicField(this, "className", "sooo--video-movie-mode");
      __publicField(this, "parentClassName", "sooo--video-movie-mode-parent");
      __publicField(this, "modalClassName", "sooo--video-movie-mode-modal");
    }
    get isEnable() {
      var _a;
      return !!((_a = this.player) == null ? void 0 : _a.classList.contains(this.className));
    }
    enableAction() {
      const action = (el) => {
        el.classList.add(this.className);
        el.ownerDocument.body.append((() => {
          const modal = el.ownerDocument.createElement("DIV");
          modal.className = this.modalClassName;
          return modal;
        })());
      };
      actionOfAllParent(this.player, {
        parent: (el) => {
          el.classList.add(this.parentClassName);
          return true;
        },
        iframe: action,
        self: action
      });
    }
    disableAction() {
      var _a;
      (_a = this.player) == null ? void 0 : _a.classList.remove(this.className);
      actionOfAllSubWindow((win) => {
        var _a2;
        (_a2 = win.document.querySelector(`.${this.modalClassName}`)) == null ? void 0 : _a2.remove();
        win.document.querySelectorAll(`.${this.parentClassName}`).forEach((el) => {
          el.classList.remove(this.parentClassName);
        });
      });
    }
  }
  class Mirror extends SwitchAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u89C6\u9891\u955C\u50CF");
      __publicField(this, "className", "sooo--video-mirror");
    }
    get isEnable() {
      var _a;
      return !!((_a = this.player) == null ? void 0 : _a.classList.contains(this.className));
    }
    enableAction() {
      var _a;
      (_a = this.player) == null ? void 0 : _a.classList.add(this.className);
    }
    disableAction() {
      var _a;
      (_a = this.player) == null ? void 0 : _a.classList.remove(this.className);
    }
  }
  class Loop extends SwitchAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u5FAA\u73AF\u64AD\u653E");
    }
    get isEnable() {
      var _a;
      return !!((_a = this.media) == null ? void 0 : _a.loop);
    }
    enableAction() {
      this.media.loop = true;
    }
    disableAction() {
      this.media.loop = false;
    }
  }
  class Muted extends SwitchAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u89C6\u9891\u9759\u97F3");
    }
    get isEnable() {
      var _a;
      return !!((_a = this.media) == null ? void 0 : _a.muted);
    }
    enableAction() {
      this.media.muted = true;
    }
    disableAction() {
      this.media.muted = false;
    }
  }
  class Pirate extends Action {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u89C6\u9891\u89E3\u6790");
      __publicField(this, "ruleArr", [
        "https://z1.m1907.cn/?jx=",
        "https://jsap.attakids.com/?url=",
        "https://jx.bozrc.com:4433/player/?url=",
        "https://okjx.cc/?url=",
        "https://jx.blbo.cc:4433/?url=",
        "https://www.yemu.xyz/?url=",
        "https://jx.aidouer.net/?url=",
        "https://jx.xmflv.com/?url=",
        "https://jx.m3u8.tv/jiexi/?url="
      ]);
    }
    open(idx) {
      new PlayState().disable();
      GM_openInTab(this.ruleArr[between(idx, 0, this.ruleArr.length - 1)] + location.href);
    }
  }
  class ScriptState extends SwitchAction {
    constructor() {
      super(...arguments);
      __publicField(this, "_name", "\u89C6\u9891\u811A\u672C");
    }
    get isEnable() {
      return this.video.config.enable;
    }
    enableAction() {
      this.video.config.enable = true;
    }
    disableAction() {
      this.video.config.enable = false;
    }
  }
  document.addEventListener("keydown", (e) => {
    if (isActiveElementEditable() || Video.isNotExistPlayer())
      return;
    const defer = () => {
      e.stopPropagation();
      e.stopImmediatePropagation();
      e.preventDefault();
    };
    if (e.shiftKey && e.code == "KeyU") {
      new ScriptState().toggle();
    }
    if (Video.isDisable())
      return defer();
    let hasAction = true;
    switch (true) {
      case e.code == "Enter":
        new Fullscreen().toggle();
        break;
      case e.code == "Space":
        new PlayState().toggle();
        break;
      case (e.shiftKey && e.code == "KeyA"):
        new CurrentTime().sub();
        break;
      case (e.shiftKey && e.code == "KeyD"):
        new CurrentTime().add();
        break;
      case (e.shiftKey && e.code == "KeyW"):
        new Volume().add();
        break;
      case (e.shiftKey && e.code == "KeyS"):
        new Volume().sub();
        break;
      case (e.shiftKey && e.code == "KeyZ"):
        new PlaybackRate().sub();
        break;
      case (e.shiftKey && e.code == "KeyX"):
        new PlaybackRate().restart();
        break;
      case (e.shiftKey && e.code == "KeyC"):
        new PlaybackRate().add();
        break;
      case (e.ctrlKey && e.shiftKey && e.code == "BracketRight"):
        new PictureInPicture().toggle();
        break;
      case (e.shiftKey && e.code == "KeyO"):
        new MovieMode().toggle();
        break;
      case (e.shiftKey && e.code == "KeyH"):
        new Mirror().toggle();
        break;
      case (e.shiftKey && e.code == "KeyL"):
        new Loop().toggle();
        break;
      case (e.shiftKey && e.code == "KeyM"):
        new Muted().toggle();
        break;
      case (e.shiftKey && e.code == "Digit1"):
        new Pirate().open(1);
        break;
      case (e.shiftKey && e.code == "Digit2"):
        new Pirate().open(2);
        break;
      case (e.shiftKey && e.code == "Digit3"):
        new Pirate().open(3);
        break;
      case (e.shiftKey && e.code == "Digit4"):
        new Pirate().open(4);
        break;
      case (e.shiftKey && e.code == "Digit5"):
        new Pirate().open(5);
        break;
      case (e.shiftKey && e.code == "Digit6"):
        new Pirate().open(6);
        break;
      case (e.shiftKey && e.code == "Digit7"):
        new Pirate().open(7);
        break;
      case (e.shiftKey && e.code == "Digit8"):
        new Pirate().open(8);
        break;
      case (e.shiftKey && e.code == "Digit9"):
        new Pirate().open(9);
        break;
      default:
        hasAction = false;
    }
    if (!hasAction)
      return;
    defer();
  });
})();