Created
April 4, 2023 11:20
-
-
Save DanteDeRuwe/8f228c05674de606efdb8d57cc6b9570 to your computer and use it in GitHub Desktop.
Video speed user script.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Video speed | |
// @author Dante De Ruwe | |
// @version 0.0.7 | |
// @description Let's you change the speed for all HTML5 videos with the use of the square bracket keys. | |
// @namespace http://tampermonkey.net/ | |
// @include * | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
const [MIN_SPEED, MAX_SPEED] = [0.3, 6]; | |
const DURATION_SPEED_DISPLAY_MS = 1500; | |
const TICK_RATE_MS = 50; | |
const intervals = []; | |
const timeouts = []; | |
const listeners = []; | |
const KEYMAP = { | |
BracketLeft: { | |
//decrease | |
default: (s) => s - 0.1, | |
shiftKey: (s) => s - 1, | |
}, | |
BracketRight: { | |
//increase | |
default: (s) => s + 0.1, | |
shiftKey: (s) => s + 1, | |
}, | |
Backslash: { | |
//ad buster | |
default: (s) => MAX_SPEED, | |
shiftKey: (s) => MAX_SPEED / 2, | |
}, | |
Quote: { | |
// reset | |
default: (s) => 1, | |
shiftKey: (s) => s, | |
}, | |
}; | |
const getPlayingVideo = () => { | |
const videos = [...document.querySelectorAll('video[src]')]; | |
const notpaused = videos.filter((v) => !v.paused); | |
return notpaused.length > 0 ? notpaused[0] : null; //for now (?) | |
}; | |
const applyStyles = (el, styles) => Object.entries(styles).forEach(([k, v]) => (el.style[k] = v)); | |
const roundAndCap = (val) => Math.max(MIN_SPEED, Math.min(MAX_SPEED, Math.round(val * 100) / 100)); | |
const createDisplay = () => { | |
const display = document.createElement('div'); | |
display.id = 'video-speed-display'; | |
applyStyles(display, { | |
position: 'fixed', | |
width: '40px', | |
textAlign: 'center', | |
padding: '5px', | |
color: 'white', | |
backgroundColor: 'rgba(0,0,0,.5)', | |
borderRadius: '5px', | |
visibility: 'hidden', | |
zIndex: 999, | |
}); | |
return display; | |
}; | |
let t; | |
const logSpeed = (video, display, speed) => { | |
clearTimeout(t); | |
const { x, y, width } = video.getBoundingClientRect(); | |
applyStyles(display, { | |
top: `${y + 30}px`, | |
left: `${x + width / 2 - 20}px`, | |
}); | |
display.style.setProperty('visibility', 'visible', 'important'); | |
display.innerText = `${speed}x`; | |
t = setTimeout(() => (display.style.visibility = 'hidden'), DURATION_SPEED_DISPLAY_MS); | |
timeouts.push(t); | |
}; | |
const run = (initialvideo) => { | |
let video = initialvideo; | |
const display = createDisplay(); | |
document.body.appendChild(display); | |
let speed = 1; | |
let prevspeed = 1; | |
const listener = document.addEventListener('keyup', (event) => { | |
if (!(event.code in KEYMAP)) return; | |
const actions = KEYMAP[event.code]; | |
const action = event.shiftKey ? actions.shiftKey : actions.default; | |
prevspeed = speed; | |
speed = roundAndCap(action(speed)); | |
logSpeed(video, display, speed); | |
}); | |
listeners.push(listener); | |
const interval = setInterval(() => { | |
// re-sync if speed has been externally modified | |
if (video.playbackRate !== prevspeed) { | |
speed = prevspeed = video.playbackRate = roundAndCap(video.playbackRate); | |
logSpeed(video, display, speed); | |
} | |
video.playbackRate = speed; | |
}, TICK_RATE_MS); | |
intervals.push(interval); | |
}; | |
const cleanup = () => { | |
intervals.filter((x) => x).forEach(clearInterval); | |
timeouts.filter((x) => x).forEach(clearTimeout); | |
listeners.filter((x) => x).forEach(document.removeEventListener); | |
const displays = [...document.querySelectorAll('#video-speed-display')]; | |
displays.forEach((d) => d.parentNode.removeChild(d)); | |
}; | |
let prevVideoSrc = ''; | |
const observer = new MutationObserver((mutations, obs) => { | |
const video = getPlayingVideo(); | |
if (!video) { | |
return; | |
} | |
if (prevVideoSrc !== video.src) { | |
prevVideoSrc = `${video.src}`; | |
cleanup(); | |
run(video); | |
} | |
}); | |
observer.observe(document, { | |
childList: true, | |
subtree: true, | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment