您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays the remaining duration of a YouTube video next to the video duration, taking into account the playback rate.
当前为
// ==UserScript== // @name YouTube - Remaining time // @namespace https://gist.github.com/4lrick/cf14cf267684f06c1b7bc559ddf2b943 // @version 1.0 // @description Displays the remaining duration of a YouTube video next to the video duration, taking into account the playback rate. // @author 4lrick // @match https://www.youtube.com/* // @grant none // @license GPL-3.0-only // ==/UserScript== (() => { 'use strict'; let timeDisplay; let videoIsInit = false; let activeVideoElement = null; let activeVideoInterval = null; const checkAndMonitorVideoPage = () => { const newVideoPageActive = window.location.href.startsWith('https://www.youtube.com/watch'); if (newVideoPageActive && !videoIsInit) { initializeTimeDisplay(); videoIsInit = true; } }; const initializeTimeDisplay = () => { timeDisplay = document.createElement('div'); timeDisplay.style.display = 'inline-block'; timeDisplay.style.marginLeft = '10px'; timeDisplay.style.color = '#ddd'; const timeDuration = document.querySelector('.ytp-time-duration'); const timeContainer = document.querySelector('.ytp-time-display'); const appContainer = document.querySelector('ytd-app'); if (timeDuration && timeContainer) { timeContainer.appendChild(timeDisplay); } setupMutationObserver(appContainer); }; const setupMutationObserver = (appContainer) => { const observer = new MutationObserver((mutationsList, observer) => { updateActiveVideo(); }); observer.observe(appContainer, { subtree: true, childList: true }); }; const updateActiveVideo = () => { const isMiniplayerActive = document.querySelector('ytd-app').hasAttribute('miniplayer-is-active'); const videoElement = document.querySelector('video'); if (!isMiniplayerActive && videoElement) { if (activeVideoInterval) { clearInterval(activeVideoInterval); } activeVideoElement = videoElement; updateVideoTime(); activeVideoInterval = setInterval(updateVideoTime, 1000); } else { clearInterval(activeVideoInterval); activeVideoElement = null; timeDisplay.textContent = ''; } }; const updateVideoTime = () => { const { currentTime, duration, playbackRate } = activeVideoElement; const timeRemaining = (duration - currentTime) / playbackRate; const hoursRemaining = Math.floor(timeRemaining / 3600); const minutesRemaining = Math.floor((timeRemaining % 3600) / 60); const secondsRemaining = Math.floor(timeRemaining % 60); let formattedTimeRemaining = ''; if (hoursRemaining > 0) { formattedTimeRemaining += hoursRemaining.toString() + ':'; } formattedTimeRemaining += minutesRemaining.toString().padStart(2, '0') + ':' + secondsRemaining.toString().padStart(2, '0'); timeDisplay.textContent = '(' + formattedTimeRemaining + ')'; }; const checkVideoPageActiveInterval = () => { checkAndMonitorVideoPage(); window.requestAnimationFrame(checkVideoPageActiveInterval); }; checkVideoPageActiveInterval(); })();