Html5VideoShortcut

YouTube-like keyboard shortcuts for HTML5 video.

  1. // ==UserScript==
  2. // @name Html5VideoShortcut
  3. // @namespace com.gmail.fujifruity.greasemonkey
  4. // @version 0.1
  5. // @description YouTube-like keyboard shortcuts for HTML5 video.
  6. // @author fujifruity
  7. // @match *://*/*
  8. // @grant none
  9. // @run-at document-idle
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. {
  14. const Shortcut = (run, msg) => ({
  15. run: run,
  16. msg: msg
  17. })
  18. const getShortcut = (v /*video*/, key) =>
  19. // Play/Pause
  20. key == 'k' || key == ' ' ? Shortcut(() => v.paused ? v.play() : v.pause(), v.paused ? 'play' : 'pause') :
  21. // Seek
  22. key == 'j' ? Shortcut(() => v.currentTime -= 10, '-10s') :
  23. key == 'l' ? Shortcut(() => v.currentTime += 10, '+10s') :
  24. key == 'ArrowLeft' ? Shortcut(() => v.currentTime -= 5, '-5s') :
  25. key == 'ArrowRight' ? Shortcut(() => v.currentTime += 5, '+5s') :
  26. key == ',' ? Shortcut(() => v.currentTime -= 0.0333, '') :
  27. key == '.' ? Shortcut(() => v.currentTime += 0.0333, '') :
  28. '0' <= key && key <= '9' ? Shortcut(() => v.currentTime = parseInt(key) * v.duration / 10, key + '/10') :
  29. // Volume
  30. key == 'ArrowDown' ? Shortcut(() => v.volume -= 0.1, parseInt(v.volume * 100) - 10 + '%') :
  31. key == 'ArrowUp' ? Shortcut(() => v.volume += 0.1, parseInt(v.volume * 100) + 10 + '%') :
  32. key == 'm' ? Shortcut(() => v.muted = !v.muted, v.muted ? 'unmute' : 'mute') :
  33. // Speed
  34. key == '<' && v.playbackRate >= 0.50 ? Shortcut(() => v.playbackRate -= 0.25, v.playbackRate - 0.25 + 'x') :
  35. key == '>' && v.playbackRate <= 1.75 ? Shortcut(() => v.playbackRate += 0.25, v.playbackRate + 0.25 + 'x') : null
  36.  
  37. const modalTimeout = 400
  38. const modalId = 'fujifruity-Html5VideoShortcut'
  39. const showMsg = (v, msg) => {
  40. const modal = document.getElementById(modalId) ?? createModal()
  41. modal.innerText = msg
  42. const vrect = v.getBoundingClientRect()
  43. const top = vrect.y + vrect.height / 2 - modal.height / 2 //+ document.documentElement.scrollTop
  44. const left = vrect.x + vrect.width / 2 - modal.width / 2 //+ document.documentElement.scrollLeft
  45. modal.style.top = parseInt(top) + 'px'
  46. modal.style.left = parseInt(left) + 'px'
  47. modal.style.position = 'absolute'
  48. document.body.appendChild(modal)
  49. window.setTimeout(() => modal.remove(), modalTimeout)
  50. }
  51. const createModal = () => {
  52. const modal = document.createElement('div')
  53. modal.id = modalId
  54. modal.width = 80
  55. modal.height = 50
  56. modal.style.width = modal.width + 'px'
  57. modal.style.height = modal.height + 'px'
  58. modal.style.lineHeight = modal.height + 'px'
  59. modal.style.textAlign = 'center'
  60. modal.style.color = 'lightgray'
  61. modal.style.fontFamily = 'sans-serif'
  62. modal.style.backgroundColor = '#000000aa'
  63. return modal
  64. }
  65.  
  66. window.addEventListener('keydown', event => {
  67. if (event.target.tagName != 'VIDEO') return
  68. if (event.ctrlKey || event.altKey || event.metaKey) return
  69. const v = event.target
  70. const shortcut = getShortcut(v, event.key)
  71. if (shortcut) {
  72. shortcut.run()
  73. if (shortcut.msg) showMsg(v, shortcut.msg)
  74. event.preventDefault()
  75. }
  76. })
  77. }