YouTube Permanent ProgressBar

Keeps YouTube progress bar visible all the time.

// ==UserScript==
// @name         YouTube Permanent ProgressBar
// @namespace    http://tampermonkey.net/
// @version      0.3.4
// @description  Keeps YouTube progress bar visible all the time.
// @author       ChromiaCat
// @match        *://www.youtube.com/*
// @license      MIT
// ==/UserScript==

var style = document.createElement('style');
var to = { createHTML: s => s },
    tp = window.trustedTypes?.createPolicy ? trustedTypes.createPolicy("", to) : to,
    html = s => tp.createHTML(s);
style.type = 'text/css';
style.innerHTML = html('.ytp-autohide .ytp-chrome-bottom{opacity:1!important;display:block!important}.ytp-autohide .ytp-chrome-bottom .ytp-progress-bar-container{bottom:-1px!important}.ytp-autohide .ytp-chrome-bottom .ytp-chrome-controls{opacity:0!important}');
document.getElementsByTagName('head')[0].appendChild(style);

var permanentProgressBar = {
    options: {
        UPDATE_INTERVAL: 200,                   // Update interval in milliseconds.
        PROGRESSBAR_OPACITY_WINDOW: 1,          // Progress bar opacity in window mode.
        PROGRESSBAR_OPACITY_FULLSCREEN: 0.5,    // Progress bar opacity in fullscreen mode.
        UPDATE_VIDEO_TIMER: true,               // Update the video timer (current time display).
        UPDATE_PROGRESSBAR: true,               // Update the progress bar for played content.
        UPDATE_BUFFERBAR: true,                 // Update the buffer bar for loaded content.
    },

    // Converts current video time to a pretty, human-readable format
    prettifyVideoTime: function (video) {
        let seconds = "" + Math.floor(video.currentTime % 60);
        let minutes = "" + Math.floor((video.currentTime % 3600) / 60);
        let hours = "" + Math.floor(video.currentTime / 3600);
        if (video.currentTime / 60 > 60) {
            return `${hours}:${minutes.padStart(2, '0')}:${seconds.padStart(2, '0')}`;
        } else {
            return `${minutes}:${seconds.padStart(2, '0')}`;
        }
    },

    // Gets either the current time or the buffered end for the video
    getDuration: function (video, type) {
        if (type === "PROGRESSBAR") {
            return video.currentTime; // Current time for the progress bar.
        } else if (type === "BUFFERBAR") {
            if (video.buffered.length > 0) {
                // Find the buffered range relevant to the current time.
                let bufferedEnd = 0;
                for (let i = 0; i < video.buffered.length; i++) {
                    if (video.currentTime >= video.buffered.start(i) && video.currentTime <= video.buffered.end(i)) {
                        bufferedEnd = video.buffered.end(i);
                        break;
                    }
                }
                return bufferedEnd;
            }
            return 0; // No buffered data.
        }
    },

    // works only on chapterless (old) videos
    updateCurrentTimeField: function (player) {
        const video = player.querySelector("video");
        const currentTime = player.querySelector(".ytp-time-current");
        if (!video || !currentTime) {
            return;
        }
        currentTime.innerText = permanentProgressBar.prettifyVideoTime(video);
    },

    // Updates the progress bar (without chapters)
    updateProgressBar: function (player) {
        const video = player.querySelector("video");
        const progressBar = player.querySelector(".ytp-play-progress");
        const bufferBar = player.querySelector(".ytp-load-progress");
        if (!video || !progressBar || !bufferBar) {
            return;
        }

        // Set progress and buffer bars based on video playback and buffered time.
        progressBar.style.transform = `scaleX(${video.currentTime / video.duration})`;
        bufferBar.style.transform = `scaleX(${this.getDuration(video, "BUFFERBAR") / video.duration})`;
    },

    // Updates the progress and buffer bars for videos with chapters
    updateProgressBarWithChapters: function (player, type) {
        const video = player.querySelector("video");
        if (video == null || isNaN(video.duration)) {
            return;
        }

        // Get chapter progress bar elements
        const progressBarWidthsCollection = player.getElementsByClassName("ytp-progress-bar-padding");
        let progressBarChaptersCollection;
        if (type === "PROGRESSBAR") {
            progressBarChaptersCollection = player.getElementsByClassName("ytp-play-progress");
        } else if (type === "BUFFERBAR") {
            progressBarChaptersCollection = player.getElementsByClassName("ytp-load-progress");
        }

        // quit if elements do not exist
        if (!progressBarWidthsCollection || !progressBarChaptersCollection) {
            return;
        }

        // Compute the ratio of video duration to chapter progress bar width
        let totalProgressBarWidth = 0;
        for (let i = 0; i < progressBarWidthsCollection.length; i++) {
            totalProgressBarWidth += progressBarWidthsCollection[i].offsetWidth;
        }
        const durationWidthRatio = video.duration / totalProgressBarWidth;

        // loop inside chapters
        let chaptersPixelWidthUntilCurrentChapter = 0;
        for (let i = 0; i < progressBarWidthsCollection.length; i++) {
            // Check if the buffered or played time exceeds the current chapter width
            if (this.getDuration(video, type) > durationWidthRatio * (chaptersPixelWidthUntilCurrentChapter + progressBarWidthsCollection[i].offsetWidth)) {
                progressBarChaptersCollection[i].style.transform = "scaleX(1)";
                chaptersPixelWidthUntilCurrentChapter += progressBarWidthsCollection[i].offsetWidth; // increase the current chapters location by adding last watched chapter

            // If not, it means that we are on this chapter.
            // Find the appropriate size for the chapter and scale it
            } else {
                let currentTimeInChapter = this.getDuration(video, type) - (durationWidthRatio * chaptersPixelWidthUntilCurrentChapter); // current time
                let currentChapterLength = durationWidthRatio * progressBarWidthsCollection[i].offsetWidth; // total chapter time
                let currentChapterRatio = currentTimeInChapter / currentChapterLength;

                progressBarChaptersCollection[i].style.transform = `scaleX(${currentChapterRatio})`;
                break;
            }
        }
    },

    // Main update function called periodically
    update: function () {
        const player = document.querySelector(".html5-video-player"); // Get video element
        if (player == null) {
            return;
        }

        /* update css
        if(document.fullscreenElement){
            document.querySelector(".ytp-chrome-bottom").style.opacity = permanentProgressBar.options.PROGRESSBAR_OPACITY_FULLSCREEN;
        }
        else{
            document.querySelector(".ytp-chrome-bottom").style.opacity = permanentProgressBar.options.PROGRESSBAR_OPACITY_WINDOW;
        }
        */

        if (this.options.UPDATE_VIDEO_TIMER) {
            this.updateCurrentTimeField(player);
        }

        if (this.options.UPDATE_PROGRESSBAR) {
            this.updateProgressBarWithChapters(player, "PROGRESSBAR");
        }

        if (this.options.UPDATE_BUFFERBAR) {
            this.updateProgressBarWithChapters(player, "BUFFERBAR");
        }
    },

    // Starts the periodic update process
    start: function () {
        setInterval(this.update.bind(this), this.options.UPDATE_INTERVAL);
    }
};

permanentProgressBar.start();