🏠 返回首頁 

Greasy Fork is available in English.

HTML5快速鍵hotkeys

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

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