HTML5 Audio/Video Keyboard Shortcuts With OSD(L改)

HTML5快速鍵控制跳秒+速度

2024-02-20 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        HTML5 Audio/Video Keyboard Shortcuts With OSD(L改)
// @name:zh-TW  HTML5跳秒+速度
// @namespace   https://greatest.deepsurf.us/zh-TW/users/4839-leadra
// @version     1.0.1
// @license     AGPLv3
// @author      jcunews,leadra
// @description HTML5快速鍵控制跳秒+速度
// @description:zh-TW HTML5快速鍵控制跳秒+速度
// @description:EN Adds keyboard shortcuts for controlling HTML5 media player (audio/video) with OSD support. Seek media to 0%, 5%, 10%, ..., or 95%. Rewind and fast fordward. Change media speed even beyond YouTube's speed limit. Change audio volume to 20%, 40%, 60%, 80%, or 100%. Change video aspect ratio for TV and letterbox content (for widescreen monitors).
// @match       *://www.youtube.com/*
// @grant       none
// @run-at      document-start
// ==/UserScript==

/*
Notes:

- Some shortcuts won't work on non US keyboards. Non US keyboard users will need to manually edit the keys in the script.
- In YouTube, if the video speed is below 0.25x or above 2x, the YouTube setting display will be capped to 0.1x or 2x.
- Web browser video speeds: Firefox = 0.25 to 5.0; Chrome = 0.1 to 16.0.


Keyboard Shortcuts:

CTRL+,             = Rewind sec 1/30th
CTRL+.             = forward sec 1/30th
CTRL+LEFT         = Rewind sec 30
CTRL+RIGHT        = forward sec 30
a         = Rewind minute 1
d         = forward minute 1
A         = Rewind sec 99999
D         = forward sec 99999
CTRL+/             = forward minutes 1.5
0 to 9             = Seek media to 0%, 10%, 20%,...90%
SHIFT+0 to SHIFT+9 = Seek media to 5%, 15%, 25%,...95%
Alt+1 to Alt+5   = Change audio volume to 20%, 40%, 60$, 80%, 100%
[Z]             = Decrease speed by 0.2x (by default)
[X]             = Increase speed by 0.2x (by default)
[C]             = Reset  speed
CTRL+'             = Change custom speed
CTRL+\             = Change unit of speed increment/decrement

For Widescreen Video Viewport:
CTRL+6             = Change video aspect ratio for widescreen content. Fix widescreen content shrunk to 4:3 TV format.
CTRL+7             = Change video aspect ratio for letterbox content. Fix 4:3 letterbox content stretched to widescreen format.
CTRL+8             = Change video aspect ratio for TV content. Fix 4:3 TV content stretched to widescreen format.

For 4:3 TV Video Viewport:
CTRL+SHIFT+6       = Change video aspect ratio for ultra widescreen content. Fix ultra widescreen content compressed into 4:3 TV format.
CTRL+SHIFT+7       = Zoom 4:3 letterbox content to remove half of top+bottom borders, but also remove left+right content a little.
                     This can also be used to half-zoom ultra widescreen content on widescreen viewport. i.e. half-zoom of CTRL+6.
CTRL+SHIFT+8       = Change video aspect ratio for widescreen content. Fix widescreen content compressed into 4:3 TV format.

For Any Video Viewport:
CTRL+9             = Reset video aspect ratio
*/


((eleOSD, osdTimer) => {

  //=== CONFIGURATION BEGIN ===

  //Video speed increment/decrement unit.
  var incrementUnit = 0.2;

  //Duration (in milliseconds) to display On Screen Display (OSD) when changing playback rate. Set to zero or less to disable.
  var osdTimeout = 3000;

  //Keyboard shortcuts.
  //key = Key name. String type if single shortcut, or array of string if multiple shortcut (for single function multiple shortcuts).
  //      Each key name can either be the character which is produced by the key (e.g. `a`, `4`, `*`, etc.),
  //      or the code name for the key (e.g. `Digit2`, `BracketLeft`, etc.). Both types are case sensitive.
  //      When SHIFT modifier is used with keys which produces a character, key code name should be used.
  //      A list of key code names can be found here: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values
  //modifiers = Any combinations of "C", "S", and "A", for Ctrl, Shift, and Alt keys.
  var keys = [

    { //0 to 9: seek media to 0%,10%,20%,...90%
      key: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], modifiers: "",
      func: (ele, key, keyIndex) => ele.currentTime = keyIndex / 10 * ele.duration
    },
    { //shift+0 to shift+9: seek media to 5%,15%,25%,...95%
      key: [")", "!", "@", "#", "$", "%", "^", "&", "*", "("], modifiers: "S",
      func: (ele, key, keyIndex) => ele.currentTime = (keyIndex + 0.5) / 10 * ele.duration
    },
    { //Alt+1 to Alt+5: set audio volume to 20%,40%,60%,80%,100%
      key: ["1", "2", "3", "4", "5"], modifiers: "A",
      func: (ele, key, keyIndex)  => updAudioVolume(ele, (parseInt(key) * 2) / 10)
    },
    //Rewind and fordward
    {
      key: "a", modifiers: "S",
      func: (ele, key) => ele.currentTime -= 1
    },
    {
      key: "A", modifiers: "S",
      func: (ele, key) => ele.currentTime -= 99999
    },
    {
      key: "ArrowLeft", modifiers: "C",
      func: (ele, key) => ele.currentTime -= 30
    },
    {
      key: ",", modifiers: "C",
      func: (ele, key) => ele.currentTime -= 1/30
    },
    {
      key: "d", modifiers: "",
      func: (ele, key) => ele.currentTime += 1
    },
    {
      key: "D", modifiers: "S",
      func: (ele, key) => ele.currentTime += 99999
    },
    {
      key: "ArrowRight", modifiers: "C",
      func: (ele, key) => ele.currentTime += 30
    },
    {
      key: ".", modifiers: "C",
      func: (ele, key) => ele.currentTime += 1/30
    },

    {
      key: "/", modifiers: "C",
      func: (ele, key) => ele.currentTime += 90
    },
    //[Z]: decrease speed
    {
      key: "z", modifiers: "",
      func: (ele, key) => {
        key = ele.playbackRate - incrementUnit;
        if (key < 0.1) {
          key = 0.1;
        } else if ((key < 1) && (ele.playbackRate > 1)) key = 1;
        updVideoSpeed(ele, key);
      }
    },
    {
      key: "-", modifiers: "",
      func: (ele, key) => {
        key = ele.playbackRate - incrementUnit;
        if (key < 0.1) {
          key = 0.1;
        } else if ((key < 1) && (ele.playbackRate > 1)) key = 1;
        updVideoSpeed(ele, key);
      }
    },
    //[X]: increase speed
    {
      key: "x", modifiers: "",
      func: (ele, key) => {
        key = ele.playbackRate + incrementUnit;
        if (key > 16) {
          key = 16;
        } else if ((key > 1) && (ele.playbackRate < 1)) key = 1;
        updVideoSpeed(ele, key);
      }
    },
    {
      key: "+", modifiers: "",
      func: (ele, key) => {
        key = ele.playbackRate + incrementUnit;
        if (key > 16) {
          key = 16;
        } else if ((key > 1) && (ele.playbackRate < 1)) key = 1;
        updVideoSpeed(ele, key);
      }
    },
    //[C]: reset speed to 1x
    {
      key: "c", modifiers: "",
      func: (ele, key) => {updVideoSpeed(ele, 1)
                          }
    },
    {
      key: "Z", modifiers: "S",
      func: (ele, key) => {updVideoSpeed(ele, 1)
                          }

    },
    {
      key: "*", modifiers: "",
      func: (ele, key) => {updVideoSpeed(ele, 1)
                          }

    },
    { //ctrl+': use custom speed
      key: "'", modifiers: "C",
      func: (ele, key) => {
        if ((key = prompt("Enter media speed from 0.1 to 16 (inclusive).\ne.g.: 1 = Normal, 0.5 = Half, 2 = Double, 3 = Triple, etc.", ele.playbackRate)) === null) return;
        if (isNaN(key = parseFloat(key.trim()))) {
          alert("Input must be a number.");
          return;
        }
        updVideoSpeed(ele, (key = parseFloat(key.toFixed(1))) < 0.1 ? 0.1 : (key > 16 ? 16 : key));
      }
    },
    { //ctrl+\: change unit of speed increment/decrement
      key: "\\", modifiers: "C",
      func: (ele, key) => {
        if ((key = prompt("Enter unit of media speed increment/decrement from 0.1 to 4 (inclusive).", incrementUnit)) === null) return;
        if (!isNaN(key = parseFloat(key.trim()))) {
          incrementUnit = (key = parseFloat(key.toFixed(1))) < 0.1 ? 0.1 : (key > 4 ? 4 : key);
        } else alert("Input must be a number.");
      }
    },
    { //ctrl+6: Widescreen aspect ratio
      key: "6", modifiers: "C", videoOnly: true,
      func: (ele, key) => updVideoAspect("scaleX(1.3333)", "Widescreen")
    },
    { //ctrl+7: Letterbox aspect ratio
      key: "7", modifiers: "C", videoOnly: true,
      func: (ele, key) => updVideoAspect("scaleY(1.3333)", "Letterbox")
    },
    { //ctrl+8: TV aspect ratio
      key: "8", modifiers: "C", videoOnly: true,
      func: (ele, key) => updVideoAspect("scaleX(0.75)", "TV")
    },
    { //ctrl+shift+6: Ultra widescreen aspect ratio
      key: "Digit6", modifiers: "CS", videoOnly: true,
      func: (ele, key) => updVideoAspect("scaleY(0.7168)", "Ultra Widescreen")
    },
    { //ctrl+shift+7: Half-zoom letterbox
      key: "Digit7", modifiers: "CS", videoOnly: true,
      func: (ele, key) => updVideoAspect("scale(1.1666)", "Letterbox Half-Zoom")
    },
    { //ctrl+shift+8: Widescreen on TV
      key: "Digit8", modifiers: "CS", videoOnly: true,
      func: (ele, key) => updVideoAspect("scaleY(0.5625)", "Widescreen On TV")
    },
    { //ctrl+9: reset video aspect ratio
      key: "9", modifiers: "C", videoOnly: true,
      func: (ele, key) => updVideoAspect("", "Reset")
    }
  ];
  keys.forEach((k, s, m) => {
    s = k.modifiers.toUpperCase();
    k.modifiers = {ctrl: s.includes("C"), shift: s.includes("S"), alt: s.includes("A")};
  });

  //=== CONFIGURATION END ===

  function showOSD(s) {
    if (osdTimeout < 0) return;
    if (eleOSD) {
      eleOSD.textContent = s;
    } else {
      eleOSD = document.createElement("DIV");
      eleOSD.style.cssText = "position:fixed;z-index:999999999;right:.5rem;bottom:.5rem;margin:0;padding:.2rem .5rem .1rem .5rem;width:auto;height:auto;font:normal 16pt/normal sans-serif;background:#444;color:#fff";
      eleOSD.textContent = s;
      document.body.appendChild(eleOSD);
    }
    clearTimeout(osdTimer);
    osdTimer = setTimeout(() => {
      eleOSD.remove();
      eleOSD = null;
    }, osdTimeout);
  }

  function stopEvent(ev) {
    ev.preventDefault();
    ev.stopPropagation();
    ev.stopImmediatePropagation();
  }

  function updVideoSpeed(ele, spd, e) {
    if ((location.hostname === "www.youtube.com") && (e = ele.parentNode.parentNode).setPlaybackRate && (spd >= 0.25) && (spd <= 2)) {
      e.setPlaybackRate(spd = parseFloat(spd.toFixed(1)));
    } else ele.playbackRate = spd = parseFloat(spd.toFixed(1));
    showOSD("Speed " + spd + "x");
  }

  function updVideoAspect(asp, label, s) {
    if (!(s = document.getElementById("vidAspOvr"))) document.body.appendChild(s = document.createElement("STYLE")).id = "vidAspOvr";
    s.innerHTML = asp ? `video{transform:${asp}!important}` : "";
    showOSD("Ratio: " + label);
  }

  function updAudioVolume(ele, vol, e) {
    if ((location.hostname === "www.youtube.com") && (e = ele.parentNode.parentNode).setVolume) {
      e.setVolume(vol * 100);
    } else ele.volume = vol;
    showOSD("Audio " + (vol * 100) + "%");
  }

  function isVisible(ele) {
    while (ele && ele.tagName) {
      if (getComputedStyle(ele).display === "none") return false;
      ele = ele.parentNode
    }
    return true
  }

  incrementUnit = parseFloat((incrementUnit < 0.1 ? 0.1 : (incrementUnit > 1 ? 1 : incrementUnit)).toFixed(1));
  addEventListener("keydown", function(ev, ele) {
    if ((!(ele = document.activeElement) || !((ele.contentEditable === "true") || ["BUTTON", "INPUT", "SELECT", "TEXTAREA"].includes(ele.tagName))) && (ele = document.querySelector("video,audio"))) {
      keys.some((k, a, i) => {
        a = Array.isArray(k.key);
        if (
          ((!a && ((k.key === ev.key) || (k.key === ev.code))) || (a && (((i = k.key.indexOf(ev.key)) >= 0) || ((i = k.key.indexOf(ev.code)) >= 0)))) &&
          (k.modifiers.ctrl === ev.ctrlKey) && (k.modifiers.shift === ev.shiftKey) && (k.modifiers.alt === ev.altKey) &&
          (!k.videoOnly || (ele.tagName === "VIDEO")) && (isVisible(ele) || (ele.tagName === "AUDIO"))
        ) {
          stopEvent(ev);
          k.func(ele, ev.key, a ? i : null);
          return true;
        }
      });
    }
  }, true);

})();