在视频时长旁边显示YouTube视频的剩余时长,考虑播放速度。
当前为
// ==UserScript==
// @name YouTube - Remaining Time Indicator
// @name:fr YouTube - Indicateur du temps restant
// @name:es YouTube - Indicador de tiempo restante
// @name:de YouTube - Anzeige der verbleibenden Zeit
// @name:it YouTube - Indicatore del tempo rimanente
// @name:zh-CN YouTube - 剩余时间指示器
// @namespace https://gist.github.com/4lrick/cf14cf267684f06c1b7bc559ddf2b943
// @version 2.1
// @description Displays the remaining duration of a YouTube video next to the video duration, taking into account the playback rate.
// @description:fr Affiche la durée restante d'une vidéo YouTube à côté de la durée de la vidéo, en tenant compte de la vitesse de lecture.
// @description:es Muestra la duración restante de un video de YouTube junto a la duración del video, teniendo en cuenta la velocidad de reproducción.
// @description:de Zeigt die verbleibende Dauer eines YouTube-Videos neben der Videodauer an und berücksichtigt dabei die Wiedergabegeschwindigkeit.
// @description:it Mostra la durata rimanente di un video di YouTube accanto alla durata del video, 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 lastPlaybackRate = null;
let lastRenderedText = '';
let lastCurrentTime = null;
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'
);
const shouldUpdate =
videoElement &&
!isLive &&
(
!videoElement.paused ||
videoElement.playbackRate !== lastPlaybackRate ||
currentTime !== lastCurrentTime ||
showEndTime != !showEndTime
);
if (!videoElement || isLive || !timeContainer) {
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);
}
if (shouldUpdate) {
const text = formatTimeDisplay(videoElement);
if (text !== lastRenderedText) {
timeDisplay.textContent = text;
lastRenderedText = text;
}
lastPlaybackRate = videoElement.playbackRate;
lastCurrentTime = currentTime;
}
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 });
})();