Html5VideoShortcut

YouTube-like keyboard shortcuts for HTML5 video.

// ==UserScript==
// @name         Html5VideoShortcut
// @namespace    com.gmail.fujifruity.greasemonkey
// @version      0.1
// @description  YouTube-like keyboard shortcuts for HTML5 video.
// @author       fujifruity
// @match        *://*/*
// @grant        none
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

{
    const Shortcut = (run, msg) => ({
        run: run,
        msg: msg
    })
    const getShortcut = (v /*video*/, key) =>
        // Play/Pause
         key == 'k' || key == ' ' ? Shortcut(() => v.paused ? v.play() : v.pause(), v.paused ? 'play' : 'pause') :
        // Seek
         key == 'j' ?               Shortcut(() => v.currentTime -= 10,     '-10s') :
         key == 'l' ?               Shortcut(() => v.currentTime += 10,     '+10s') :
         key == 'ArrowLeft' ?       Shortcut(() => v.currentTime -= 5,      '-5s') :
         key == 'ArrowRight' ?      Shortcut(() => v.currentTime += 5,      '+5s') :
         key == ',' ?               Shortcut(() => v.currentTime -= 0.0333, '') :
         key == '.' ?               Shortcut(() => v.currentTime += 0.0333, '') :
         '0' <= key && key <= '9' ? Shortcut(() => v.currentTime = parseInt(key) * v.duration / 10, key + '/10') :
        // Volume
         key == 'ArrowDown' ?       Shortcut(() => v.volume -= 0.1, parseInt(v.volume * 100) - 10 + '%') :
         key == 'ArrowUp' ?         Shortcut(() => v.volume += 0.1, parseInt(v.volume * 100) + 10 + '%') :
         key == 'm' ?               Shortcut(() => v.muted = !v.muted, v.muted ? 'unmute' : 'mute') :
        // Speed
         key == '<' && v.playbackRate >= 0.50 ? Shortcut(() => v.playbackRate -= 0.25, v.playbackRate - 0.25 + 'x') :
         key == '>' && v.playbackRate <= 1.75 ? Shortcut(() => v.playbackRate += 0.25, v.playbackRate + 0.25 + 'x') : null

    const modalTimeout = 400
    const modalId = 'fujifruity-Html5VideoShortcut'
    const showMsg = (v, msg) => {
        const modal = document.getElementById(modalId) ?? createModal()
        modal.innerText = msg
        const vrect = v.getBoundingClientRect()
        const top = vrect.y + vrect.height / 2 - modal.height / 2 //+ document.documentElement.scrollTop
        const left = vrect.x + vrect.width / 2 - modal.width / 2 //+ document.documentElement.scrollLeft
        modal.style.top = parseInt(top) + 'px'
        modal.style.left = parseInt(left) + 'px'
        modal.style.position = 'absolute'
        document.body.appendChild(modal)
        window.setTimeout(() => modal.remove(), modalTimeout)
    }
    const createModal = () => {
        const modal = document.createElement('div')
        modal.id = modalId
        modal.width = 80
        modal.height = 50
        modal.style.width = modal.width + 'px'
        modal.style.height = modal.height + 'px'
        modal.style.lineHeight = modal.height + 'px'
        modal.style.textAlign = 'center'
        modal.style.color = 'lightgray'
        modal.style.fontFamily = 'sans-serif'
        modal.style.backgroundColor = '#000000aa'
        return modal
    }

    window.addEventListener('keydown', event => {
        if (event.target.tagName != 'VIDEO') return
        if (event.ctrlKey || event.altKey || event.metaKey) return
        const v = event.target
        const shortcut = getShortcut(v, event.key)
        if (shortcut) {
            shortcut.run()
            if (shortcut.msg) showMsg(v, shortcut.msg)
            event.preventDefault()
        }
    })
}