|
/** |
|
* @author Qwerty <[email protected]> |
|
* |
|
* @name Enable 2/5/10 sec rewind (HUD) |
|
* |
|
* @description |
|
* This script allows you to precisely and more granularly rewind and fast forward videos on any website.\ |
|
* It also displays a HUD showing the total saved time and time spent seeking and buffering. |
|
* |
|
* - Use the `<` and `>` keys to rewind and fast forward by 5 seconds, respectively. |
|
* - Use the **Shift** or **RightAlt** modifier keys to skip by 10 and 2 seconds, respectively. |
|
* - When the video is **paused**, it will seek frame by frame instead at 30 fps *(configurable)* and double or half the speed with the modifier keys. |
|
* |
|
* @description |
|
* submit issues here: https://gist.github.com/ackvf/b180e9883069ad753969a30cb2622787 |
|
*/ |
|
|
|
((_ = window.q_skipCount = { |
|
|
|
/* change these values in devTools `q_skipCount.<...>` to your liking */ |
|
|
|
videoSelector: 'video', /* configure the querySelector for the video element */ |
|
expectedFramerate: 30, /* configure your video's framerate */ |
|
timeout: 2000, /* HUD automatically hides after this time. Requires restart. */ |
|
|
|
/* --- */ |
|
|
|
skippedTime: 0, |
|
spentLoading: 0, |
|
el: null, |
|
video: null, |
|
timeoutRef: null, |
|
|
|
handleKey(ev) { |
|
_.video = document.querySelector(_.videoSelector) ?? document.querySelector('video'); |
|
|
|
let d = 0; |
|
let f = _.expectedFramerate; |
|
|
|
switch (ev.key) { |
|
/* < > keys */ |
|
case ",": d = -5; break; |
|
case ",": d = -5; break; |
|
case ".": d = +5; break; |
|
/* Shift + < > keys */ |
|
case "?": d = -10; f /= 2; break; |
|
case ":": d = +10; f /= 2; break; |
|
/* RightAlt + < > keys */ |
|
case "<": d = -2; f *= 2; break; |
|
case ">": d = +2; f *= 2; break; |
|
case " ": togglePlayback(); break; |
|
} |
|
|
|
if (_.video && d) { |
|
/* framestep seek if video is paused */ |
|
if (_.video.paused) _.video.currentTime += Math.sign(d) / f; |
|
else { |
|
_.video.currentTime += d; |
|
_.skippedTime += d; |
|
_.lastDelta = d; |
|
const bufferingStartTime = performance.now(); |
|
_.video.addEventListener('canplaythrough', () => { |
|
_.spentLoading += performance.now() - bufferingStartTime; |
|
_.renderHUD(); |
|
}, { once: true }); |
|
} |
|
} |
|
|
|
if (!_.video || _.video !== document.querySelector(_.videoSelector)) console.warn(`Video ${_.videoSelector} not found`, { video: _.video }); |
|
}, |
|
renderHUD() { |
|
if (_.timeoutRef) _.removeHUD(); |
|
|
|
const ID = "skipCountContainer"; |
|
|
|
_.el ??= _.createHUD(ID, _.timeout); |
|
_.el.querySelector('#theSeek-m').textContent = ~~(_.skippedTime / 60); |
|
_.el.querySelector('#theSeek-s').textContent = _.skippedTime % 60; |
|
_.el.querySelector('#spentLoading').textContent = ~~(_.spentLoading / 100) / 10; |
|
|
|
_.el.classList.add(_.lastDelta > 0 ? 'positive' : 'negative'); |
|
setTimeout(() => _.el.classList.remove('positive', 'negative')); |
|
_.timeoutRef = setTimeout(_.removeHUD, _.timeout); |
|
|
|
document.body.insertAdjacentElement('afterbegin', _.el); |
|
}, |
|
removeHUD() { |
|
clearTimeout(_.timeoutRef); |
|
_.timeoutRef = null; |
|
_.el.remove(); |
|
_.el.classList.remove('positive', 'negative'); |
|
}, |
|
createHUD(ID, timeout) { |
|
const hud = document.createElement('div'); |
|
hud.id = ID; |
|
hud.className = `default transition`; |
|
|
|
const style = document.createElement('style'); |
|
style.textContent = ` |
|
#${ID} { |
|
position: absolute; |
|
z-index: 10000; |
|
left: 5px; |
|
top: 5px; |
|
font-size: 32px; |
|
padding: 5px 13px; |
|
opacity: 0.6; |
|
backdrop-filter: blur(5px) sepia(1); |
|
} |
|
|
|
#${ID}.default { |
|
border: 1px solid slategray; |
|
color: slategray; |
|
opacity: 0; |
|
box-shadow: inset 0 0 11px -1px slategray; |
|
} |
|
|
|
#${ID}.positive { |
|
border-color: dodgerblue; |
|
color: dodgerblue; |
|
opacity: 0.7; |
|
box-shadow: inset 0 0 11px -1px dodgerblue; |
|
} |
|
|
|
#${ID}.negative { |
|
border-color: tomato; |
|
color: tomato; |
|
opacity: 0.7; |
|
box-shadow: inset 0 0 11px -1px tomato; |
|
} |
|
|
|
#${ID}.transition { |
|
transition: all 800ms ease-out, opacity ${timeout}ms ease-in; |
|
} |
|
|
|
#${ID} #theSeek-m::after { content: 'm'; } |
|
#${ID} #theSeek-s::after, #${ID} #spentLoading::after { content: 's'; } |
|
`; |
|
hud.appendChild(style); |
|
|
|
const savedText = document.createTextNode('saved'); |
|
hud.appendChild(savedText); |
|
hud.appendChild(document.createElement('br')); |
|
|
|
const spanM = document.createElement('span'); |
|
spanM.id = 'theSeek-m'; |
|
hud.appendChild(spanM); |
|
|
|
const spanS = document.createElement('span'); |
|
spanS.id = 'theSeek-s'; |
|
hud.appendChild(spanS); |
|
|
|
hud.appendChild(document.createElement('br')); |
|
|
|
const spanL = document.createElement('span'); |
|
spanL.id = 'spentLoading'; |
|
hud.appendChild(spanL); |
|
|
|
return hud; |
|
}, |
|
}) => document.onkeypress = _.handleKey)(/* window.q_skipCount */); |