// ==UserScript== // @name Video Element Rate Controller Re-dux // @namespace https://github.com/mirnhoj/video-element-playbackrate-setter // @version 2.1 // @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_registerMenuCommand // @downloadURL none // ==/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 the default values for initialization const speedStep = 0.1; const displayTimeMilliSec = 1500; const enhancerExtention = true; const pref = "preference-changed"; const eventName = "speed"; var timeoutID; 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:\n(e.g., 0.1 = 10% faster)', curEntry); if (speedStep != null) { while (isNaN(speedStep)) { speedStep = prompt('Please input a valid number!\n\nNew adjustment rate:\n(e.g., 0.1 = 10% faster)', 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); } }); } } function init() { var VERCRspeedStep = localStorage.getItem("VERCRspeedStep",); var VERCRdisplayTimeMS = localStorage.getItem("VERCRdisplayTimeMS"); var VERCRenhancerExtention = localStorage.getItem("VERCRenhancerExtention"); var VERCRpref = localStorage.getItem("VERCRpref"); var VERCReventName = localStorage.getItem("VERCReventName"); if (!VERCRspeedStep) { VERCRspeedStep = speedStep; localStorage.setItem("VERCRspeedStep", Number(VERCRspeedStep)); } if (!VERCRdisplayTimeMS) { VERCRdisplayTimeMS = displayTimeMilliSec; localStorage.setItem("VERCRdisplayTimeMS", Number(VERCRdisplayTimeMS)); } if (!VERCRenhancerExtention) { VERCRenhancerExtention = enhancerExtention; localStorage.setItem("VERCRenhancerExtention", VERCRenhancerExtention); } if (!VERCRpref) { VERCRpref = pref; localStorage.setItem("VERCRpref", VERCRpref); } if (!VERCReventName) { VERCReventName = "speed"; localStorage.setItem("VERCReventName", VERCReventName); } } function getVal(variable) { var value; var storage = (localStorage ? localStorage : (sessionStorage ? sessionStorage : (window.content.localStorage ? window.content.localStorage : null))); try { switch (variable) { case "speedStep": value = storage.getItem("VERCRspeedStep"); // console.log('Variable "' + variable + '" is ' + value); return Number(value); case "displayTimeMilliSec": value = storage.getItem("VERCRdisplayTimeMS"); // console.log('Variable "' + variable + " is " + value); return Number(value); case "enhancerExtention": value = storage.getItem("VERCRenhancerExtention"); // console.log('Variable "' + variable + " is " + value); return value; case "pref": value = storage.getItem("VERCRpref"); // console.log('Variable "' + variable + " is " + value); return value; case "eventName": value = storage.getItem("VERCReventName"); // console.log('Variable "' + variable + " is " + value); return value; default: return null; } } catch (e) { if (e.name == "NS_ERROR_FILE_CORRUPTED") { storage = sessionStorage ? sessionStorage : null; //set the new storage if fails storage.setItem("VERCRspeedStep", speedStep); storage.setItem("VERCRdisplayTimeMS", displayTimeMilliSec); storage.setItem("VERCRenhancerExtention", enhancerExtention); storage.setItem("VERCRpref", pref); storage.setItem("VERCReventName", eventName); } } } function setVal(variable, value) { var storage = (localStorage ? localStorage : (sessionStorage ? sessionStorage : (window.content.localStorage ? window.content.localStorage : null))); try { switch (variable) { case "speedStep": storage.setItem("VERCRspeedStep", Number(value)); console.log('Setting "' + variable + '" to ' + value); return value; case "displayTimeMilliSec": storage.setItem("VERCRdisplayTimeMS", Number(value)); console.log('Setting "' + variable + '" to ' + value); return value; case "enhancerExtention": storage.setItem("VERCRenhancerExtention", value); console.log('Setting "' + variable + '" to ' + value); return value; case "pref": storage.setItem("VERCRpref", value); console.log('Setting "' + variable + '" to ' + value); return value; case "eventName": storage.setItem("VERCReventName", value); console.log('Setting "' + variable + '" to ' + value); return value; default: return null; } } catch (e) { if (e.name == "NS_ERROR_FILE_CORRUPTED") { storage = sessionStorage ? sessionStorage : null; //set the new storage if fails storage.setItem("VERCRspeedStep", speedStep); storage.setItem("VERCRdisplayTimeMS", displayTimeMilliSec); storage.setItem("VERCRenhancerExtention", enhancerExtention); storage.setItem("VERCRpref", pref); storage.setItem("VERCReventName", eventName); } } } function setPlaybackRate(rate, shouldShowInfobox) { showDisplay = false; // grab the video elements and set their playback rate. var videoElement = document.getElementsByTagName("video")[0]; videoElement.playbackRate = rate; // add infobox to dom if it doesn't already exist. if (videoElement && !document.getElementById("playbackrate-indicator")) { videoElement.parentElement.appendChild(infobox); } if (shouldShowInfobox) { showInfobox(rate); } } function showInfobox(rate) { rate = new Big(rate); // update rate indicator. infobox.innerHTML = rate + "x"; // show infobox infobox.style.visibility = "visible"; // clear out any previous timers and have the infobox hide after the pre-set time period window.clearTimeout(timeoutID); timeoutID = window.setTimeout(function() { infobox.style.visibility = "hidden"; }, getVal("displayTimeMilliSec")); } // mimic vlc keyboard shortcuts function addKeyListener() { window.addEventListener('keydown', function(event) { var key = event.key; var videoElement = document.getElementsByTagName("video")[0]; var currentPlaybackRate = new Big(videoElement.playbackRate); var speedStep = new Big(getVal("speedStep")); switch (key) { // decrease playback rate if '[' is pressed case "[": // currentPlaybackRate -= speedStep; currentPlaybackRate = currentPlaybackRate.minus(speedStep); // console.log('Setting "currentPlaybackRate" to ' + currentPlaybackRate); showDisplay = true; setPlaybackRate(currentPlaybackRate, showDisplay); break; // increase playback rate if ']' is pressed case "]": // currentPlaybackRate += speedStep; currentPlaybackRate = currentPlaybackRate.add(speedStep); // console.log('Setting "currentPlaybackRate" to ' + currentPlaybackRate); 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 showInfobox(currentPlaybackRate); break; case "up": // currentPlaybackRate += speedStep; // uncomment to actually modify the playback speed showInfobox(currentPlaybackRate); 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"; // const addonID = "815f3dd0-c7e5-4c5a-b049-c6a74b325a7a"; 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); } }