您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 YouTube 播放器中显示剩余时间或结束时间,并考虑播放速度。
// ==UserScript== // @name YouTube - Time Indicators // @name:fr YouTube - Indicateurs de temps // @name:es YouTube - Indicadores de tiempo // @name:de YouTube - Zeitindikatoren // @name:it YouTube - Indicatori del tempo // @name:zh-CN YouTube - 时间指示器(多种) // @namespace https://gist.github.com/4lrick/cf14cf267684f06c1b7bc559ddf2b943 // @version 2.2 // @description Shows remaining or end time in the YouTube player, taking into account the playback speed. // @description:fr Affiche le temps restant ou l'heure de fin dans le lecteur YouTube, en tenant compte de la vitesse de lecture. // @description:es Muestra el tiempo restante o la hora de finalización en el reproductor de YouTube, teniendo en cuenta la velocidad de reproducción. // @description:de Zeigt im YouTube-Player die verbleibende Zeit oder die Endzeit an und berücksichtigt dabei die Wiedergabegeschwindigkeit. // @description:it Mostra il tempo rimanente o l'orario di fine nel riproduttore YouTube, tenendo conto della velocità di riproduzione. // @description:zh-CN 在 YouTube 播放器中显示剩余时间或结束时间,并考虑播放速度。 // @author 4lrick // @match https://www.youtube.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @grant none // @license GPL-3.0-only // ==/UserScript== (function() { 'use strict'; let timeDisplay; let lastRenderedText = ''; let showEndTime = localStorage.getItem('yt-player-remaining-time-mode') === 'true'; function createTimeDisplayElement() { const timeDisplayElement = document.createElement('span'); timeDisplayElement.style.display = 'inline-block'; timeDisplayElement.style.marginLeft = '10px'; timeDisplayElement.style.color = '#ddd'; timeDisplayElement.style.cursor = 'pointer'; timeDisplayElement.title = 'Click to toggle between remaining time and end time'; timeDisplayElement.addEventListener('click', () => { showEndTime = !showEndTime; localStorage.setItem('yt-player-remaining-time-mode', showEndTime); }); return timeDisplayElement; } function formatTimeDisplay(videoElement) { if (showEndTime) { const endTime = new Date(Date.now() + (videoElement.duration - videoElement.currentTime) * 1000 / videoElement.playbackRate); return `(${endTime.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })})`; } else { const timeRemaining = (videoElement.duration - videoElement.currentTime) / videoElement.playbackRate; const hours = Math.floor(timeRemaining / 3600); const minutes = Math.floor((timeRemaining % 3600) / 60); const seconds = Math.floor(timeRemaining % 60); return `(${hours > 0 ? `${hours}:` : ''}${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')})`; } } function displayRemainingTime() { const videoElement = document.querySelector('video'); const isLive = document.querySelector('.ytp-time-display')?.classList.contains('ytp-live'); const miniplayerUI = document.querySelector('.ytp-miniplayer-ui'); const isMiniplayerVisible = miniplayerUI && getComputedStyle(miniplayerUI).display !== 'none'; const currentTime = videoElement?.currentTime; const timeContainer = document.querySelector( isMiniplayerVisible ? '.ytp-miniplayer-ui .ytp-time-contents' : '.ytp-chrome-controls .ytp-time-contents' ); if (!videoElement || isLive || !timeContainer || isNaN(videoElement.duration)) { if (timeDisplay) { timeDisplay.remove(); timeDisplay = null; } requestAnimationFrame(displayRemainingTime); return; } if (!timeDisplay) { timeDisplay = createTimeDisplayElement(); timeContainer.appendChild(timeDisplay); } if (!timeContainer.contains(timeDisplay)) { timeDisplay.remove(); timeContainer.appendChild(timeDisplay); } const text = formatTimeDisplay(videoElement); if (text !== lastRenderedText) { timeDisplay.textContent = text; lastRenderedText = text; } requestAnimationFrame(displayRemainingTime); } function initRemainingCounter() { const timeContainer = document.querySelector('.ytp-time-contents'); if (timeContainer) { timeDisplay = createTimeDisplayElement(); timeContainer.appendChild(timeDisplay); requestAnimationFrame(displayRemainingTime); observer.disconnect(); } } function checkVideoExists() { const videoElement = document.querySelector('video'); if (videoElement) { initRemainingCounter(); } } const observer = new MutationObserver(checkVideoExists); observer.observe(document.body, { childList: true, subtree: true }); })();