Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name YouTube Adjusted Time
- // @namespace http://tampermonkey.net/
- // @version 1.1.1
- // @description Displays the actual watch time on YouTube based on playback speed.
- // @author YourName (lub pozostaw puste/zmień)
- // @match *://*.youtube.com/*
- // @match *://youtube.com/*
- // @match *://*.youtube-nocookie.com/*
- // @match *://youtube-nocookie.com/*
- // @grant none
- // @run-at document-idle
- // ==/UserScript==
- (function() {
- 'use strict';
- console.log("YouTube Adjusted Time (Tampermonkey script) loaded.");
- let adjustedTimeElement = null;
- let lastVideoElement = null;
- let originalDurationElement = null;
- let rateChangeListener = null; // Referencja do listenera 'ratechange'
- let timeUpdateListener = null; // Referencja do listenera 'timeupdate'
- // Funkcja do formatowania sekund na MM:SS lub HH:MM:SS
- function formatTime(totalSeconds) {
- if (isNaN(totalSeconds) || totalSeconds < 0) {
- return "??:??";
- }
- const hours = Math.floor(totalSeconds / 3600);
- const minutes = Math.floor((totalSeconds % 3600) / 60);
- const seconds = Math.floor(totalSeconds % 60);
- const paddedSeconds = String(seconds).padStart(2, '0');
- const paddedMinutes = String(minutes).padStart(2, '0');
- if (hours > 0) {
- const paddedHours = String(hours).padStart(2, '0');
- return `${paddedHours}:${paddedMinutes}:${paddedSeconds}`;
- } else {
- return `${paddedMinutes}:${paddedSeconds}`;
- }
- }
- // Funkcja aktualizująca TYLKO bieżący przeliczony czas (wywoływana przez 'timeupdate')
- function updateCurrentAdjustedTimeDisplay() {
- // Sprawdź, czy mamy wszystkie potrzebne elementy i dane
- if (!lastVideoElement || !adjustedTimeElement || !adjustedTimeElement.dataset.rate || !adjustedTimeElement.dataset.totalAdjustedFormatted) {
- // console.log("Skipping timeupdate: missing elements or data");
- return;
- }
- const currentTime = lastVideoElement.currentTime;
- // Odczytaj zapisane wartości prędkości i całkowitego sformatowanego czasu
- const playbackRate = parseFloat(adjustedTimeElement.dataset.rate);
- const formattedAdjustedTotal = adjustedTimeElement.dataset.totalAdjustedFormatted;
- // Podstawowe walidacje
- if (isNaN(playbackRate) || playbackRate <= 0 || !isFinite(currentTime)) {
- // console.log("Skipping timeupdate: invalid playback rate or current time");
- return;
- }
- const adjustedCurrentTime = currentTime / playbackRate;
- const formattedAdjustedCurrent = formatTime(adjustedCurrentTime);
- const textToShow = `${formattedAdjustedCurrent} / ${formattedAdjustedTotal} @${playbackRate}x`;
- // Optymalizacja: Aktualizuj DOM tylko jeśli tekst się zmienił
- if (adjustedTimeElement.textContent !== textToShow) {
- adjustedTimeElement.textContent = textToShow;
- // console.log(`Timeupdate: ${textToShow}`); // Opcjonalny debugging
- }
- }
- // Funkcja aktualizująca całość (całkowity czas, dodanie listenerów) - główna logika
- function updateAdjustedTime() {
- const videoElement = document.querySelector('video.html5-main-video');
- if (!videoElement || isNaN(videoElement.duration)) {
- if (adjustedTimeElement) {
- adjustedTimeElement.textContent = "";
- }
- // console.log("Video element or duration not ready.");
- // Usuń stare listenery jeśli wideo zniknęło
- if (lastVideoElement) {
- if (rateChangeListener) lastVideoElement.removeEventListener('ratechange', rateChangeListener);
- if (timeUpdateListener) lastVideoElement.removeEventListener('timeupdate', timeUpdateListener);
- //console.log("Removed listeners because video element disappeared.");
- rateChangeListener = null;
- timeUpdateListener = null;
- lastVideoElement = null;
- }
- return;
- }
- // --- Zarządzanie Listenerami ---
- // Jeśli wideo się zmieniło, usuń stare listenery z POPRZEDNIEGO wideo
- if (lastVideoElement && lastVideoElement !== videoElement) {
- if (rateChangeListener) {
- lastVideoElement.removeEventListener('ratechange', rateChangeListener);
- // console.log("Removed old ratechange listener.");
- }
- if (timeUpdateListener) {
- lastVideoElement.removeEventListener('timeupdate', timeUpdateListener);
- // console.log("Removed old timeupdate listener.");
- }
- rateChangeListener = null;
- timeUpdateListener = null;
- }
- // Dodaj listenery tylko jeśli jeszcze nie istnieją dla AKTUALNEGO elementu wideo
- if (lastVideoElement !== videoElement) {
- // Listener na zmianę prędkości
- rateChangeListener = updateAdjustedTime; // Zmiana prędkości wymaga przeliczenia wszystkiego
- videoElement.addEventListener('ratechange', rateChangeListener);
- // console.log("Added new ratechange listener.");
- // Listener na zmianę bieżącego czasu
- timeUpdateListener = updateCurrentAdjustedTimeDisplay; // Aktualizuje tylko bieżący czas
- videoElement.addEventListener('timeupdate', timeUpdateListener);
- // console.log("Added new timeupdate listener.");
- lastVideoElement = videoElement; // Zapamiętaj aktualne wideo
- }
- // --- Koniec Zarządzania Listenerami ---
- const originalDuration = videoElement.duration;
- const playbackRate = videoElement.playbackRate;
- if (playbackRate <= 0 || !isFinite(originalDuration)) { // Ignoruj live streamy itp.
- if (adjustedTimeElement) {
- adjustedTimeElement.textContent = "";
- }
- // Upewnij się, że listenery są usunięte dla live streamów
- if(rateChangeListener && videoElement) videoElement.removeEventListener('ratechange', rateChangeListener);
- if(timeUpdateListener && videoElement) videoElement.removeEventListener('timeupdate', timeUpdateListener);
- rateChangeListener = null;
- timeUpdateListener = null;
- lastVideoElement = null; // Zresetuj też lastVideoElement
- return;
- }
- // Obliczenia czasów
- const adjustedTotalDuration = originalDuration / playbackRate;
- const formattedAdjustedTotalTime = formatTime(adjustedTotalDuration);
- const currentAdjustedTime = videoElement.currentTime / playbackRate;
- const formattedCurrentAdjustedTime = formatTime(currentAdjustedTime);
- originalDurationElement = document.querySelector('.ytp-time-duration');
- if (originalDurationElement) {
- // Stwórz element, jeśli nie istnieje
- if (!adjustedTimeElement) {
- adjustedTimeElement = document.createElement('span');
- adjustedTimeElement.id = 'adjusted-duration-display';
- adjustedTimeElement.style.marginLeft = '5px';
- adjustedTimeElement.style.color = '#ffeb3b'; // Żółty kolor dla lepszej widoczności
- // Wstaw po oryginalnym czasie LUB przed nim, jeśli nie ma następnego rodzeństwa
- if(originalDurationElement.nextSibling) {
- originalDurationElement.parentNode.insertBefore(adjustedTimeElement, originalDurationElement.nextSibling);
- } else {
- originalDurationElement.parentNode.appendChild(adjustedTimeElement);
- }
- // console.log("Created adjusted time element.");
- }
- // Zapisz potrzebne dane w atrybutach data-* dla listenera 'timeupdate'
- adjustedTimeElement.dataset.rate = playbackRate;
- adjustedTimeElement.dataset.totalAdjustedFormatted = formattedAdjustedTotalTime;
- // Ustaw początkowy tekst (zawierający już bieżący czas)
- const textToShow = `${formattedCurrentAdjustedTime} / ${formattedAdjustedTotalTime} @${playbackRate}x`;
- if (adjustedTimeElement.textContent !== textToShow) {
- adjustedTimeElement.textContent = textToShow;
- // console.log(`Updated adjusted time (initial/ratechange): ${textToShow}`);
- }
- } else {
- // Jeśli element oryginalnego czasu zniknął
- if (adjustedTimeElement) {
- adjustedTimeElement.remove();
- adjustedTimeElement = null;
- // console.log("Original duration element not found, removing adjusted time element.");
- }
- // Usuń listenery, jeśli nie ma gdzie wyświetlać czasu
- if (lastVideoElement) {
- if (rateChangeListener) lastVideoElement.removeEventListener('ratechange', rateChangeListener);
- if (timeUpdateListener) lastVideoElement.removeEventListener('timeupdate', timeUpdateListener);
- rateChangeListener = null;
- timeUpdateListener = null;
- // Nie resetuj lastVideoElement tutaj, bo wideo może nadal istnieć, tylko UI się zmieniło
- }
- }
- }
- // Funkcja inicjująca lub aktualizująca po zmianie wideo
- function initializeOrUpdate() {
- // console.log("Attempting to initialize or update adjusted time...");
- // Usuń stare listenery, jeśli istniały na poprzednim wideo
- if (lastVideoElement) {
- if (rateChangeListener) {
- lastVideoElement.removeEventListener('ratechange', rateChangeListener);
- rateChangeListener = null;
- // console.log("Removed ratechange listener during re-initialization.");
- }
- if (timeUpdateListener) {
- lastVideoElement.removeEventListener('timeupdate', timeUpdateListener);
- timeUpdateListener = null;
- // console.log("Removed timeupdate listener during re-initialization.");
- }
- }
- lastVideoElement = null; // Zresetuj referencję do wideo
- // Usuń stary element wyświetlający czas, jeśli istnieje
- const existingAdjustedElement = document.getElementById('adjusted-duration-display');
- if(existingAdjustedElement) {
- existingAdjustedElement.remove();
- adjustedTimeElement = null;
- // console.log("Removed existing adjusted time element during re-initialization.");
- }
- // Poczekaj na załadowanie elementów YouTube
- setTimeout(updateAdjustedTime, 500);
- setTimeout(updateAdjustedTime, 1500); // Ponowna próba
- setTimeout(updateAdjustedTime, 3000); // Ostatnia próba
- }
- // ---- Główne punkty wejścia ----
- // 1. Uruchom przy pierwszym ładowaniu
- initializeOrUpdate();
- // 2. Nasłuchuj na nawigację w YouTube (SPA)
- document.addEventListener('yt-navigate-finish', initializeOrUpdate);
- // 3. MutationObserver (fallback i dynamiczne ładowanie)
- const observer = new MutationObserver((mutationsList) => {
- let needsUpdate = false;
- for (const mutation of mutationsList) {
- // Sprawdź czy pojawił się odtwarzacz lub element czasu
- const playerAdded = Array.from(mutation.addedNodes).some(node => node.matches && (node.matches('video.html5-main-video') || node.querySelector('video.html5-main-video')));
- const durationAdded = Array.from(mutation.addedNodes).some(node => node.matches && (node.matches('.ytp-time-duration') || node.querySelector('.ytp-time-duration')));
- // Sprawdź czy zniknął nasz element (np. przez zmianę UI YouTube)
- const adjustedRemoved = Array.from(mutation.removedNodes).some(node => node && node.id === 'adjusted-duration-display');
- if (playerAdded || durationAdded || adjustedRemoved) {
- needsUpdate = true;
- break; // Wystarczy jedno dopasowanie
- }
- }
- if(needsUpdate){
- // console.log("MutationObserver detected change, queueing update.");
- // Użyj małego opóźnienia, aby dać czas na ustabilizowanie DOM
- clearTimeout(observer.debounceTimer); // Zapobiegaj wielokrotnym wywołaniom w krótkim czasie
- observer.debounceTimer = setTimeout(updateAdjustedTime, 250);
- }
- });
- observer.debounceTimer = null; // Inicjalizacja timera dla debounce
- observer.observe(document.body, { childList: true, subtree: true });
- // 4. Nasłuchuj na 'loadedmetadata' (gdy czas trwania staje się dostępny)
- // Delegacja zdarzeń na body
- document.body.addEventListener('loadedmetadata', (event) => {
- if (event.target && event.target.matches('video.html5-main-video')) {
- // console.log("loadedmetadata event detected on main video.");
- updateAdjustedTime(); // Metadane są gotowe, odśwież wszystko
- }
- }, true); // Użyj fazy przechwytywania
- })();
Advertisement