您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Keeps YouTube progress bar visible all the time.
// ==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();