// ==UserScript==
// @name HTML5快速鍵hotkeys
// @namespace https://gf.qytechs.cn/zh-TW/users/4839-leadra
// @version 1.1
// @license AGPLv3
// @author jcunews,leadra
// @description HTML5快速鍵控制跳秒+速度+寬高比+截圖
// @description:EN Adds keyboard shortcuts for controlling HTML5 media player. Seek media to N%. Rewind and fast fordward. Change media speed even beyond YouTube's speed limit. Change audio volume to N%. Change video aspect ratio for TV and letterbox content (for widescreen monitors).
// @match https://www.youtube.com/*
// @match https://ani.gamer.com.tw/*
// @grant none
// @run-at document-start
// ==/UserScript==
/*
鍵盤快速鍵:
CTRL+, = 快退 1/30 秒
CTRL+。 = 快進 1/30 秒
CTRL+左鍵 = 快退 30 秒
CTRL+右=前進 秒 30
a = 快退 分鐘 1
d = 分鐘 1
A = 快退秒 99999
D = 快進秒 99999
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` 等)。 兩種類型都區分大小寫。
// 當 SHIFT 修飾符與產生字元的鍵一起使用時,應使用鍵代碼名稱。
// 可以在此處找到關鍵代碼名稱清單:https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values
//修飾符 = “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: "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
},
//速度-
{
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: true,
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);
})();