YouTube - Remaining time

Displays the remaining duration of a YouTube video next to the video duration, taking into account the playback rate.

Verzia zo dňa 15.10.2023. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

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