YouTube - Remaining time

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

当前为 2023-10-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube - Remaining time
  3. // @namespace https://gist.github.com/4lrick/cf14cf267684f06c1b7bc559ddf2b943
  4. // @version 1.0
  5. // @description Displays the remaining duration of a YouTube video next to the video duration, taking into account the playback rate.
  6. // @author 4lrick
  7. // @match https://www.youtube.com/*
  8. // @grant none
  9. // @license GNU GPLv3
  10. // ==/UserScript==
  11.  
  12. (() => {
  13. 'use strict';
  14.  
  15. let timeDisplay;
  16. let videoIsInit = false;
  17. let activeVideoElement = null;
  18. let activeVideoInterval = null;
  19.  
  20. const checkAndMonitorVideoPage = () => {
  21. const newVideoPageActive = window.location.href.startsWith('https://www.youtube.com/watch');
  22. if (newVideoPageActive && !videoIsInit) {
  23. initializeTimeDisplay();
  24. videoIsInit = true;
  25. }
  26. };
  27.  
  28. const initializeTimeDisplay = () => {
  29. timeDisplay = document.createElement('div');
  30. timeDisplay.style.display = 'inline-block';
  31. timeDisplay.style.marginLeft = '10px';
  32. timeDisplay.style.color = '#ddd';
  33.  
  34. const timeDuration = document.querySelector('.ytp-time-duration');
  35. const timeContainer = document.querySelector('.ytp-time-display');
  36. const appContainer = document.querySelector('ytd-app');
  37.  
  38. if (timeDuration && timeContainer) {
  39. timeContainer.appendChild(timeDisplay);
  40. }
  41. setupMutationObserver(appContainer);
  42. };
  43.  
  44. const setupMutationObserver = (appContainer) => {
  45. const observer = new MutationObserver((mutationsList, observer) => {
  46. updateActiveVideo();
  47. });
  48. observer.observe(appContainer, { subtree: true, childList: true });
  49. };
  50.  
  51. const updateActiveVideo = () => {
  52. const isMiniplayerActive = document.querySelector('ytd-app').hasAttribute('miniplayer-is-active');
  53. const videoElement = document.querySelector('video');
  54.  
  55. if (!isMiniplayerActive && videoElement) {
  56. if (activeVideoInterval) {
  57. clearInterval(activeVideoInterval);
  58. }
  59.  
  60. activeVideoElement = videoElement;
  61. updateVideoTime();
  62. activeVideoInterval = setInterval(updateVideoTime, 1000);
  63. } else {
  64. clearInterval(activeVideoInterval);
  65. activeVideoElement = null;
  66. timeDisplay.textContent = '';
  67. }
  68. };
  69.  
  70. const updateVideoTime = () => {
  71. const { currentTime, duration, playbackRate } = activeVideoElement;
  72. const timeRemaining = (duration - currentTime) / playbackRate;
  73.  
  74. const hoursRemaining = Math.floor(timeRemaining / 3600);
  75. const minutesRemaining = Math.floor((timeRemaining % 3600) / 60);
  76. const secondsRemaining = Math.floor(timeRemaining % 60);
  77.  
  78. let formattedTimeRemaining = '';
  79. if (hoursRemaining > 0) {
  80. formattedTimeRemaining += hoursRemaining.toString() + ':';
  81. }
  82. formattedTimeRemaining += minutesRemaining.toString().padStart(2, '0') + ':' + secondsRemaining.toString().padStart(2, '0');
  83.  
  84. timeDisplay.textContent = '(' + formattedTimeRemaining + ')';
  85. };
  86.  
  87. const checkVideoPageActiveInterval = () => {
  88. checkAndMonitorVideoPage();
  89. window.requestAnimationFrame(checkVideoPageActiveInterval);
  90. };
  91.  
  92. checkVideoPageActiveInterval();
  93. })();