HTML5快速鍵hotkeys

HTML5快速鍵控制跳秒+速度+寬高比+截圖

Versione datata 22/02/2024. Vedi la nuova versione l'ultima versione.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name        HTML5快速鍵hotkeys
// @namespace   https://greatest.deepsurf.us/zh-TW/users/4839-leadra
// @version     1.1.3
// @license     AGPLv3
// @author      jcunews,leadra
// @description HTML5快速鍵控制跳秒+速度+寬高比+截圖
// @match       https://www.youtube.com/*
// @match       https://ani.gamer.com.tw/*
!// @match       *://*/*
// @grant       none
// @run-at      document-start
// @icon         https://www.google.com/s2/favicons?domain=youtube.com
// ==/UserScript==

/*
鍵盤快速鍵:
[a] = 後退 秒 1
[d] = 前進 秒 1
[A] = 後退秒 99999
[D] = 前進秒 99999
CTRL+左鍵 = 後退 秒 3
CTRL+右鍵 = 前進 秒 3
CTRL+左鍵 = 後退 秒 30
CTRL+右鍵 = 前進 秒 30
CTRL+ , = 後退 秒 1/30
CTRL+。 = 前進 秒 1/30
CTRL+/ = 前進 分鐘 1.5
0 至 9 = 跳至 0%、10%、20%、...90%
SHIFT+0 至 SHIFT+9 = 跳至 5%、15%、25%、...95%
Alt+1 至 Alt+5 = 將音量改為 20%、40%、60%、80%、100%
[Z] = 速度降低 0.2 倍(預設)
[X] = 速度提高 0.2 倍(預設)
[C] = 速度復原
CTRL+' = 更改預設速度
CTRL+\ = 更改速度倍率

對於寬螢幕視窗:
CTRL+6 = 變更寬螢幕內容的影片寬高比。 修正寬螢幕內容縮小為 4:3 電視格式的問題。
CTRL+7 = 更改內容的影片寬高比。 修正 4:3 內容拉伸為寬螢幕格式的問題。
CTRL+8 = 更改內容的影片寬高比。 修正 4:3 內容拉伸為寬螢幕格式的問題。

對於 4:3 視窗:
CTRL+SHIFT+6 = 變更超寬螢幕內容的影片寬高比。 修正壓縮為 4:3 電視格式的超寬螢幕內容。
CTRL+SHIFT+7 = 縮放 4:3 信箱內容以刪除頂部+底部邊框的一半,同時也刪除一點左側+右側的內容。
                      (這也可用於在寬螢幕視窗上半縮放超寬螢幕內容。 即 CTRL+6 的半縮放。)
CTRL+SHIFT+8 = 變更寬螢幕內容的影片寬高比。 修正壓縮為 4:3 電視格式的寬螢幕內容。

對於任何視窗:
CTRL+9 = 重置影片寬高比
*/

((eleOSD, osdTimer) => {

  //===預設===

  //速度倍率
  var incrementUnit = 0.2;

  //變更播放速率時螢幕右下提示 (OSD) 的持續時間(以毫秒為單位)。 設定為零或更少以停用。
  var osdTimeout = 3000;

 //鍵盤快速鍵。
   // 每個鍵名可以是該鍵產生的字元(例如 'a'、'4'、'*' 等),
   // 例如 'Digit2'、'BracketLeft' 等兩種類型都區分大小寫。
   //修飾符 = "C"、"S"和"A"的任意組合,用於 Ctrl、Shift 和 Alt 鍵。
  var keys = [

    { //0 to 9: 跳到 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: 跳到 5%,15%,25%,...95%
      key: [")", "!", "@", "#", "$", "%", "^", "&", "*", "("], modifiers: "S",
      func: (ele, key, keyIndex) => ele.currentTime = (keyIndex + 0.5) / 10 * ele.duration
    },
    { //Alt+1 至 Alt+5 = 將音量改為 20%、40%、60$、80%、100%
      key: ["1", "2", "3", "4", "5"], modifiers: "A",
      func: (ele, key, keyIndex)  => updAudioVolume(ele, (parseInt(key) * 2) / 10)
    },
    //前進後退
    {
      key: "a", modifiers: "",
      func: (ele, key) => ele.currentTime -= 1
    },
    {
      key: "d", modifiers: "",
      func: (ele, key) => ele.currentTime += 1
    },
    {
      key: "A", modifiers: "S",
      func: (ele, key) => ele.currentTime -= 99999
    },
    {
      key: "D", modifiers: "S",
      func: (ele, key) => ele.currentTime += 99999
    },
    {
      key: "ArrowLeft", modifiers: "",
      func: (ele, key) => ele.currentTime -= 3
    },
    {
      key: "ArrowRight", modifiers: "",
      func: (ele, key) => ele.currentTime += 3
    },
    {
      key: "ArrowLeft", modifiers: "C",
      func: (ele, key) => ele.currentTime -= 30
    },
    {
      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 += 1/30
    },

    {
      key: "/", modifiers: "C",
      func: (ele, key) => ele.currentTime += 90
    },
    //速度-
    {
      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);
      }
    },
    //速度+
    {
      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]: 速度復原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+': 更改預設速度
      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+\: 更改速度倍率
      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.");
      }
    },
    //截圖screenshot
    {
      key: "S", modifiers: "S", videoOnly: false,
      func: (ele, key) => screenshot()
    },

    //對於寬螢幕視窗ctrl+6.7.8
    {
      key: "6", modifiers: "C", videoOnly: true,
      func: (ele, key) => updVideoAspect("scaleX(1.3333)", "Widescreen")
    },
    {
      key: "7", modifiers: "C", videoOnly: true,
      func: (ele, key) => updVideoAspect("scaleY(1.3333)", "Letterbox")
    },
    {
      key: "8", modifiers: "C", videoOnly: true,
      func: (ele, key) => updVideoAspect("scaleX(0.75)", "TV")
    },
    //對於 4:3 視窗CTRL+SHIFT+6.7.8
    {
      key: "Digit6", modifiers: "CS", videoOnly: true,
      func: (ele, key) => updVideoAspect("scaleY(0.7168)", "Ultra Widescreen")
    },
    {
      key: "Digit7", modifiers: "CS", videoOnly: true,
      func: (ele, key) => updVideoAspect("scale(1.1666)", "Letterbox Half-Zoom")
    },
    {
      key: "Digit8", modifiers: "CS", videoOnly: true,
      func: (ele, key) => updVideoAspect("scaleY(0.5625)", "Widescreen On TV")
    },
    //CTRL+9 = 重置影片寬高比
    {
      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 BEGIN ===
  var imageFormat = "jpeg"; //can be one of these: jpeg, png
  //=== CONFIGURATION END ===

 function screenshot(ele, cv) {
    if (ele = document.querySelector("video")) {
      cv = document.createElement("CANVAS");
      if (cv.width = ele.videoWidth) {
        cv.height = ele.videoHeight;
        cv.getContext("2d").drawImage(ele, 0, 0);
        ele = document.createElement("A");
        ele.href = cv.toDataURL("image/" + imageFormat);
        ele.download = (document.title) ;//+ (imageFormat === "jpeg" ? "jpg" : imageFormat)
        ele.style.visibility = "hidden";
        document.body.appendChild(ele).click();
        ele.remove();
        return;
      } else {
        alert("The HTML5 video media has not been loaded yet.");
      }
    } else {
      alert("There is no HTML5 video on this page.");
    }
  };



  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);

})();