Raw Source
akuma06 / Funimation and Crunchyroll Timer

// ==UserScript==
// @name         Funimation and Crunchyroll Timer
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  Displays a timer synced with the video playing.
// @author       akuma06
// @license      MIT
// @downloadURL  https://openuserjs.org/install/akuma06/Funimation_Timer.user.js
// @updateURL    https://openuserjs.org/meta/akuma06/Funimation_Timer.meta.js
// @match        https://www.funimation.com/*
// @match        https://static.crunchyroll.com/*
// @grant        none
// ==/UserScript==

(function () {
  'use strict';
  const defaultConfig = {
    color: '#FFFFFF', // Can be a color in RGB or a word like "white"
    background: 'rgba(0, 0, 0, 0)', // Background defined in RGB and an opacity value: rgba(red, green, blue, opacity) with opacity ranging from 0 to 1 and colors from 0 to 255
    fontSize: '40px', // Size for the displayed timer (00:00:00)
    top: undefined, // To position the timer relative to the top border of the player, it can't be defined if bottom is defined (in pixels)
    left: undefined, // To position the timer relative to the left border of the player, it can't be defined if right is defined (in pixels)
    bottom: '30px', // To position the timer relative to the bottom border of the player (in pixels)
    right: '20px', // To position the timer relative to the right border of the player (in pixels)
  };

  function createTimer(config) {
    const div = document.createElement("div");
    div.innerText = "00:00:00";
    div.style.position = "absolute";
    if (config.bottom !== undefined && config.bottom != '') {
      div.style.bottom = config.bottom;
    }
    else if (config.top !== undefined && config.top != '') {
      div.style.top = config.top;
    }
    if (config.right !== undefined && config.right != '') {
      div.style.right = config.right;
    }
    else if (config.left !== undefined && config.left != '') {
      div.style.left = config.left;
    }
    div.style.zIndex = 100004;
    div.style.fontSize = config.fontSize;
    div.style.color = config.color;
    div.style.background = config.background;
    div.style.pointerEvents = "none"; // disabling events so control bar works on players
    return div;
  }

  function updateTimer(timer, elapsed) {
    const hours = Math.floor(elapsed / 3600).toFixed();
    const minutes = Math.floor((elapsed - hours * 3600) / 60);
    const seconds = elapsed % 60;
    timer.innerText = `${(hours > 9) ? hours : "0" + hours}:${(minutes > 9) ? minutes : "0" + minutes}:${(seconds > 9) ? seconds : "0" + seconds}`;
  }

  function setupTimerFunimation(player, doc) {
    player.ready(() => {
      const timer = createTimer(defaultConfig);
      doc.querySelector("#brightcove-player").appendChild(timer);
      player.on("timeupdate", (e) => {
        const elapsed = player.currentTime().toFixed();
        updateTimer(timer, elapsed);
      });
    }, true);
  }

  function setupTimerCrunchyroll(player, doc) {
    const timer = createTimer(defaultConfig);
    doc.querySelector("#vilosRoot").appendChild(timer);
    player.addEventListener("timeupdate", (e) => {
      const elapsed = player.currentTime.toFixed();
      updateTimer(timer, elapsed);
    });
  }
  if (document.querySelector("#brightcove-player") !== null) {
    setupTimerFunimation(window.videojs("brightcove-player"), document);
  }
  else if (document.querySelector("#player") !== null) {
    document.querySelector("#player").addEventListener("load", () => {
      setupTimerFunimation(document.querySelector("#player").contentWindow.videojs("brightcove-player"), document.querySelector("#player").contentDocument);
    });
  }
  else if (document.querySelector("#player0") !== null) {
    setupTimerCrunchyroll(document.querySelector("#player0"), document)
  }
})();