// ==UserScript==
// @name Video Element Rate Controller Re-dux
// @namespace https://github.com/mirnhoj/video-element-playbackrate-setter
// @version 2.0
// @description Add keyboard shortcuts that will increase/decrease the playback rate for video elements.
// @include http*://*.youtube.com/*
// @include http*://*.gfycat.com/*
// @include http*://*.vimeo.com/*
// @include https://www.facebook.com/video.php*
// @include https://www.facebook.com/*/videos/*
// @include https://www.kickstarter.com/*
// @require https://cdnjs.cloudflare.com/ajax/libs/big.js/5.1.2/big.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// ==/UserScript==
/* jshint esversion: 6 */
//
// if you want to extend the functionality of this script to other sites
// besides youtube, add additional @include keys to the metadata block.
//
// if you want to change the default playback rate from 1x, change the line
// "var currentPlaybackRate = 1;" to equal something other than 1, like 1.3 to
// have all videos start playing at an increased speed, or 0.7 to have all
// videos start playing at a decreased speed.
//
// if you want change the granularity of the playback rate adjustment, change
// the line "var speedStep = 0.1;" to equal something other than 0.1, like 0.01
// for more granular adjustments, or 0.25 for less granular adjustments.
// These values are now set up elsewhere and are configurable in the extention settings
// var currentPlaybackRate = 1; // default playback rate.
// var speedStep = 0.125; // how much to modify speed for each keypress
// var displayTimeMilliSec = 1500; // how long to show the current speed indicator in milliseconds
// var enhancerExtention = true; // set to false if you aren't using Enhancer for Youtube
var infobox = document.createElement("h1");
var showDisplay = false;
main();
function GMsetup() {
if (GM_registerMenuCommand) {
GM_registerMenuCommand('Video Rate Re-dux: Set adjustment rate', function() {
var curEntry = getVal("speedStep") || "";
var speedStep = prompt('New adjustment rate:', curEntry);
if (speedStep != null) {
while (isNaN(speedStep)) {
speedStep = prompt('Please input a valid number!\n\nNew adjustment rate:', curEntry);
}
setVal("speedStep", speedStep);
}
});
GM_registerMenuCommand('Video Rate Re-dux: Set display timeout', function() {
var curEntry = getVal("displayTimeMilliSec") || "";
var displayTimeMilliSec = prompt('New display timeout length (in milliseconds):', curEntry);
if (displayTimeMilliSec != null) {
while (isNaN(displayTimeMilliSec)) {
displayTimeMilliSec = prompt('Please input a valid number!\n\nNew display timeout length (in milliseconds):', curEntry);
}
setVal("displayTimeMilliSec", displayTimeMilliSec);
}
});
GM_registerMenuCommand('Video Rate Re-dux: Set extention usage', function() {
var curEntry = getVal("enhancerExtention") || "";
curEntry = curEntry === true ? "Yes" : "No";
var enhancerExtention = prompt('Are you using Enhancer for YouTube?:', curEntry);
if (enhancerExtention != null) {
if (typeof(enhancerExtention) === "string") {
var regex = /ye?s?|true|i am/i;
enhancerExtention = regex.test(enhancerExtention);
var enhancerExtentionOutput = regex.test(enhancerExtention) === true ? "Yes" : "No";
alert('Extention use has been set to: "' + enhancerExtentionOutput + '"\n');
} else {
enhancerExtention = false;
}
setVal("enhancerExtention", enhancerExtention);
} else {
setVal("enhancerExtention", "No");
}
});
}
}
function init() {
var VERCRspeedStep = getVal("speedstep");
var VERCRdisplayTimeMS = getVal("displayTimeMilliSec");
var VERCRenhancerExtention = getVal("enhancerExtention");
var VERCRpref = getVal("pref");
var VERCReventName = getVal("eventName");
if (!VERCRspeedStep) {
VERCRspeedStep = 0.125;
setVal("speedstep", VERCRspeedStep);
}
if (!VERCRdisplayTimeMS) {
VERCRdisplayTimeMS = 1500;
setVal("displayTimeMilliSec", VERCRdisplayTimeMS);
}
if (!VERCRenhancerExtention) {
VERCRenhancerExtention = true;
setVal("enhancerExtention", VERCRenhancerExtention);
}
if (!VERCRpref) {
VERCRpref = "preference-changed";
setVal("pref", VERCRpref);
}
if (!VERCReventName) {
VERCReventName = "speed";
setVal("eventName", VERCReventName);
}
}
function getVal(variable) {
var storage = (localStorage ? localStorage : (window.content.localStorage ? window.content.localStorage : null));
try {
switch (variable) {
case "speedstep":
var speedStep = storage.getItem("VERCRspeedStep");
return speedStep;
case "displayTimeMilliSec":
var displayTimeMilliSec = storage.getItem("VERCRdisplayTimeMS");
return displayTimeMilliSec;
case "enhancerExtention":
var enhancerExtention = storage.getItem("VERCRenhancerExtention");
return enhancerExtention;
case "pref":
var pref = storage.getItem("VERCRpref");
return pref;
case "eventName":
var eventName = storage.getItem("VERCReventName");
return eventName;
default:
return null;
}
}
catch (e) {
if (e.name == "NS_ERROR_FILE_CORRUPTED") {
storage = sessionStorage ? sessionStorage : null;//set the new storage if fails
}
}
}
function setVal(variable, value) {
var storage = (localStorage ? localStorage : (window.content.localStorage ? window.content.localStorage : null));
try {
switch (variable) {
case "speedstep":
storage.setItem("VERCRspeedStep", Big(value));
break;
case "displayTimeMilliSec":
storage.setItem("VERCRdisplayTimeMS", value);
break;
case "enhancerExtention":
storage.setItem("VERCRenhancerExtention", value);
break;
case "pref":
storage.setItem("VERCRpref", value);
break;
case "eventName":
storage.setItem("VERCReventName", value);
break;
default:
break;
}
}
catch (e) {
if (e.name == "NS_ERROR_FILE_CORRUPTED") {
storage = sessionStorage ? sessionStorage : null;//set the new storage if fails
}
}
}
function setPlaybackRate(rate, showInfobox) {
rate = new Big(rate);
// grab the video elements and set their playback rate.
var videoElement = document.getElementsByTagName("video")[0];
videoElement.playbackRate = rate;
infobox.innerHTML = rate + "x";
// add infobox to dom if it doesn't already exist.
if (videoElement && !document.getElementById("playbackrate-indicator")) {
videoElement.parentElement.appendChild(infobox);
}
// show infobox and update rate indicator.
if (showInfobox) {
infobox.style.visibility = "visible";
// clear out any previous timers and have the infobox hide after the pre-set time period
var timeoutID;
window.clearTimeout(timeoutID);
timeoutID = window.setTimeout(function() {
infobox.style.visibility = "hidden";
}, getVal("displayTimeMilliSec"));
showDisplay = false;
}
}
// mimic vlc keyboard shortcuts
function addKeyListener() {
window.addEventListener('keydown', function(event) {
var key = event.key;
var videoElement = document.getElementsByTagName("video")[0];
var currentPlaybackRate = videoElement.playbackRate;
var speedStep = getVal("speedStep");
switch (key) {
// decrease playback rate if '[' is pressed
case "[":
currentPlaybackRate -= speedStep;
showDisplay = true;
setPlaybackRate(currentPlaybackRate, showDisplay);
break;
// increase playback rate if ']' is pressed
case "]":
currentPlaybackRate += speedStep;
showDisplay = true;
setPlaybackRate(currentPlaybackRate, showDisplay);
break;
default:
return null;
}
if (getVal("enhancerExtention") === true) {
var pref = getVal("pref");
var eventName = getVal("eventName");
window.postMessage({
enhancerforyoutube: pref,
name: eventName,
value: currentPlaybackRate
}, "*");
}
});
}
// show the current speed display on the video when mouse wheel is rolled on the speed element if using Enhancer For Youtube
function addWheelListener() {
var enhancerToolbar = document.getElementById("enhancer-for-youtube-toolbar");
var enhancerToolbarChildren = enhancerToolbar.children[0].children;
var eventName = getVal("eventName");
for (var i = 0; i < enhancerToolbarChildren.length; i++) {
if (enhancerToolbarChildren[i].dataset.name === eventName) {
var speedChild = enhancerToolbarChildren[i];
}
}
if (speedChild) {
speedChild.addEventListener('wheel', function(event) {
var wDelta = event.wheelDelta < 0 ? 'down' : 'up';
var videoElement = document.getElementsByTagName("video")[0];
var currentPlaybackRate = videoElement.playbackRate;
switch (wDelta) {
case "down":
// currentPlaybackRate -= speedStep; // uncomment to actually modify the playback speed
showDisplay = true;
setPlaybackRate(currentPlaybackRate, showDisplay);
break;
case "up":
// currentPlaybackRate += speedStep; // uncomment to actually modify the playback speed
showDisplay = true;
setPlaybackRate(currentPlaybackRate, showDisplay);
break;
}
});
}
}
async function onReady() {
addKeyListener();
if (getVal("enhancerExtention") === true) {
var i = 0;
do {
await wait(200);
i++
} while (!document.getElementById("enhancer-for-youtube-toolbar") && i < 50);
onExtentionReady(); // Or setTimeout(onReady, 0); if you want it consistently async
}
}
function wait(time) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, time);
});
}
function onExtentionReady() {
addWheelListener();
}
function Ext_Detect_NotInstalled(ExtName, ExtID, obj) {
console.log(ExtName + ' Not Installed');
setVal("enhancerExtention", false);
obj.parentNode.removeChild(obj);
}
function Ext_Detect_Installed(ExtName, ExtID, obj) {
console.log(ExtName + ' Installed');
setVal("enhancerExtention", true);
obj.parentNode.removeChild(obj);
}
var Ext_Detect = function(ExtName, ExtID) {
var is_firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
if (is_firefox == true) {
var sMoz = document.createElement('script');
sMoz.onload = function() { Ext_Detect_Installed(ExtName, ExtID, sMoz); };
sMoz.onerror = function() { Ext_Detect_NotInstalled(ExtName, ExtID, sMoz); };
sMoz.src = 'moz-extension://' + ExtID + '/resources/youtube-polymer.js';
document.body.appendChild(sMoz);
} else if (is_chrome == true) {
var sChrome = document.createElement('script');
sChrome.onload = function() { Ext_Detect_Installed(ExtName, ExtID); };
sChrome.onerror = function() { Ext_Detect_NotInstalled(ExtName, ExtID); };
sChrome.src = 'chrome-extension://' + ExtID + '/resources/youtube-polymer.js';
document.body.appendChild(sChrome);
}
}
function main() {
const addonID = "397abcaa-dabc-4cd2-b808-0df445170b78";
init();
window.onload = function() { Ext_Detect("Enhancer for YouTube", addonID); };
GMsetup();
infobox.setAttribute("id", "playbackrate-indicator");
infobox.style.position = "absolute";
infobox.style.top = "10%";
infobox.style.right = "10%";
infobox.style.color = "rgba(255, 0, 0, 1)";
infobox.style.zIndex = "99999"; // ensures that it shows above other elements.
infobox.style.visibility = "hidden";
infobox.style.marginTop = "3%";
if (document.readyState !== "loading") {
onReady(); // Or setTimeout(onReady, 0); if you want it consistently async
} else {
document.addEventListener("DOMContentLoaded", onReady);
}
}