HTML5 视频增强脚本

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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();
  });
})();