Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Video speed control userscript
// ==UserScript==
// @name TF Guitar Wisdom video speed control
// @description Injects a video speed controller in the embedded video frames at TF Guitar Wisdom, visible on mouse over, easily adaptable for other sites
// @version 1.1.0
// @author xbb
// @match https://embed.vhx.tv/*
// @allFrames true
// ==/UserScript==
(() => {
const waitVideoEl = new Promise((resolve) => {
const video = document.querySelector('video');
if (video) {
resolve(video);
return;
}
(new MutationObserver((mutationRecords, observer) => {
const video = document.querySelector('video');
if (video) {
resolve(video);
observer.disconnect();
}
})).observe(document.documentElement, {
childList: true,
subtree: true
});
});
waitVideoEl.then((video) => {
const rateText = document.createElement('span');
Object.assign(rateText.style, {
margin: '0 .5em',
flex: '0 0 2em',
textAlign: 'center',
userSelect: 'none'
});
const rateEl = document.createElement('div');
Object.assign(rateEl.style, {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
position: 'fixed',
top: '.5em',
left: '50%',
width: '20%',
minWidth: '22em',
borderRadius: '2px',
backgroundColor: 'rgba(30,30,30,.9)',
color: 'white',
padding: '.5em',
fontFamily: 'Arial',
transform: 'translate(-50%, 0)',
transition: 'opacity .25s',
opacity: 0,
visibility: 'hidden'
});
const rateInput = document.createElement('input');
rateInput.type = 'range';
rateInput.min = 0.25;
rateInput.max = 4;
rateInput.step = 0.25;
rateInput.style.margin = '0 .5em';
rateInput.style.flex = '1 1 auto';
let loopStart, loopEnd, loop;
const btnStyle = {
display: 'block',
color: 'black',
fontWeight: 'bold',
marginLeft: '.5em',
width: '1.5em',
height: '1.5em',
flex: '0 0 auto',
padding: 0,
border: 0,
textAlign: 'center',
};
const lBtn = document.createElement('button');
Object.assign(lBtn.style, btnStyle);
lBtn.textContent = 'L';
lBtn.addEventListener('click', () => {
loop = !loop;
if (loop) {
lBtn.style.backgroundColor = '#0e0';
} else {
lBtn.style.backgroundColor = 'white';
}
});
const aBtn = document.createElement('button');
Object.assign(aBtn.style, btnStyle);
aBtn.textContent = 'A';
aBtn.addEventListener('click', () => {
loopStart = video.currentTime;
});
const bBtn = document.createElement('button');
Object.assign(bBtn.style, btnStyle);
bBtn.textContent = 'B';
bBtn.addEventListener('click', () => {
loopEnd = video.currentTime;
});
video.addEventListener('timeupdate', () => {
if (!loop || !loopStart || !loopEnd || loopStart >= loopEnd) {
return;
}
if (video.currentTime > loopEnd) {
video.currentTime = loopStart;
}
});
let hideTimeout;
const hide = () => {
clearTimeout(hideTimeout);
rateEl.style.visibility = 'hidden';
rateEl.style.opacity = 0;
};
const show = () => {
rateEl.style.visibility = 'visible';
rateEl.style.opacity = 1;
clearTimeout(hideTimeout);
hideTimeout = setTimeout(hide, 2000);
};
const setRate = (rate) => {
rateInput.value = rate;
rateText.textContent = parseFloat(rate).toFixed(2) + 'x';
video.playbackRate = rate;
};
setRate(video.playbackRate);
rateEl.appendChild(rateInput);
rateEl.appendChild(rateText);
rateEl.appendChild(lBtn);
rateEl.appendChild(aBtn);
rateEl.appendChild(bBtn);
document.body.appendChild(rateEl);
rateText.addEventListener('click', () => setRate(1));
rateInput.addEventListener('input', () => {
setRate(rateInput.value);
});
document.addEventListener('mousemove', show);
document.addEventListener('mouseout', hide);
console.log('speed control initialized');
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment