Greasy Fork is available in English.
Persistent YouTube Progress Bar
// ==UserScript==
// @name YouTube Progress Bar + Clock
// @namespace slevin
// @version 1.0
// @license MIT
// @description Persistent YouTube Progress Bar
// @match https://www.youtube.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @run-at document-idle
// ==/UserScript==
document.head.append(Object.assign(document.createElement('style'), {
textContent: `
.ytp-progress-bar-container {
transition: bottom 0.2s ease !important;
}
#movie_player.ytp-autohide .ytp-progress-bar-container {
bottom: 8px !important;
}
.ytp-autohide .ytp-chrome-bottom {
display: block !important;
opacity: 1 !important;
}
.ytp-autohide .ytp-chrome-controls {
opacity: 0 !important;
pointer-events: none !important;
transition: opacity 0.2s ease !important;
}
.ytp-progress-bar-container::after {
content: attr(data-time);
position: absolute;
bottom: 12px;
right: 0;
z-index: 10;
padding: 3px 8px;
background: rgba(0, 0, 0, 0.35);
border-radius: 12px;
color: #eee;
font-family: "YouTube Noto", Roboto, Arial, sans-serif;
font-size: 14px;
font-weight: 500;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease;
}
.ytp-autohide .ytp-progress-bar-container::after {
opacity: 1;
transition: opacity 0.2s ease;
}
`
}));
const fmt = s => isNaN(s) ? '0:00' : (Math.floor(s / 3600) ? Math.floor(s / 3600) + ':' + String(Math.floor((s % 3600) / 60)).padStart(2, '0') : Math.floor((s % 3600) / 60)) + ':' + String(Math.floor(s % 60)).padStart(2, '0');
const scaleFor = (p, start, end) => p >= end ? 1 : (p > start ? (p - start) / (end - start) : 0);
let v, container, scrubber;
document.addEventListener('yt-navigate-finish', () => {
v = null;
container = null;
scrubber = null;
});
(function loop() {
if (document.querySelector('video')) {
v ??= document.querySelector('video');
container ??= document.querySelector('.ytp-progress-bar-container');
scrubber ??= document.querySelector('.ytp-scrubber-container');
if (v?.duration && container) {
container.setAttribute('data-time', `${fmt(v.currentTime)} / ${fmt(v.duration)}`);
const { left: barLeft, width: barWidth } = container.getBoundingClientRect();
const progress = v.currentTime / v.duration;
if (scrubber) scrubber.style.transform = `translateX(${progress * barWidth}px)`;
let bufferEnd = 0;
for (let i = 0; i < v.buffered.length; i++) {
if (v.buffered.start(i) <= v.currentTime) bufferEnd = Math.max(bufferEnd, v.buffered.end(i));
}
const bProg = bufferEnd / v.duration;
document.querySelectorAll('.ytp-progress-list').forEach(seg => {
const { left, width } = seg.getBoundingClientRect();
const sStart = (left - barLeft) / barWidth, sEnd = (left - barLeft + width) / barWidth;
const fill = seg.querySelector('.ytp-play-progress'), load = seg.querySelector('.ytp-load-progress');
if (fill) fill.style.transform = `scaleX(${scaleFor(progress, sStart, sEnd)})`;
if (load) load.style.transform = `scaleX(${scaleFor(bProg, sStart, sEnd)})`;
});
}
}
requestAnimationFrame(loop);
})();
window.addEventListener('keydown', e => {
if (['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName) || document.activeElement?.isContentEditable || !['ArrowRight', 'ArrowLeft'].includes(e.key)) return;
e.stopImmediatePropagation();
e.preventDefault();
const v = document.querySelector('video');
if (v) v.currentTime += e.key === 'ArrowRight' ? 5 : -5;
}, true);