HTML5快速鍵hotkeys

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

  1. // ==UserScript==
  2. // @name HTML5快速鍵hotkeys
  3. // @namespace https://gf.qytechs.cn/zh-TW/users/4839-leadra
  4. // @version 1.3.1
  5. // @license AGPLv3
  6. // @author jcunews
  7. // @description HTML5快速鍵控制跳秒+速度+寬高比+截圖
  8. // @match https://www.youtube.com/*
  9. // @match https://ani.gamer.com.tw/*
  10. // @match https://web.telegram.org/k/*
  11. // @grant none
  12. // @run-at document-start
  13. // @icon https://www.youtube.com/favicon.ico
  14. // ==/UserScript==
  15. // @match *://*/*
  16. /*
  17. 鍵盤快速鍵(大小寫通用):
  18. [A] = 後退 秒 1
  19. [D] = 前進 秒 1
  20. [←]左鍵 = 後退 秒 3
  21. [→]右鍵 = 前進 秒 3
  22. CTRL+[←]左鍵 = 後退 秒 30
  23. CTRL+[→]右鍵 = 前進 秒 30
  24. SHIFT+[←]左鍵← = 後退 秒 60
  25. SHIFT+[→]右鍵→ = 前進 秒 60
  26. SHIFT+[A] = 後退 秒 30
  27. SHIFT+[D] = 前進 秒 30
  28. SHIFT+Alt+[A] = 後退 秒 99999
  29. SHIFT+Alt+[D] = 前進 秒 99999
  30. CTRL+[,] = 後退 秒 1/30
  31. CTRL+[.] = 前進 秒 1/30
  32. SHIFT+[0] ~ [9] = 跳至 5%、15%、25%...95%
  33. Alt+[1] ~ [0] = 將音量改為 10%、20%...90%、0%
  34. Alt+[`] = 將音量改為 5%
  35. [-],[Z] = 速度 -0.2 倍(預設)
  36. [+],[X] = 速度 +0.2 倍(預設)
  37. SHIFT+[X] = 速度 +1 倍
  38. [*],[C],SHIFT+[Z] = 速度復原
  39. CTRL+['] = 更改預設速度
  40. CTRL+[\] = 更改速度倍率
  41. SHIFT+[S] = 截圖(JPG可改)
  42.  
  43. 對於寬螢幕視窗:
  44. CTRL+6 = 變更寬螢幕內容的影片寬高比。 修正寬螢幕內容縮小為 4:3 電視格式的問題。
  45. CTRL+7 = 更改內容的影片寬高比。 修正 4:3 內容拉伸為寬螢幕格式的問題。
  46. CTRL+8 = 更改內容的影片寬高比。 修正 4:3 內容拉伸為寬螢幕格式的問題。
  47. 對於 4:3 視窗:
  48. CTRL+SHIFT+6 = 變更超寬螢幕內容的影片寬高比。 修正壓縮為 4:3 電視格式的超寬螢幕內容。
  49. CTRL+SHIFT+7 = 縮放 4:3 信箱內容以刪除頂部+底部邊框的一半,同時也刪除一點左側+右側的內容。(這也可用於在寬螢幕視窗上半縮放超寬螢幕內容。 即 CTRL+6 的半縮放。)
  50. CTRL+SHIFT+8 = 變更寬螢幕內容的影片寬高比。 修正壓縮為 4:3 電視格式的寬螢幕內容。
  51. 對於任何視窗:
  52. CTRL+9 = 重置影片寬高比
  53. */
  54.  
  55. ((eleOSD, osdTimer) => {
  56.  
  57. //速度倍率
  58. var incrementUnit = 0.2;
  59. //變更播放速率時螢幕右下提示 (OSD) 的持續時間(以毫秒為單位)。 設定為零或更少以停用。
  60. var osdTimeout = 500;
  61. //截圖格式jpeg, png
  62. var imageFormat = "jpeg";
  63.  
  64. //鍵盤快速鍵。
  65. // 每個鍵名可以是該鍵產生的字元(例如 'a'、'4'、'*' 等),
  66. // 例如 'Digit2'、'BracketLeft' 等兩種類型都區分大小寫。
  67. //修飾符 = "C"、"S"和"A"的任意組合,大小寫通用,用於 Ctrl、Shift 和 Alt 鍵。
  68. //caseSensitive = `true` 如果鍵名區分大小寫。如果省略,則預設不區分大小寫。
  69. var keys = [
  70. /*{ //0 to 9: 跳到 0%,10%,20%,...90%
  71. key: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], modifiers: "",
  72. func: (ele, key, keyIndex) => ele.currentTime = keyIndex / 10 * ele.duration
  73. },*/
  74. { //shift+0 to shift+9: 跳到 5%,15%,25%,...95%
  75. key: [")", "!", "@", "#", "$", "%", "^", "&", "*", "("], modifiers: "S",
  76. func: (ele, key, keyIndex) => ele.currentTime = (keyIndex + 0.5) / 10 * ele.duration
  77. },
  78. { //Alt+1~Alt+0 = 將音量改為 10%、20%...90%、0%
  79. key: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], modifiers: "A",
  80. func: (ele, key, keyIndex) => updAudioVolume(ele, (parseInt(key) * 1) / 10)
  81. },
  82. { //Alt+` = 將音量改為 5%
  83. key: ["`"], modifiers: "A",
  84. func: (ele, key, keyIndex) => updAudioVolume(ele, 0.05)
  85. },
  86. //前進後退
  87. {
  88. key: ["a"], modifiers: "",
  89. func: (ele, key) => ele.currentTime -= 1
  90. },
  91. {
  92. key: ["d"], modifiers: "",
  93. func: (ele, key) => ele.currentTime += 1
  94. },
  95. {
  96. key: "ArrowLeft", modifiers: "",
  97. func: (ele, key) => ele.currentTime -= 3
  98. },
  99. {
  100. key: "ArrowRight", modifiers: "",
  101. func: (ele, key) => ele.currentTime += 3
  102. },
  103. {
  104. key: "ArrowLeft", modifiers: "C",
  105. func: (ele, key) => ele.currentTime -= 30
  106. },
  107. {
  108. key: "ArrowRight", modifiers: "C",
  109. func: (ele, key) => ele.currentTime += 30
  110. },
  111. {
  112. key: "ArrowLeft", modifiers: "S",
  113. func: (ele, key) => ele.currentTime -= 60
  114. },
  115. {
  116. key: "ArrowRight", modifiers: "S",
  117. func: (ele, key) => ele.currentTime += 60
  118. },
  119. {
  120. key: ["a"], modifiers: "S",
  121. func: (ele, key) => ele.currentTime -= 30
  122. },
  123. {
  124. key: ["d"], modifiers: "S",
  125. func: (ele, key) => ele.currentTime += 30
  126. },
  127. {
  128. key: ["a"], modifiers: "SA",
  129. func: (ele, key) => ele.currentTime -= 99999
  130. },
  131. {
  132. key: ["d"], modifiers: "SA",
  133. func: (ele, key) => ele.currentTime += 99999
  134. },
  135. {
  136. key: ",", modifiers: "C",
  137. func: (ele, key) => ele.currentTime -= 1/30
  138. },
  139. {
  140. key: ".", modifiers: "C",
  141. func: (ele, key) => ele.currentTime += 1/30
  142. },
  143. //速度-
  144. {
  145. key: ["z","-"], modifiers: "",
  146. func: (ele, key) => {
  147. key = ele.playbackRate - incrementUnit;
  148. if (key < 0.1) {
  149. key = 0.1;
  150. } else if ((key < 1) && (ele.playbackRate > 1)) key = 1;
  151. updVideoSpeed(ele, key);
  152. }
  153. },
  154. //速度+
  155. {
  156. key: ["x","+"], modifiers: "",
  157. func: (ele, key) => {
  158. key = ele.playbackRate + incrementUnit;
  159. if (key > 16) {
  160. key = 16;
  161. } else if ((key > 1) && (ele.playbackRate < 1)) key = 1;
  162. updVideoSpeed(ele, key);
  163. }
  164. },
  165. //速度+1X
  166. {
  167. key: ["x"], modifiers: "S",
  168. func: (ele, key) => {
  169. key = ele.playbackRate + incrementUnit;
  170. if (key > 16) {
  171. key = 16;
  172. } else if ((key > 1) && (ele.playbackRate < 1)) key = 1;
  173. updVideoSpeed(ele, 0.8+key);
  174. }
  175. },
  176. //速度復原1x
  177. {
  178. key: ["c","*"], modifiers: "",
  179. func: (ele, key) => {updVideoSpeed(ele, 1)}
  180. },
  181. {
  182. key: ["z"], modifiers: "S",
  183. func: (ele, key) => {updVideoSpeed(ele, 1)}
  184. },
  185. //ctrl+': 更改預設速度
  186. {
  187. key: "'", modifiers: "C",
  188. func: (ele, key) => {
  189. 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;
  190. if (isNaN(key = parseFloat(key.trim()))) {
  191. alert("Input must be a number.");
  192. return;
  193. }
  194. updVideoSpeed(ele, (key = parseFloat(key.toFixed(1))) < 0.1 ? 0.1 : (key > 16 ? 16 : key));
  195. }
  196. },
  197. //ctrl+\: 更改速度倍率
  198. {
  199. key: "\\", modifiers: "C",
  200. func: (ele, key) => {
  201. if ((key = prompt("Enter unit of media speed increment/decrement from 0.1 to 4 (inclusive).", incrementUnit)) === null) return;
  202. if (!isNaN(key = parseFloat(key.trim()))) {
  203. incrementUnit = (key = parseFloat(key.toFixed(1))) < 0.1 ? 0.1 : (key > 4 ? 4 : key);
  204. } else alert("Input must be a number.");
  205. }
  206. },
  207. //截圖screenshot
  208. {
  209. key: ["s"], modifiers: "S", videoOnly: false,
  210. func: (ele, key) => screenshot()
  211. },
  212. //對於寬螢幕視窗ctrl+6.7.8
  213. {
  214. key: "6", modifiers: "C", videoOnly: true,
  215. func: (ele, key) => updVideoAspect("scaleX(1.3333)", "Widescreen")
  216. },
  217. {
  218. key: "7", modifiers: "C", videoOnly: true,
  219. func: (ele, key) => updVideoAspect("scaleY(1.3333)", "Letterbox")
  220. },
  221. {
  222. key: "8", modifiers: "C", videoOnly: true,
  223. func: (ele, key) => updVideoAspect("scaleX(0.75)", "TV")
  224. },
  225. //對於 4:3 視窗CTRL+SHIFT+6.7.8
  226. {
  227. key: "Digit6", modifiers: "CS", videoOnly: true,
  228. func: (ele, key) => updVideoAspect("scaleY(0.7168)", "Ultra Widescreen")
  229. },
  230. {
  231. key: "Digit7", modifiers: "CS", videoOnly: true,
  232. func: (ele, key) => updVideoAspect("scale(1.1666)", "Letterbox Half-Zoom")
  233. },
  234. {
  235. key: "Digit8", modifiers: "CS", videoOnly: true,
  236. func: (ele, key) => updVideoAspect("scaleY(0.5625)", "Widescreen On TV")
  237. },
  238. //CTRL+9 = 重置影片寬高比
  239. {
  240. key: "9", modifiers: "C", videoOnly: true,
  241. func: (ele, key) => updVideoAspect("", "Reset")
  242. }
  243. ];
  244. keys.forEach((k, s, m) => {
  245. if ((k.modifiers === undefined) || !k.modifiers.toUpperCase) k.modifiers = "";
  246. s = k.modifiers.toUpperCase();
  247. k.modifiers = {ctrl: s.includes("C"), shift: s.includes("S"), alt: s.includes("A")}
  248. });
  249.  
  250. //截圖設定
  251. function screenshot(ele, cv) {
  252. if (ele = document.querySelector("video")) {
  253. cv = document.createElement("CANVAS");
  254. if (cv.width = ele.videoWidth) {
  255. cv.height = ele.videoHeight;
  256. cv.getContext("2d").drawImage(ele, 0, 0);
  257. ele = document.createElement("A");
  258. ele.href = cv.toDataURL("image/" + imageFormat);
  259. const VideoElement = document.querySelector('video');
  260. const CurrentTime = VideoElement.currentTime;
  261. const Hours = Math.floor(CurrentTime / 3600);
  262. const Minutes = Math.floor((CurrentTime % 3600) / 60);
  263. const Seconds = Math.floor(CurrentTime % 60);
  264. const FormattedTime = `${Hours.toString().padStart(2, '0')}${Minutes.toString().padStart(2, '0')}${Seconds.toString().padStart(2, '0')}`;
  265. ele.download = document.title + `-${FormattedTime}.${imageFormat === "jpeg" ? "jpg" : imageFormat}`;//
  266. ele.style.visibility = "hidden";
  267. document.body.appendChild(ele).click();
  268. ele.remove();
  269. return;
  270. } else {
  271. alert("The HTML5 video media has not been loaded yet.");
  272. }
  273. } else {
  274. alert("There is no HTML5 video on this page.");
  275. }
  276. };
  277. var to = {createHTML: s => s}, tp = window.trustedTypes?.createPolicy ? trustedTypes.createPolicy("", to) : to, html = s => tp.createHTML(s);
  278.  
  279. //提示框
  280. function showOSD(s) {
  281. if (osdTimeout < 0) return;
  282. if (eleOSD) {
  283. eleOSD.textContent = s;
  284. } else {
  285. eleOSD = document.createElement("DIV");
  286. 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";
  287. eleOSD.textContent = s;
  288. document.body.appendChild(eleOSD);
  289. }
  290. clearTimeout(osdTimer);
  291. osdTimer = setTimeout(() => {
  292. eleOSD.remove();
  293. eleOSD = null;
  294. }, osdTimeout);
  295. }
  296.  
  297. function stopEvent(ev) {
  298. ev.preventDefault();
  299. ev.stopPropagation();
  300. ev.stopImmediatePropagation();
  301. }
  302.  
  303. function updVideoSpeed(ele, spd, e) {
  304. // if ((location.hostname === "www.youtube.com") && (e = ele.parentNode.parentNode).setPlaybackRate && (spd >= 0.25) && (spd <= 2)) {
  305. // e.setPlaybackRate(spd = parseFloat(spd.toFixed(1)));
  306. // } else ele.playbackRate = spd = parseFloat(spd.toFixed(1));
  307. ele.playbackRate = spd = parseFloat(spd.toFixed(1));
  308. showOSD("Speed " + spd + "x");
  309. }
  310.  
  311. function updVideoAspect(asp, label, s) {
  312. if (!(s = document.getElementById("vidAspOvr"))) document.body.appendChild(s = document.createElement("STYLE")).id = "vidAspOvr";
  313. s.innerHTML = html(asp ? `video{transform:${asp}!important}` : "");
  314. showOSD("Ratio: " + label);
  315. }
  316.  
  317. function updAudioVolume(ele, vol, e) {
  318. if ((location.hostname === "www.youtube.com") && (e = ele.parentNode.parentNode).setVolume) {
  319. e.setVolume(vol * 100);
  320. } else ele.volume = vol;
  321. showOSD("Audio " + (vol * 100) + "%");
  322. }
  323.  
  324. function isVisible(ele) {
  325. while (ele && ele.tagName) {
  326. if (getComputedStyle(ele).display === "none") return false;
  327. ele = ele.parentNode
  328. }
  329. return true
  330. }
  331.  
  332. incrementUnit = parseFloat((incrementUnit < 0.1 ? 0.1 : (incrementUnit > 1 ? 1 : incrementUnit)).toFixed(1));
  333. addEventListener("keydown", function(ev, ele, evkey, evcode, kkey) {
  334. if ((!(ele = document.activeElement) || !((ele.contentEditable === "true") || ["BUTTON", "INPUT", "SELECT", "TEXTAREA"].includes(ele.tagName))) && (ele = document.querySelector("video,audio"))) {
  335. keys.some((k, a, i) => {
  336. a = !!k.key.sort;
  337. evkey = k.caseSensitive ? ev.key : ev.key.toUpperCase();
  338. evcode = k.caseSensitive ? ev.code : ev.code.toUpperCase();
  339. kkey = k.caseSensitive ? k.key : (a ? k.key.map(s => s.toUpperCase()) : k.key.toUpperCase());
  340. if (
  341. ((!a && ((kkey === evcode) || (kkey === evkey))) || (a && (((i = kkey.indexOf(evcode)) >= 0) || ((i = kkey.indexOf(evkey)) >= 0)))) &&
  342. (k.modifiers.ctrl === ev.ctrlKey) && (k.modifiers.shift === ev.shiftKey) && (k.modifiers.alt === ev.altKey) &&
  343. (!k.videoOnly || (ele.tagName === "VIDEO")) && (isVisible(ele) || (ele.tagName === "AUDIO"))
  344. ) {
  345. stopEvent(ev);
  346. k.func?.(ele, evkey, a ? i : null, k);
  347. return true;
  348. }
  349. });
  350. }
  351. }, true);
  352.  
  353. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址