'How to track time spent watching YouTube videos using a cookie in Javascript?

I'm looking for a way to automatically track how much time I spend watching YouTube each day (so the timer resets at midnight). The catch is, I want the timer to only log time spent watching videos, not counting time browsing YouTube or when a video is paused. I've not found a browser extension that can achieve this, so I've tried doing it myself by using a Chrome extension called Page Manipulator that injects my own Javascript code into the YouTube website.

My code calculates the time watched on a video by using the video element's played method as described here. Since I don't want the timer to reset when YouTube is closed, my code stores the time spent watching YouTube in a cookie called timewatched, which is set to expire on the midnight of the day it's created. The timer appears above the YouTube video as a <p> element.

The timer sometimes freezes without any console error, possibly because the timeRanges object returned by played is sometimes empty (length of zero) when it seems like it shouldn't be (because you've been playing the video). What's gone wrong? Is there a simpler way to achieve what I'm trying to do?

My code:

max_time_in_minutes = 30;

//setCookie() and getCookie() are functions taken from w3schools
//https://www.w3schools.com/js/js_cookies.asp#:~:text=JavaScript%20can%20create%2C%20read%2C%20and,cookie%20property.
//I modified the setCookie() function to set the expiration date of the cookie to 11:59 pm on the current day
function setCookie(cname, cvalue) {
  const d = new Date();
  d.setHours(23, 59, 59, 999)
  let expires = "expires="+ d.toUTCString();
  document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}

function getCookie(cname) {
  let name = cname + "=";
  let decodedCookie = decodeURIComponent(document.cookie);
  let ca = decodedCookie.split(';');
  for(let i = 0; i <ca.length; i++) {
    let c = ca[i];
    while (c.charAt(0) == ' ') {
      c = c.substring(1);
    }
    if (c.indexOf(name) == 0) {
      return c.substring(name.length, c.length);
    }
  }
  return "";
}



function getCookieTime(){
  //Returns how much time is stored in the timewatched cookie (in seconds)
  //If there is no timewatched cookie, getCookie() will return a blank string
  //This function is called only once, 2 seconds after the page loads.
  
  var cookieTime = parseInt(getCookie("timewatched"))
  if(Number.isNaN(cookieTime)){
    //If there is no timewatched cookie, create one that's set to zero.
    console.log("Starting from zero...")
    cookieTime = 0;
    document.cookie = "timewatched:0"
  }
  return cookieTime;
}

function getTimeWatched(){
  //Returns how much time the user has spent watching YouTube,
  //including how much time was in the timewatched cookie at the time of the page load.
  
  //Add up all the times from the videoElement.played() array
  var playedLength = videoElement.played.length 
  var ttw = 0; //total time watched in seconds
  for(var i = 0; i < playedLength; i ++){
    ttw += videoElement.played.end(i) - videoElement.played.start(i)
  }
  
  //Last step: add the cookieTimeAtStart value to the sum.
  return ttw + cookieTimeAtStart;
}

function initiateTimer(){
  //This function is called 2 seconds after the page loads
  //The last line of code calls this function (see bottom)
  
  //Gets how many seconds are in the timewatched cookie at the start of the page load
  //If there is no timewatched cookie, one will be created and set to zero seconds
  cookieTimeAtStart = getCookieTime();
  
  
  videoElement = document.querySelectorAll("video")[0]
  pvideosrc = videoElement.src
  
  console.log("Cookie time at start: " + cookieTimeAtStart)
  
  totalTimeWatched = 0;
  
  //timerElement is the <p> element that will appear above the video that shows time spent watching.
  timerElement = document.createElement('p')
  timerElement.innerHTML = "this is the timer element" //This will be changed
  timerElement.setAttribute('style', 'font-size:15px; color:white')
  
  //divElement is a <div> element that already exists on the YouTube page. It will be the parent of the timerElement
  divElement = document.querySelectorAll('div[id="primary"][class="style-scope ytd-watch-flexy"]')[0]
  
  divElement.prepend(timerElement)
  
  updateTimer(); //updateTimer() is a recursive function that's called every 5 seconds
}

function updateTimer(){
  //This function is called every five seconds. It updates the totalTimeWatched variable
  
  
  //Check to see if the user has switched videos since the last timer update. If so,
  //recalculate cookieTimeAtStart
  //cookieTimeAtStart needs to be recalculated every time the user switches to a new video.
  //However, clicking on a new video does not refresh the YouTube page. As an alternative, we will
  //recalculate cookieTimeAtStart whenever the user clicks on anything that changes the src of the video element.
  videoElement = document.querySelectorAll("video")[0]
  var videosrc = videoElement.src
  if(videosrc !== pvideosrc){
    console.log("NEW PAGE!")
    cookieTimeAtStart = getCookieTime();
    console.log("Cookie time at start: " + cookieTimeAtStart)
  }
  pvideosrc = videosrc;
  
  totalTimeWatched = getTimeWatched();
  
  var ttwstr = Math.round(totalTimeWatched)
  
  //Store the total time watched in seconds in our timewatched cookie
  //The cookie will expire at midnight of the current day
  document.cookie = setCookie("timewatched", ttwstr.toString())
  
  //Update the timerElement to show the total time watched in minutes and seconds
  var minutes = Math.floor(totalTimeWatched / 60)
  var seconds = Math.round(totalTimeWatched) % 60
  seconds = seconds.toString()
  seconds = seconds.padStart(2,"0")
  timerElement.innerHTML = "Watch time today: " + minutes + ":" + seconds
  
  //If user has spent more than 30 minutes watching YouTube today, alert them.
  if(totalTimeWatched > max_time_in_minutes * 60 && totalTimeWatched < (max_time_in_minutes * 60) + 5)confirm("You have exceeded 30 minutes on YouTube today. Proceed?")
  
  //Call updateTimer() again in five seconds (recursive)
  setTimeout(updateTimer, 5000)
}

//initiateTimer() will be called 2 seconds after the page loads 
setTimeout(initiateTimer, 2000)



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source