HTML5 视频增强脚本

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
  });
})();