Skip to content

Instantly share code, notes, and snippets.

@DanteDeRuwe
Created April 4, 2023 11:20
Show Gist options
  • Save DanteDeRuwe/8f228c05674de606efdb8d57cc6b9570 to your computer and use it in GitHub Desktop.
Save DanteDeRuwe/8f228c05674de606efdb8d57cc6b9570 to your computer and use it in GitHub Desktop.
Video speed user script.
// ==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