Youtube Tracker

Counts youtube watchtime

  1. // ==UserScript==
  2. // @name Youtube Tracker
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3
  5. // @description Counts youtube watchtime
  6. // @author Kaanium
  7. // @match https://www.youtube.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  9. // @grant GM_setClipboard
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. function secondsToTime(seconds) {
  17. const hours = Math.floor(seconds / 3600);
  18. const minutes = Math.floor((seconds % 3600) / 60);
  19. const remainingSeconds = seconds % 60;
  20.  
  21. const formattedHours = hours.toString().padStart(2, '0');
  22. const formattedMinutes = minutes.toString().padStart(2, '0');
  23. const formattedSeconds = remainingSeconds.toString().padStart(2, '0');
  24.  
  25. if (hours > 0) {
  26. return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
  27. } else {
  28. return `${formattedMinutes}:${formattedSeconds}`;
  29. }
  30. }
  31.  
  32. function secondsToMoe(seconds) {
  33. const minutes = Math.floor(seconds / 60);
  34. const fractionalMinutes = (seconds % 60) / 60;
  35. return (minutes + fractionalMinutes).toFixed(2);
  36. }
  37.  
  38. function timeToSeconds(timeString) {
  39. const timeParts = timeString.split(':').map(Number);
  40. const totalParts = timeParts.length;
  41.  
  42. if (totalParts === 2) {
  43. // Format: mm:ss
  44. const [minutes, seconds] = timeParts;
  45. if (isNaN(minutes) || isNaN(seconds) || minutes < 0 || seconds < 0) {
  46. throw new Error('Invalid time format. Please use numeric values for minutes and seconds.');
  47. }
  48. return minutes * 60 + seconds;
  49. } else if (totalParts === 3) {
  50. // Format: hh:mm:ss
  51. const [hours, minutes, seconds] = timeParts;
  52. if (isNaN(hours) || isNaN(minutes) || isNaN(seconds) || hours < 0 || minutes < 0 || seconds < 0) {
  53. throw new Error('Invalid time format. Please use numeric values for hours, minutes, and seconds.');
  54. }
  55. return hours * 3600 + minutes * 60 + seconds;
  56. } else {
  57. throw new Error('Invalid time format. Expected format: hh:mm:ss or mm:ss');
  58. }
  59. }
  60.  
  61. function handleTextbox1Change() {
  62. timeTextbox2.value = secondsToMoe(timeToSeconds(timeTextbox1.value));
  63. totalWatchedTime = timeToSeconds(timeTextbox1.value);
  64. localStorage.setItem('totalWatchedTime', totalWatchedTime);
  65. }
  66.  
  67. function createStyledButton(text, onClickHandler) {
  68. const button = document.createElement("button");
  69. button.textContent = text;
  70. applyCommonStyles(button);
  71. button.onclick = onClickHandler;
  72. return button;
  73. }
  74.  
  75. function createStyledTextbox(type, value) {
  76. const textbox = document.createElement("input");
  77. textbox.setAttribute("type", type);
  78. textbox.setAttribute("value", value);
  79. applyCommonStyles(textbox);
  80. return textbox;
  81. }
  82.  
  83. function applyCommonStyles(element) {
  84. element.style.fontSize = "10px";
  85. element.style.backgroundColor = "hsl(0, 0%, 7%)";
  86. element.style.color = "#ffffffe0";
  87. element.style.borderColor = "hsla(0, 0%, 53.3%, 0.4)";
  88. element.style.borderWidth = "1px";
  89. element.style.borderRadius = "40px";
  90. element.style.fontFamily = '"Roboto","Arial",sans-serif';
  91. element.style.fontSize = "1.4rem";
  92. element.style.lineHeight = "2rem";
  93. element.style.fontWeight = "500";
  94. element.style.textAlign = "center";
  95. element.style.width = "5%";
  96. element.style.margin = "3px"
  97. }
  98.  
  99. var container = document.querySelector("#masthead > div:nth-child(5)");
  100.  
  101. const timeTextbox1 = createStyledTextbox("text", "00:00:00");
  102. container.insertBefore(timeTextbox1, document.getElementById("end"));
  103.  
  104. timeTextbox1.addEventListener("input", handleTextbox1Change);
  105.  
  106. const timeTextbox2 = createStyledTextbox("text", "0.0");
  107. container.insertBefore(timeTextbox2, document.getElementById("end"));
  108.  
  109. const newButton = createStyledButton("Clipboard", function () {
  110. GM_setClipboard(".log listening " + timeTextbox2.value);
  111. });
  112. container.insertBefore(newButton, document.getElementById("end"));
  113.  
  114. const resetButton = createStyledButton("Reset", function () {
  115. totalWatchedTime = 0;
  116. bufferSum = 0;
  117. bufferTime = 0;
  118. timeTextbox1.value = "00:00:00";
  119. timeTextbox2.value = "0.0";
  120. localStorage.removeItem('totalWatchedTime');
  121. });
  122. container.insertBefore(resetButton, document.getElementById("end"));
  123.  
  124. let startTime = new Date();
  125. let totalWatchedTime = parseInt(localStorage.getItem('totalWatchedTime')) || 0;
  126. let isVideoPlaying = true;
  127. let bufferTime = 0;
  128. let bufferSum = 0;
  129. let seconds = 0;
  130. var videoPlayer = document.querySelector("video");
  131. var moviePlayer = null;
  132.  
  133.  
  134. function updateTimeSpent() {
  135. if (isVideoPlaying && !moviePlayer.classList.contains("buffering-mode")) {
  136. bufferSum += bufferTime
  137. bufferTime = 0
  138. seconds = parseInt((new Date() - startTime) / 1000, 10);
  139. var temp = parseInt(localStorage.getItem('totalWatchedTime'))
  140. if (temp > totalWatchedTime + seconds - bufferSum) {
  141. totalWatchedTime = temp
  142. bufferSum = 0
  143. startTime = new Date()
  144. seconds = parseInt((new Date() - startTime) / 1000, 10);
  145. }
  146. timeTextbox2.value = secondsToMoe(totalWatchedTime + seconds - bufferSum);
  147. timeTextbox1.value = secondsToTime(totalWatchedTime + seconds - bufferSum);
  148. localStorage.setItem('totalWatchedTime', totalWatchedTime + seconds - bufferSum);
  149. }
  150. else if(moviePlayer.classList.contains("buffering-mode")) {
  151. bufferTime = parseInt((new Date() - startTime) / 1000, 10);
  152. console.log("buffer time: " + bufferTime)
  153. }
  154. }
  155.  
  156. function saveTotalWatchedTime() {
  157. if (videoPlayer.src) {
  158. let _seconds = parseInt((new Date() - startTime) / 1000, 10);
  159. totalWatchedTime += _seconds;
  160. }
  161. }
  162.  
  163. const checkForVideoPlayer = () => {
  164. videoPlayer = document.querySelector("video");
  165. moviePlayer = document.querySelector("#movie_player");
  166. if (videoPlayer && window.location.pathname == "/watch") {
  167. isVideoPlaying = true;
  168. videoPlayer.addEventListener("timeupdate", updateTimeSpent);
  169. videoPlayer.addEventListener("pause", function () {
  170. isVideoPlaying = false;
  171. console.log("pause")
  172. saveTotalWatchedTime();
  173. });
  174. videoPlayer.addEventListener("play", function () {
  175. isVideoPlaying = true;
  176. console.log("play")
  177. startTime = new Date();
  178. });
  179. videoPlayer.addEventListener("canplaythrough", function () {
  180. console.log("video can play through");
  181. startTime = new Date();
  182. });
  183. } else {
  184. setTimeout(checkForVideoPlayer, 1000);
  185. }
  186. };
  187.  
  188. checkForVideoPlayer();
  189.  
  190. document.addEventListener("yt-navigate-start", function() {
  191. isVideoPlaying = false;
  192. saveTotalWatchedTime()
  193. });
  194.  
  195. window.addEventListener('keydown', function(event) {
  196. if (event.key === 'ArrowLeft' || event.key === 'ArrowRight' || /^[0-9]$/.test(event.key)) {
  197. saveTotalWatchedTime();
  198. startTime = new Date();
  199. }
  200. });
  201.  
  202. window.addEventListener('popstate', function(event) {
  203. totalWatchedTime = timeToSeconds(timeTextbox1.value);
  204. startTime = new Date();
  205. });
  206.  
  207. updateTimeSpent();
  208. })();