Last active
February 20, 2022 22:23
-
-
Save xbb/579e3d0928f396313e3931f08a152b35 to your computer and use it in GitHub Desktop.
Video speed control userscript
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 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