Lit3Nitride / i-Achelois.videoPlayer

// ==UserScript==
// @name         i-Achelois.videoPlayer
// @namespace    https://zaw.li
// @version      0.1
// @description  Adds a variable speed slider into the (i-)NTULearn recorded lectures
// @author       Lit3Nitride
// @homepage     https://github.com/Lit3Nitride/i-Achelois
// @license GPL-3.0+; http://www.gnu.org/licenses/gpl-3.0.txt
// @match        https://*.ntu.edu.sg/aculearn*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Set up the variables.
    //
    // btn
    //    is one of the radio buttons that we'll
    //    keep and use to actually change the speed
    //
    // slider
    //    is the volume slider we'll insert
    //
    // sliderNum
    //    is the text box that displays the current speed
    //
    // sliderText
    //    is the rounded speed multiplied by 100
    //    so that we can display 2 decimal places
    var btn, slider, sliderNum, sliderText

    // This is the function that keeps checking
    // for the video player to be loaded before
    // starting the script proper
    (function load() {
        btn = document.getElementById("mep_0_rate_25")
        if (btn === null)
          setTimeout(load, 100)
        else
          init()
    })()

    // Setup of the rest of the script once the video player is loaded
    function init() {

      // Remove the annoying thumbnail nonsense
      document.getElementById("div_index").parentNode.removeChild(document.getElementById("div_index"))

      // The container of all the speed selectors
      var rateSelect = document.getElementsByClassName("mejs-rate-selector")[0]

      // Resize the container since we now need only to fit the slider
      rateSelect.style["height"] = "25px"
      rateSelect.style["width"] = "150px"
      rateSelect.style["margin-bottom"] = "-5px"

      // Make the first radio button invisible (without removing it)
      rateSelect.children[0].rows[0].style["display"] = "none"

      // Add in the slider and the current speed text box
      rateSelect.children[0].rows[1].innerHTML = '\
        <td>\
          <input type="range" min="52.5" max="200" value="100" id="btnSlider" step="any" style="width:80%;">\
        </td>\
        <td style="color: white">\
          <span id="sliderNum">1.00</span>×\
        </td>'

      // Assign the newly created elements into our pre-defined variables
      slider = document.getElementById("btnSlider")
      sliderNum = document.getElementById("sliderNum")

      // Remove the rest of the speed buttons
      for (var i=rateSelect.children[0].rows.length-1; i>1; i--)
          rateSelect.children[0].rows[i].remove()

      // Assign the trigger that handles speed change to changes of the slider
      rateSelect.children[0].rows[1].children[0].children[0].onmousedown = function() {trigger(true)}
      rateSelect.children[0].rows[1].children[0].children[0].onmouseup = trigger
      rateSelect.children[0].rows[1].children[0].children[0].onchange = trigger

      // Make the rate button a "reset" button (like how clicking on
      // the volume button mutes the video
      document.getElementsByClassName("mejs-rate-button")[0].children[0].onclick = function(){
        slider.value = 100
        doTrigger()
      }

    }

    // Precheck to see if the speed should be continually updated
    function trigger(triggerStart) {
      // Check if the mouse button is being held down
      // If so, keep updating the speed
      window.isTriggered = typeof triggerStart == "undefined" ? false:triggerStart
      setTimeout(doTrigger, 20)
    }

    // The actual changing of speed
    function doTrigger() {
      // We interpolate the slider so that
      //
      // 1. Have the values that correspond to the original speed buttons
      //    f(2) -> 8.00
      //    f(1) -> 1.00
      //    f(?) -> 0.25
      //
      // 2. Have |f'(1)| < f'(2) and f'(1) < f'( ? ) because when we are closer
      //    to speed 1.00, we want to fine-tune it more, but when we are further
      //    from 1.00, we just want the video to be really fast or really slow
      //    so we want the slider to change more further from there
      //
      // A polynomial curve of order 3 centered about x=1 fits this description,
      // then we solve for the parameters
      btn.value = 7*(slider.value/100 - 1)**3+1

      // Multiply the value by 100 and round it, which corresponds to 2 decimal places
      sliderText = Math.round(btn.value*100)

      // Print it out with the decimal point
      sliderNum.textContent = (sliderText < 100 ? 0:sliderText.toString().substr(0,1)) + "." + sliderText.toString().substr(sliderText >= 100)

      // Basically we've changed the value of the lone radio survivor into
      // the speed we want and click it
      btn.click()

      // Keep repeating if the mouse is still triggered
      if (window.isTriggered)
        setTimeout(doTrigger, 20)

    }
})();