视频倍速快捷键

speed up/down video

  1. // ==UserScript==
  2. // @name 视频倍速快捷键
  3. // @version 1.1
  4. // @description speed up/down video
  5. // @author BlueSky
  6. // @match *://*.bilibili.com/*
  7. // @match *://*.netflix.com/*
  8. // @match *://*.youtube.com/*
  9. // @grant none
  10. // @namespace https://gf.qytechs.cn/users/447360
  11. // ==/UserScript==
  12.  
  13. /**
  14. * configMap: {
  15. * [siteName: string]: {
  16. * titleSelectors: string[] // 视频标题元素选择器
  17. * videoSelector: string // 视频元素选择器
  18. * fullscreenSelector: string // 全屏按钮元素选择器
  19. * keyMap: { // 按键功能映射
  20. * forward0_25?: string // 速度增加 0.25x
  21. * back0_25?: string // 速度减少 0.25x
  22. * forward0_5?: string // 速度增加 0.5x
  23. * back0_5?: string // 速度减少 0.5x
  24. * rate0_5?: string // 设为 0.5x
  25. * rate1?: string // 设为 1x
  26. * rate2?: string // 设为 2x
  27. * rate3?: string // 设为 3x
  28. * rate4?: string // 设为 4x
  29. * fullscreen?: string // 切换全屏
  30. * }
  31. * }
  32. * }
  33. */
  34.  
  35. (function () {
  36. 'use strict';
  37. const configMap = {
  38. bilibili: {
  39. titleSelectors: [
  40. 'div.bilibili-player-video-top-title',
  41. 'span.tit',
  42. ],
  43. videoSelector: 'bwp-video',
  44. fullscreenSelector: '.bilibili-player-video-btn.bilibili-player-video-btn-fullscreen',
  45. keyMap: {
  46. forward0_25: ']',
  47. back0_25: '[',
  48. forward0_5: '=',
  49. back0_5: '-',
  50. rate0_5: '`',
  51. rate1: '1',
  52. rate2: '2',
  53. rate3: '3',
  54. rate4: '4',
  55. fullscreen: 'Enter',
  56. },
  57. },
  58. youtube: {
  59. titleSelectors: [
  60. '.ytp-title-link.yt-uix-sessionlink.ytp-title-fullerscreen-link',
  61. 'h1 > yt-formatted-string.style-scope.ytd-video-primary-info-renderer'
  62. ],
  63. videoSelector: 'video',
  64. fullscreenSelector: '.ytp-fullscreen-button.ytp-button',
  65. keyMap: {
  66. forward0_25: ']',
  67. back0_25: '[',
  68. forward0_5: '=',
  69. back0_5: '-',
  70. rate0_5: '`',
  71. fullscreen: 'Enter',
  72. },
  73. },
  74. netflix: {
  75. titleSelectors: [
  76. '.watch-video h4',
  77. ],
  78. videoSelector: 'video',
  79. fullscreenSelector: 'button[data-uia^=control-fullscreen]',
  80. keyMap: {
  81. forward0_25: ']',
  82. back0_25: '[',
  83. forward0_5: '=',
  84. back0_5: '-',
  85. rate0_5: '`',
  86. rate1: '1',
  87. rate2: '2',
  88. rate3: '3',
  89. rate4: '4',
  90. },
  91. },
  92. }
  93. let rate = 1
  94. let titleSelectors = []
  95. let videoSelector = 'video'
  96. let fullscreenSelector = ''
  97. let keyMap = {}
  98. const siteName = location.hostname.split('.').slice(-2)[0]
  99. const config = configMap[siteName]
  100. if (config) {
  101. console.debug(`found config for ${siteName}:`, config)
  102. titleSelectors = config.titleSelectors
  103. videoSelector = config.videoSelector
  104. fullscreenSelector = config.fullscreenSelector
  105. keyMap = config.keyMap
  106. }
  107.  
  108.  
  109. window.addEventListener('keydown', (e) => {
  110. if (e.key === keyMap.rate0_5) {
  111. rate = 0.5
  112. } else if (e.key === keyMap.rate1) {
  113. rate = 1
  114. } else if (e.key === keyMap.rate2) {
  115. rate = 2
  116. } else if (e.key === keyMap.rate3) {
  117. rate = 3
  118. } else if (e.key === keyMap.rate4) {
  119. rate = 4
  120. } else if (e.key === keyMap.forward0_25 && rate < 16) {
  121. rate += 0.25
  122. } else if (e.key === keyMap.back0_25 && rate > 0.25) {
  123. rate -= 0.25
  124. } else if (e.key === keyMap.forward0_5 && rate < 16) {
  125. rate += 0.5
  126. } else if (e.key === keyMap.back0_5 && rate > 0.5) {
  127. rate -= 0.5
  128. } else if (e.key === keyMap.fullscreen) {
  129. toggleFullScreen()
  130. } else {
  131. return
  132. }
  133. setVideoRate()
  134. setVideoTitle()
  135. })
  136.  
  137. function getTitle() {
  138. return titleSelectors
  139. .map(selector => document.querySelector(selector))
  140. .filter(el => el && el.innerText)
  141. .map(el => el.innerText)[0]
  142. }
  143.  
  144. function setVideoRate() {
  145. let el = document.querySelector(videoSelector) || document.querySelector('video')
  146. if (el) {
  147. console.debug(`rate: ${rate}x`)
  148. el.playbackRate = rate
  149. }
  150. }
  151.  
  152. function setVideoTitle() {
  153. const title = (getTitle() || '').replace(/^\[.*\] /, '')
  154. for (const selector of titleSelectors) {
  155. const el = document.querySelector(selector)
  156. if (el) {
  157. el.innerHTML = `[${rate}x] ${title}`
  158. }
  159. }
  160. }
  161.  
  162. // 浏览器原生全屏事件,无控制条&弹幕
  163. function nativeToggleFullScreen() {
  164. const el = document.querySelector(videoSelector)
  165. if (el) {
  166. if (document.fullscreenElement) {
  167. console.debug(`enter fullscreen`)
  168. document.exitFullscreen()
  169. } else {
  170. console.debug(`exit fullscreen`)
  171. el.webkitRequestFullScreen()
  172. }
  173. }
  174. }
  175.  
  176. function toggleFullScreen() {
  177. const el = document.querySelector(fullscreenSelector)
  178. if (el) {
  179. console.debug(`toggleFullScreen`)
  180. el.click()
  181. }
  182. }
  183. })();

QingJ © 2025

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