YouTube Permanent ProgressBar

Keeps YouTube progress bar visible all the time.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         YouTube Permanent ProgressBar
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Keeps YouTube progress bar visible all the time.
// @author       Can Kurt
// @match        *://www.youtube.com/*
// @license      MIT
// @repository   https://github.com/cccaaannn/YouTubePermanentProgressBar
// ==/UserScript==

var permanentProgressBar = {

    options: {
        UPDATE_INTERVAL: 500,                    // Update interval in milliseconds, decrease for smoother progress movement or increase for performance.
        PROGRESSBAR_OPACITY_WINDOW: 1,           // Progress bar opacity on window mode between 0 - 1.
        PROGRESSBAR_OPACITY_FULLSCREEN: 0.5,     // Progress bar opacity on fullscreen mode between 0 - 1.

        UPDATE_VIDEO_TIMER: true,
        UPDATE_PROGRESSBAR: true,
        UPDATE_BUFFERBAR: true,
        UPDATE_SCRUBBER: true
    },

    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')}`;
        }
    },

    getDuration: function (video, type) {
        if (type === "PROGRESSBAR") {
            return video.currentTime;
        }
        else if (type === "BUFFERBAR") {
            return video.buffered.end(video.buffered.length - 1);
        }
    },

    updateCurrentTimeField: function (player) {
        const video = player.querySelector("video");
        const currentTime = player.querySelector(".ytp-time-current");
        if (!video || !currentTime) {
            return;
        }

        currentTime.innerText = permanentProgressBar.prettifyVideoTime(video);
    },

    updateScrubber: function (passedChapterCount, progressBarLastChapter, chaptersPixelWidthUntilCurrentChapter) {
        const scrubber = document.getElementsByClassName("ytp-scrubber-container");

        if (!scrubber || scrubber.length !== 1) {
            return;
        }

        const positionInfo = progressBarLastChapter.getBoundingClientRect();

        // Chapters has hidden margin and its different on fullscreen
        let chapterMarginFix = passedChapterCount * 2.05;
        if (document.fullscreenElement) {
            chapterMarginFix = passedChapterCount * 3.05;
        }

        const chaptersWithPadding = chaptersPixelWidthUntilCurrentChapter + chapterMarginFix;

        const distanceFromLeft = positionInfo.width + chaptersWithPadding;

        scrubber[0].style.transform = `translateX(${distanceFromLeft}px)`;
    },

    // Deprecated
    updateProgressBar: function (player) {
        // works only on chapterless (old) videos
        const video = player.querySelector("video");
        const progressBar = player.querySelector(".ytp-play-progress");
        const bufferBar = player.querySelector(".ytp-load-progress");
        if (!video || !progressBar || !bufferBar) {
            return;
        }

        progressBar.style.transform = `scaleX(${video.currentTime / video.duration})`;
        bufferBar.style.transform = `scaleX(${video.buffered.end(video.buffered.length - 1) / video.duration})`;
    },

    updateProgressBarWithChapters: function (player, type) {
        // YouTube api does not provides current time in chapters
        // this function finds current time in the chapter by finding the ratio between total video duration and total width of the chapters div

        const video = player.querySelector("video");
        if (video == null || isNaN(video.duration)) {
            return;
        }

        // there can be multiple chapters
        const progressBarWidthsCollection = player.getElementsByClassName("ytp-progress-bar-padding");

        // select progress or bufferBar
        let progressBarChaptersCollection;
        if (type === "PROGRESSBAR") {
            progressBarChaptersCollection = player.getElementsByClassName("ytp-play-progress");
        }
        if (type === "BUFFERBAR") {
            progressBarChaptersCollection = player.getElementsByClassName("ytp-load-progress");
        }

        // quit if elements does not exists
        if (!video || !progressBarWidthsCollection || !progressBarChaptersCollection) {
            return;
        }

        // find the ratio between total video duration and total width of the chapters div
        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;
        let passedChapterCount = 0;
        for (let i = 0; i < progressBarWidthsCollection.length; i++) {

            // if current time is bigger than durationWidthRatio * (chapters pixel width including current one) scale the current chapter to 1 because we passed it
            if (permanentProgressBar.getDuration(video, type) > durationWidthRatio * (chaptersPixelWidthUntilCurrentChapter + progressBarWidthsCollection[i].offsetWidth)) {
                progressBarChaptersCollection[i].style.transform = "scaleX(1)";

                // increase the current chapters location by adding last watched chapter
                chaptersPixelWidthUntilCurrentChapter += progressBarWidthsCollection[i].offsetWidth;

                passedChapterCount++;
            }

            // If not, it means that we are on this chapter.
            // Find the appropriate size for the chapter and scale it
            else {
                // current time
                let currentTimeInChapterInSeconds = permanentProgressBar.getDuration(video, type) - (durationWidthRatio * chaptersPixelWidthUntilCurrentChapter);

                // total chapter time
                let currentChapterLengthInSeconds = durationWidthRatio * progressBarWidthsCollection[i].offsetWidth;

                let currentChapterTimeRatio = currentTimeInChapterInSeconds / currentChapterLengthInSeconds

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

                // Update scrubber position to align with progressbar
                if (type === "PROGRESSBAR" && permanentProgressBar.options.UPDATE_SCRUBBER) {
                    const progressBarLastChapter = progressBarChaptersCollection[i];

                    permanentProgressBar.updateScrubber(
                        passedChapterCount,
                        progressBarLastChapter,
                        chaptersPixelWidthUntilCurrentChapter
                    );
                }

                break;
            }

        }

    },

    update: function () {
        // Get video element
        const player = document.querySelector(".html5-video-player");
        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;
        }

        // update video timer
        if (permanentProgressBar.options.UPDATE_VIDEO_TIMER) {
            permanentProgressBar.updateCurrentTimeField(player);
        }

        // update PROGRESSBAR
        if (permanentProgressBar.options.UPDATE_PROGRESSBAR) {
            permanentProgressBar.updateProgressBarWithChapters(player, "PROGRESSBAR");
        }

        // update BUFFERBAR
        if (permanentProgressBar.options.UPDATE_BUFFERBAR) {
            permanentProgressBar.updateProgressBarWithChapters(player, "BUFFERBAR");
        }

    },

    start: function () {
        setInterval(permanentProgressBar.update, permanentProgressBar.options.UPDATE_INTERVAL);
    }

};


permanentProgressBar.start();