YouTube Permanent ProgressBar

Keeps YouTube progress bar visible all the time.

当前为 2024-11-23 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();