Video Userscript

HTML5 视频增强脚本

目前為 2022-07-09 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name              Video Userscript
// @version           1657354152
// @description       HTML5 视频增强脚本
// @author            So
// @namespace         site.sooo.userscript.video
// @match             http://*/*
// @match             https://*/*
// @grant             GM_addStyle
// ==/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: 1.5em;
  color: whitesmoke;
  background-color: rgba(0, 0, 0, 0.555);
  z-index: 9000;
}
.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 {
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  position: fixed !important;
  background: rgba(0, 0, 0, 0.9);
  z-index: 1000000;
}
.sooo--video-mirror video {
  transform: rotateX(0deg) rotateY(180deg);
}  `);
  var style = "";
  const value = [
    {
      match: `^https?://www.bilibili.com/video/`,
      player: "#bilibili-player .bpx-player-container .bpx-player-video-area"
    }
  ];
  class Video {
    rule() {
      for (const rule of value) {
        const rg = new RegExp(rule.match);
        if (location.href.search(rg) > -1)
          return rule;
      }
      return null;
    }
    defaultMedia() {
      var _a;
      const items = document.querySelectorAll("video");
      let media = (_a = items[0]) != null ? _a : null;
      for (const item of items) {
        if (!item.paused)
          break;
        media = item;
      }
      return media;
    }
    defaultPlayer(media = null) {
      let player = media != null ? media : this.defaultMedia();
      if (!player)
        return null;
      return actionByAncestor(player, (parent) => {
        return parent.clientHeight == (player == null ? void 0 : player.clientHeight) && parent.clientWidth == (player == null ? void 0 : player.clientWidth);
      });
    }
    media() {
      const rule = this.rule();
      if (rule) {
        if (rule.media)
          return document.querySelector(rule.media);
        return document.querySelector(`${rule.player} video`);
      }
      return this.defaultMedia();
    }
    player(media = null) {
      const rule = this.rule();
      if (rule)
        return document.querySelector(rule.player);
      return this.defaultPlayer(media);
    }
  }
  function actionByAncestor(element, action) {
    for (let _i = 0; _i < 500; _i++) {
      const parent = element.parentElement;
      if (!parent || parent.tagName == "BODY")
        break;
      if (!action(parent))
        break;
      element = parent;
    }
    return element;
  }
  function reanimation(func) {
    window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
      func();
    }));
  }
  function toast(player, text) {
    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 toast2 = player.querySelector(`.${className}`);
    toast2.classList.remove(animationClassName);
    toast2.innerHTML = "";
    toast2.append(text);
    reanimation(() => {
      toast2.classList.add(animationClassName);
    });
  }
  function isActiveElementEditable() {
    const activeElement = document.activeElement;
    if (!activeElement)
      return false;
    if (activeElement.isContentEditable)
      return true;
    if ("value" in activeElement)
      return true;
    return false;
  }
  function isExistMedia() {
    return !!new Video().media();
  }
  function between(value2, min = 0, max = 1) {
    if (value2 < min)
      return min;
    if (value2 > max)
      return max;
    return value2;
  }
  class Action {
    constructor() {
      __publicField(this, "_name", "");
      __publicField(this, "video", new Video());
      __publicField(this, "_media", null);
      __publicField(this, "_player", null);
    }
    get name() {
      return this._name;
    }
    get media() {
      if (!this._media)
        this._media = this.video.media();
      return this._media;
    }
    get player() {
      if (!this._player)
        this._player = this.video.player(this.media);
      return this._player;
    }
    safeAction(action, that = this) {
      if (!this.media)
        return;
      action.apply(that);
    }
    toast(text) {
      toast(this.player, text);
    }
  }
  class SwitchAction extends Action {
    get isEnable() {
      return false;
    }
    enableAction() {
    }
    enable() {
      this.safeAction(this.enableAction);
      this.toast(`${this.name}: \u5F00`);
    }
    disableAction() {
    }
    disable() {
      this.safeAction(this.disableAction);
      this.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 !!document.fullscreenElement;
    }
    enableAction() {
      var _a;
      (_a = this.player) == null ? void 0 : _a.requestFullscreen();
    }
    disableAction() {
      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() {
      return !!document.pictureInPictureElement;
    }
    enableAction() {
      var _a;
      (_a = this.media) == null ? void 0 : _a.requestPictureInPicture();
    }
    disableAction() {
      if (!this.isEnable)
        return;
      document.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.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.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.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() {
      var _a;
      (_a = this.player) == null ? void 0 : _a.classList.add(this.className);
      document.body.append((() => {
        const modal = document.createElement("DIV");
        modal.className = this.modalClassName;
        return modal;
      })());
      actionByAncestor(this.player, (element) => {
        element.classList.add(this.parentClassName);
        return true;
      });
    }
    disableAction() {
      var _a, _b;
      (_a = this.player) == null ? void 0 : _a.classList.remove(this.className);
      (_b = document.querySelector(`.${this.modalClassName}`)) == null ? void 0 : _b.remove();
      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;
    }
  }
  document.addEventListener("keydown", (e) => {
    if (isActiveElementEditable() || !isExistMedia())
      return;
    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;
      default:
        hasAction = false;
    }
    if (!hasAction)
      return;
    e.stopPropagation();
    e.stopImmediatePropagation();
    e.preventDefault();
  });
})();