// ==UserScript== // @name Video Element Rate Controller Re-dux // @namespace https://github.com/mirnhoj/video-element-playbackrate-setter // @version 2.2 // @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 let speedStep = 0.1; let displayTimeMilliSec = 1500; let enhancerExtention = true; let pref = 'preference-changed'; let eventName = 'speed'; let timeoutID; const infobox = document.createElement('h1'); let showDisplay = false; let keyIncreaseSpeed = ']'; let keyReduceSpeed = '['; let keyResetSpeed = '\\'; function getVal(variable) { let value; let storage = (localStorage || (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; case 'keyIncreaseSpeed': return value; case 'keyReduceSpeed': return value; case 'keyResetSpeed': return value; default: return null; } } catch (e) { if (e.name === 'NS_ERROR_FILE_CORRUPTED') { storage = 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) { let storage = (localStorage || (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 || 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 GMsetup() { if (GM_registerMenuCommand) { GM_registerMenuCommand('Video Rate Re-dux: Set adjustment rate', () => { const curEntry = getVal('speedStep'); 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 keyboard shortcuts', () => { // const curEntry = `${getVal('keyIncreaseSpeed')}, ${getVal('keyReduceSpeed')}, ${getVal('keyResetSpeed')}`; // // W.I.P. // }); GM_registerMenuCommand('Video Rate Re-dux: Set display timeout', () => { const curEntry = getVal('displayTimeMilliSec'); 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', () => { let curEntry = getVal('enhancerExtention'); curEntry = curEntry === true ? 'Yes' : 'No'; enhancerExtention = prompt('Are you using Enhancer for YouTube?:', curEntry); if (enhancerExtention !== null) { if (typeof (enhancerExtention) === 'string') { const regex = /ye?s?|true|i am/i; enhancerExtention = regex.test(enhancerExtention); const enhancerExtentionOutput = regex.test(enhancerExtention) === true ? 'Yes' : 'No'; alert(`Extention use has been set to: "${enhancerExtentionOutput}"\n`); } else { enhancerExtention = false; } setVal('enhancerExtention', enhancerExtention); } }); } } function init() { let VERCRspeedStep = localStorage.getItem('VERCRspeedStep'); let VERCRdisplayTimeMS = localStorage.getItem('VERCRdisplayTimeMS'); let VERCRenhancerExtention = localStorage.getItem('VERCRenhancerExtention'); let VERCRpref = localStorage.getItem('VERCRpref'); let 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 showInfobox(rate) { const bigRate = new Big(rate); // update rate indicator. infobox.innerHTML = `${bigRate}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(() => { infobox.style.visibility = 'hidden'; }, getVal('displayTimeMilliSec')); } function setPlaybackRate(rate, shouldShowInfobox) { showDisplay = false; // grab the video elements and set their playback rate. const 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); } } // mimic vlc keyboard shortcuts function addKeyListener() { window.addEventListener('keydown', (event) => { const key = event.key; const videoElement = document.getElementsByTagName('video')[0]; let currentPlaybackRate = new Big(videoElement.playbackRate); speedStep = new Big(getVal('speedStep')); if (key === keyReduceSpeed) { // decrease playback rate if '[' is pressed // currentPlaybackRate -= speedStep; currentPlaybackRate = currentPlaybackRate.minus(speedStep); // console.log('Setting "currentPlaybackRate" to ' + currentPlaybackRate); showDisplay = true; setPlaybackRate(currentPlaybackRate, showDisplay); } else if (key === keyIncreaseSpeed) { // increase playback rate if ']' is pressed // currentPlaybackRate += speedStep; currentPlaybackRate = currentPlaybackRate.add(speedStep); // console.log('Setting "currentPlaybackRate" to ' + currentPlaybackRate); showDisplay = true; setPlaybackRate(currentPlaybackRate, showDisplay); } else if (key === keyResetSpeed) { // reset playback rate to default (1) if '\' is pressed currentPlaybackRate = 1; showDisplay = true; setPlaybackRate(currentPlaybackRate, showDisplay); } if (getVal('enhancerExtention') === true) { pref = getVal('pref'); 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() { const enhancerToolbar = document.getElementById('enhancer-for-youtube-toolbar'); const enhancerToolbarChildren = enhancerToolbar.children[0].children; let speedChild; eventName = getVal('eventName'); for (let i = 0; i < enhancerToolbarChildren.length; i++) { if (enhancerToolbarChildren[i].dataset.name === eventName) { speedChild = enhancerToolbarChildren[i]; } } if (speedChild) { speedChild.addEventListener('wheel', (event) => { const wDelta = event.wheelDelta < 0 ? 'down' : 'up'; const videoElement = document.getElementsByTagName('video')[0]; const 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; default: break; } }); } } function onExtentionReady() { addWheelListener(); } async function onReady() { addKeyListener(); if (getVal('enhancerExtention') === true) { let 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 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); } const Ext_Detect = function (ExtName, ExtID) { const IsFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; const IsChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1; if (IsFirefox === true) { const 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 (IsChrome === true) { const 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() { 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); } } main();