SheridanCoriana6

YouTube Adjusted Time

Jun 1st, 2025
177
0
Never
9
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.24 KB | Software | 0 0
  1. // ==UserScript==
  2. // @name YouTube Adjusted Time
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1.1
  5. // @description Displays the actual watch time on YouTube based on playback speed.
  6. // @author YourName (lub pozostaw puste/zmień)
  7. // @match *://*.youtube.com/*
  8. // @match *://youtube.com/*
  9. // @match *://*.youtube-nocookie.com/*
  10. // @match *://youtube-nocookie.com/*
  11. // @grant none
  12. // @run-at document-idle
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. console.log("YouTube Adjusted Time (Tampermonkey script) loaded.");
  19.  
  20. let adjustedTimeElement = null;
  21. let lastVideoElement = null;
  22. let originalDurationElement = null;
  23. let rateChangeListener = null; // Referencja do listenera 'ratechange'
  24. let timeUpdateListener = null; // Referencja do listenera 'timeupdate'
  25.  
  26. // Funkcja do formatowania sekund na MM:SS lub HH:MM:SS
  27. function formatTime(totalSeconds) {
  28. if (isNaN(totalSeconds) || totalSeconds < 0) {
  29. return "??:??";
  30. }
  31. const hours = Math.floor(totalSeconds / 3600);
  32. const minutes = Math.floor((totalSeconds % 3600) / 60);
  33. const seconds = Math.floor(totalSeconds % 60);
  34.  
  35. const paddedSeconds = String(seconds).padStart(2, '0');
  36. const paddedMinutes = String(minutes).padStart(2, '0');
  37.  
  38. if (hours > 0) {
  39. const paddedHours = String(hours).padStart(2, '0');
  40. return `${paddedHours}:${paddedMinutes}:${paddedSeconds}`;
  41. } else {
  42. return `${paddedMinutes}:${paddedSeconds}`;
  43. }
  44. }
  45.  
  46. // Funkcja aktualizująca TYLKO bieżący przeliczony czas (wywoływana przez 'timeupdate')
  47. function updateCurrentAdjustedTimeDisplay() {
  48. // Sprawdź, czy mamy wszystkie potrzebne elementy i dane
  49. if (!lastVideoElement || !adjustedTimeElement || !adjustedTimeElement.dataset.rate || !adjustedTimeElement.dataset.totalAdjustedFormatted) {
  50. // console.log("Skipping timeupdate: missing elements or data");
  51. return;
  52. }
  53.  
  54. const currentTime = lastVideoElement.currentTime;
  55. // Odczytaj zapisane wartości prędkości i całkowitego sformatowanego czasu
  56. const playbackRate = parseFloat(adjustedTimeElement.dataset.rate);
  57. const formattedAdjustedTotal = adjustedTimeElement.dataset.totalAdjustedFormatted;
  58.  
  59. // Podstawowe walidacje
  60. if (isNaN(playbackRate) || playbackRate <= 0 || !isFinite(currentTime)) {
  61. // console.log("Skipping timeupdate: invalid playback rate or current time");
  62. return;
  63. }
  64.  
  65. const adjustedCurrentTime = currentTime / playbackRate;
  66. const formattedAdjustedCurrent = formatTime(adjustedCurrentTime);
  67.  
  68. const textToShow = `${formattedAdjustedCurrent} / ${formattedAdjustedTotal} @${playbackRate}x`;
  69.  
  70. // Optymalizacja: Aktualizuj DOM tylko jeśli tekst się zmienił
  71. if (adjustedTimeElement.textContent !== textToShow) {
  72. adjustedTimeElement.textContent = textToShow;
  73. // console.log(`Timeupdate: ${textToShow}`); // Opcjonalny debugging
  74. }
  75. }
  76.  
  77.  
  78. // Funkcja aktualizująca całość (całkowity czas, dodanie listenerów) - główna logika
  79. function updateAdjustedTime() {
  80. const videoElement = document.querySelector('video.html5-main-video');
  81.  
  82. if (!videoElement || isNaN(videoElement.duration)) {
  83. if (adjustedTimeElement) {
  84. adjustedTimeElement.textContent = "";
  85. }
  86. // console.log("Video element or duration not ready.");
  87. // Usuń stare listenery jeśli wideo zniknęło
  88. if (lastVideoElement) {
  89. if (rateChangeListener) lastVideoElement.removeEventListener('ratechange', rateChangeListener);
  90. if (timeUpdateListener) lastVideoElement.removeEventListener('timeupdate', timeUpdateListener);
  91. //console.log("Removed listeners because video element disappeared.");
  92. rateChangeListener = null;
  93. timeUpdateListener = null;
  94. lastVideoElement = null;
  95. }
  96. return;
  97. }
  98.  
  99. // --- Zarządzanie Listenerami ---
  100. // Jeśli wideo się zmieniło, usuń stare listenery z POPRZEDNIEGO wideo
  101. if (lastVideoElement && lastVideoElement !== videoElement) {
  102. if (rateChangeListener) {
  103. lastVideoElement.removeEventListener('ratechange', rateChangeListener);
  104. // console.log("Removed old ratechange listener.");
  105. }
  106. if (timeUpdateListener) {
  107. lastVideoElement.removeEventListener('timeupdate', timeUpdateListener);
  108. // console.log("Removed old timeupdate listener.");
  109. }
  110. rateChangeListener = null;
  111. timeUpdateListener = null;
  112. }
  113.  
  114. // Dodaj listenery tylko jeśli jeszcze nie istnieją dla AKTUALNEGO elementu wideo
  115. if (lastVideoElement !== videoElement) {
  116. // Listener na zmianę prędkości
  117. rateChangeListener = updateAdjustedTime; // Zmiana prędkości wymaga przeliczenia wszystkiego
  118. videoElement.addEventListener('ratechange', rateChangeListener);
  119. // console.log("Added new ratechange listener.");
  120.  
  121. // Listener na zmianę bieżącego czasu
  122. timeUpdateListener = updateCurrentAdjustedTimeDisplay; // Aktualizuje tylko bieżący czas
  123. videoElement.addEventListener('timeupdate', timeUpdateListener);
  124. // console.log("Added new timeupdate listener.");
  125.  
  126. lastVideoElement = videoElement; // Zapamiętaj aktualne wideo
  127. }
  128. // --- Koniec Zarządzania Listenerami ---
  129.  
  130.  
  131. const originalDuration = videoElement.duration;
  132. const playbackRate = videoElement.playbackRate;
  133.  
  134. if (playbackRate <= 0 || !isFinite(originalDuration)) { // Ignoruj live streamy itp.
  135. if (adjustedTimeElement) {
  136. adjustedTimeElement.textContent = "";
  137. }
  138. // Upewnij się, że listenery są usunięte dla live streamów
  139. if(rateChangeListener && videoElement) videoElement.removeEventListener('ratechange', rateChangeListener);
  140. if(timeUpdateListener && videoElement) videoElement.removeEventListener('timeupdate', timeUpdateListener);
  141. rateChangeListener = null;
  142. timeUpdateListener = null;
  143. lastVideoElement = null; // Zresetuj też lastVideoElement
  144. return;
  145. }
  146.  
  147. // Obliczenia czasów
  148. const adjustedTotalDuration = originalDuration / playbackRate;
  149. const formattedAdjustedTotalTime = formatTime(adjustedTotalDuration);
  150. const currentAdjustedTime = videoElement.currentTime / playbackRate;
  151. const formattedCurrentAdjustedTime = formatTime(currentAdjustedTime);
  152.  
  153.  
  154. originalDurationElement = document.querySelector('.ytp-time-duration');
  155.  
  156. if (originalDurationElement) {
  157. // Stwórz element, jeśli nie istnieje
  158. if (!adjustedTimeElement) {
  159. adjustedTimeElement = document.createElement('span');
  160. adjustedTimeElement.id = 'adjusted-duration-display';
  161. adjustedTimeElement.style.marginLeft = '5px';
  162. adjustedTimeElement.style.color = '#ffeb3b'; // Żółty kolor dla lepszej widoczności
  163. // Wstaw po oryginalnym czasie LUB przed nim, jeśli nie ma następnego rodzeństwa
  164. if(originalDurationElement.nextSibling) {
  165. originalDurationElement.parentNode.insertBefore(adjustedTimeElement, originalDurationElement.nextSibling);
  166. } else {
  167. originalDurationElement.parentNode.appendChild(adjustedTimeElement);
  168. }
  169. // console.log("Created adjusted time element.");
  170. }
  171.  
  172. // Zapisz potrzebne dane w atrybutach data-* dla listenera 'timeupdate'
  173. adjustedTimeElement.dataset.rate = playbackRate;
  174. adjustedTimeElement.dataset.totalAdjustedFormatted = formattedAdjustedTotalTime;
  175.  
  176. // Ustaw początkowy tekst (zawierający już bieżący czas)
  177. const textToShow = `${formattedCurrentAdjustedTime} / ${formattedAdjustedTotalTime} @${playbackRate}x`;
  178. if (adjustedTimeElement.textContent !== textToShow) {
  179. adjustedTimeElement.textContent = textToShow;
  180. // console.log(`Updated adjusted time (initial/ratechange): ${textToShow}`);
  181. }
  182.  
  183. } else {
  184. // Jeśli element oryginalnego czasu zniknął
  185. if (adjustedTimeElement) {
  186. adjustedTimeElement.remove();
  187. adjustedTimeElement = null;
  188. // console.log("Original duration element not found, removing adjusted time element.");
  189. }
  190. // Usuń listenery, jeśli nie ma gdzie wyświetlać czasu
  191. if (lastVideoElement) {
  192. if (rateChangeListener) lastVideoElement.removeEventListener('ratechange', rateChangeListener);
  193. if (timeUpdateListener) lastVideoElement.removeEventListener('timeupdate', timeUpdateListener);
  194. rateChangeListener = null;
  195. timeUpdateListener = null;
  196. // Nie resetuj lastVideoElement tutaj, bo wideo może nadal istnieć, tylko UI się zmieniło
  197. }
  198. }
  199. }
  200.  
  201. // Funkcja inicjująca lub aktualizująca po zmianie wideo
  202. function initializeOrUpdate() {
  203. // console.log("Attempting to initialize or update adjusted time...");
  204.  
  205. // Usuń stare listenery, jeśli istniały na poprzednim wideo
  206. if (lastVideoElement) {
  207. if (rateChangeListener) {
  208. lastVideoElement.removeEventListener('ratechange', rateChangeListener);
  209. rateChangeListener = null;
  210. // console.log("Removed ratechange listener during re-initialization.");
  211. }
  212. if (timeUpdateListener) {
  213. lastVideoElement.removeEventListener('timeupdate', timeUpdateListener);
  214. timeUpdateListener = null;
  215. // console.log("Removed timeupdate listener during re-initialization.");
  216. }
  217. }
  218. lastVideoElement = null; // Zresetuj referencję do wideo
  219.  
  220. // Usuń stary element wyświetlający czas, jeśli istnieje
  221. const existingAdjustedElement = document.getElementById('adjusted-duration-display');
  222. if(existingAdjustedElement) {
  223. existingAdjustedElement.remove();
  224. adjustedTimeElement = null;
  225. // console.log("Removed existing adjusted time element during re-initialization.");
  226. }
  227.  
  228. // Poczekaj na załadowanie elementów YouTube
  229. setTimeout(updateAdjustedTime, 500);
  230. setTimeout(updateAdjustedTime, 1500); // Ponowna próba
  231. setTimeout(updateAdjustedTime, 3000); // Ostatnia próba
  232. }
  233.  
  234. // ---- Główne punkty wejścia ----
  235.  
  236. // 1. Uruchom przy pierwszym ładowaniu
  237. initializeOrUpdate();
  238.  
  239. // 2. Nasłuchuj na nawigację w YouTube (SPA)
  240. document.addEventListener('yt-navigate-finish', initializeOrUpdate);
  241.  
  242. // 3. MutationObserver (fallback i dynamiczne ładowanie)
  243. const observer = new MutationObserver((mutationsList) => {
  244. let needsUpdate = false;
  245. for (const mutation of mutationsList) {
  246. // Sprawdź czy pojawił się odtwarzacz lub element czasu
  247. const playerAdded = Array.from(mutation.addedNodes).some(node => node.matches && (node.matches('video.html5-main-video') || node.querySelector('video.html5-main-video')));
  248. const durationAdded = Array.from(mutation.addedNodes).some(node => node.matches && (node.matches('.ytp-time-duration') || node.querySelector('.ytp-time-duration')));
  249. // Sprawdź czy zniknął nasz element (np. przez zmianę UI YouTube)
  250. const adjustedRemoved = Array.from(mutation.removedNodes).some(node => node && node.id === 'adjusted-duration-display');
  251.  
  252.  
  253. if (playerAdded || durationAdded || adjustedRemoved) {
  254. needsUpdate = true;
  255. break; // Wystarczy jedno dopasowanie
  256. }
  257. }
  258. if(needsUpdate){
  259. // console.log("MutationObserver detected change, queueing update.");
  260. // Użyj małego opóźnienia, aby dać czas na ustabilizowanie DOM
  261. clearTimeout(observer.debounceTimer); // Zapobiegaj wielokrotnym wywołaniom w krótkim czasie
  262. observer.debounceTimer = setTimeout(updateAdjustedTime, 250);
  263. }
  264. });
  265. observer.debounceTimer = null; // Inicjalizacja timera dla debounce
  266.  
  267. observer.observe(document.body, { childList: true, subtree: true });
  268.  
  269. // 4. Nasłuchuj na 'loadedmetadata' (gdy czas trwania staje się dostępny)
  270. // Delegacja zdarzeń na body
  271. document.body.addEventListener('loadedmetadata', (event) => {
  272. if (event.target && event.target.matches('video.html5-main-video')) {
  273. // console.log("loadedmetadata event detected on main video.");
  274. updateAdjustedTime(); // Metadane są gotowe, odśwież wszystko
  275. }
  276. }, true); // Użyj fazy przechwytywania
  277.  
  278. })();
Advertisement
Comments
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • Nexdexek
    10 days
    Comment was deleted
Add Comment
Please, Sign In to add comment