Video Element Rate Controller Re-dux

Add keyboard shortcuts that will increase/decrease the playback rate for video elements.

当前为 2019-04-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Video Element Rate Controller Re-dux
  3. // @namespace https://github.com/mirnhoj/video-element-playbackrate-setter
  4. // @version 2.2
  5. // @description Add keyboard shortcuts that will increase/decrease the playback rate for video elements.
  6. // @include http*://*.youtube.com/*
  7. // @include http*://*.gfycat.com/*
  8. // @include http*://*.vimeo.com/*
  9. // @include https://www.facebook.com/video.php*
  10. // @include https://www.facebook.com/*/videos/*
  11. // @include https://www.kickstarter.com/*
  12. // @require https://cdnjs.cloudflare.com/ajax/libs/big.js/5.1.2/big.js
  13. // @grant GM_registerMenuCommand
  14.  
  15. // ==/UserScript==
  16. /* jshint esversion: 6 */
  17. //
  18. // if you want to extend the functionality of this script to other sites
  19. // besides youtube, add additional @include keys to the metadata block.
  20. //
  21. // if you want to change the default playback rate from 1x, change the line
  22. // "var currentPlaybackRate = 1;" to equal something other than 1, like 1.3 to
  23. // have all videos start playing at an increased speed, or 0.7 to have all
  24. // videos start playing at a decreased speed.
  25. //
  26. // if you want change the granularity of the playback rate adjustment, change
  27. // the line "var speedStep = 0.1;" to equal something other than 0.1, like 0.01
  28. // for more granular adjustments, or 0.25 for less granular adjustments.
  29.  
  30.  
  31. // These values are the default values for initialization
  32. let speedStep = 0.1;
  33. let displayTimeMilliSec = 1500;
  34. let enhancerExtention = true;
  35. let pref = 'preference-changed';
  36. let eventName = 'speed';
  37.  
  38. let timeoutID;
  39. const infobox = document.createElement('h1');
  40. let showDisplay = false;
  41. let keyIncreaseSpeed = ']';
  42. let keyReduceSpeed = '[';
  43. let keyResetSpeed = '\\';
  44.  
  45. function getVal(variable) {
  46. let value;
  47. let storage = (localStorage || (sessionStorage ||
  48. (window.content.localStorage ? window.content.localStorage : null)));
  49. try {
  50. switch (variable) {
  51. case 'speedStep':
  52. value = storage.getItem('VERCRspeedStep');
  53. // console.log('Variable "' + variable + '" is ' + value);
  54. return Number(value);
  55. case 'displayTimeMilliSec':
  56. value = storage.getItem('VERCRdisplayTimeMS');
  57. // console.log('Variable "' + variable + " is " + value);
  58. return Number(value);
  59. case 'enhancerExtention':
  60. value = storage.getItem('VERCRenhancerExtention');
  61. // console.log('Variable "' + variable + " is " + value);
  62. return value;
  63. case 'pref':
  64. value = storage.getItem('VERCRpref');
  65. // console.log('Variable "' + variable + " is " + value);
  66. return value;
  67. case 'eventName':
  68. value = storage.getItem('VERCReventName');
  69. // console.log('Variable "' + variable + " is " + value);
  70. return value;
  71. case 'keyIncreaseSpeed':
  72. return value;
  73. case 'keyReduceSpeed':
  74. return value;
  75. case 'keyResetSpeed':
  76. return value;
  77. default:
  78. return null;
  79. }
  80. } catch (e) {
  81. if (e.name === 'NS_ERROR_FILE_CORRUPTED') {
  82. storage = sessionStorage || null; // set the new storage if fails
  83. storage.setItem('VERCRspeedStep', speedStep);
  84. storage.setItem('VERCRdisplayTimeMS', displayTimeMilliSec);
  85. storage.setItem('VERCRenhancerExtention', enhancerExtention);
  86. storage.setItem('VERCRpref', pref);
  87. storage.setItem('VERCReventName', eventName);
  88. }
  89. }
  90. }
  91.  
  92. function setVal(variable, value) {
  93. let storage = (localStorage || (sessionStorage ||
  94. (window.content.localStorage ? window.content.localStorage : null)));
  95. try {
  96. switch (variable) {
  97. case 'speedStep':
  98. storage.setItem('VERCRspeedStep', Number(value));
  99. console.log(`Setting "${variable}" to ${value}`);
  100. return value;
  101. case 'displayTimeMilliSec':
  102. storage.setItem('VERCRdisplayTimeMS', Number(value));
  103. console.log(`Setting "${variable}" to ${value}`);
  104. return value;
  105. case 'enhancerExtention':
  106. storage.setItem('VERCRenhancerExtention', value);
  107. console.log(`Setting "${variable}" to ${value}`);
  108. return value;
  109. case 'pref':
  110. storage.setItem('VERCRpref', value);
  111. console.log(`Setting "${variable}" to ${value}`);
  112. return value;
  113. case 'eventName':
  114. storage.setItem('VERCReventName', value);
  115. console.log(`Setting "${variable}" to ${value}`);
  116. return value;
  117. default:
  118. return null;
  119. }
  120. } catch (e) {
  121. if (e.name === 'NS_ERROR_FILE_CORRUPTED') {
  122. storage = sessionStorage || null; // set the new storage if fails
  123. storage.setItem('VERCRspeedStep', speedStep);
  124. storage.setItem('VERCRdisplayTimeMS', displayTimeMilliSec);
  125. storage.setItem('VERCRenhancerExtention', enhancerExtention);
  126. storage.setItem('VERCRpref', pref);
  127. storage.setItem('VERCReventName', eventName);
  128. }
  129. }
  130. }
  131.  
  132. function GMsetup() {
  133. if (GM_registerMenuCommand) {
  134. GM_registerMenuCommand('Video Rate Re-dux: Set adjustment rate', () => {
  135. const curEntry = getVal('speedStep');
  136. speedStep = prompt('New adjustment rate:\n(e.g., 0.1 = 10% faster)', curEntry);
  137. if (speedStep !== null) {
  138. while (isNaN(speedStep)) {
  139. speedStep = prompt('Please input a valid number!\n\nNew adjustment rate:\n(e.g., 0.1 = 10% faster)', curEntry);
  140. }
  141. setVal('speedStep', speedStep);
  142. }
  143. });
  144. // GM_registerMenuCommand('Video Rate Re-dux: Set keyboard shortcuts', () => {
  145. // const curEntry = `${getVal('keyIncreaseSpeed')}, ${getVal('keyReduceSpeed')}, ${getVal('keyResetSpeed')}`;
  146. // // W.I.P.
  147. // });
  148. GM_registerMenuCommand('Video Rate Re-dux: Set display timeout', () => {
  149. const curEntry = getVal('displayTimeMilliSec');
  150. displayTimeMilliSec = prompt('New display timeout length (in milliseconds):', curEntry);
  151. if (displayTimeMilliSec !== null) {
  152. while (isNaN(displayTimeMilliSec)) {
  153. displayTimeMilliSec = prompt('Please input a valid number!\n\nNew display timeout length (in milliseconds):', curEntry);
  154. }
  155. setVal('displayTimeMilliSec', displayTimeMilliSec);
  156. }
  157. });
  158. GM_registerMenuCommand('Video Rate Re-dux: Set extention usage', () => {
  159. let curEntry = getVal('enhancerExtention');
  160. curEntry = curEntry === true ? 'Yes' : 'No';
  161. enhancerExtention = prompt('Are you using Enhancer for YouTube?:', curEntry);
  162. if (enhancerExtention !== null) {
  163. if (typeof (enhancerExtention) === 'string') {
  164. const regex = /ye?s?|true|i am/i;
  165. enhancerExtention = regex.test(enhancerExtention);
  166. const enhancerExtentionOutput = regex.test(enhancerExtention) === true ? 'Yes' : 'No';
  167. alert(`Extention use has been set to: "${enhancerExtentionOutput}"\n`);
  168. } else {
  169. enhancerExtention = false;
  170. }
  171. setVal('enhancerExtention', enhancerExtention);
  172. }
  173. });
  174. }
  175. }
  176.  
  177. function init() {
  178. let VERCRspeedStep = localStorage.getItem('VERCRspeedStep');
  179. let VERCRdisplayTimeMS = localStorage.getItem('VERCRdisplayTimeMS');
  180. let VERCRenhancerExtention = localStorage.getItem('VERCRenhancerExtention');
  181. let VERCRpref = localStorage.getItem('VERCRpref');
  182. let VERCReventName = localStorage.getItem('VERCReventName');
  183. if (!VERCRspeedStep) {
  184. VERCRspeedStep = speedStep;
  185. localStorage.setItem('VERCRspeedStep', Number(VERCRspeedStep));
  186. }
  187. if (!VERCRdisplayTimeMS) {
  188. VERCRdisplayTimeMS = displayTimeMilliSec;
  189. localStorage.setItem('VERCRdisplayTimeMS', Number(VERCRdisplayTimeMS));
  190. }
  191. if (!VERCRenhancerExtention) {
  192. VERCRenhancerExtention = enhancerExtention;
  193. localStorage.setItem('VERCRenhancerExtention', VERCRenhancerExtention);
  194. }
  195. if (!VERCRpref) {
  196. VERCRpref = pref;
  197. localStorage.setItem('VERCRpref', VERCRpref);
  198. }
  199. if (!VERCReventName) {
  200. VERCReventName = 'speed';
  201. localStorage.setItem('VERCReventName', VERCReventName);
  202. }
  203. }
  204.  
  205. function showInfobox(rate) {
  206. const bigRate = new Big(rate);
  207. // update rate indicator.
  208. infobox.innerHTML = `${bigRate}x`;
  209. // show infobox
  210. infobox.style.visibility = 'visible';
  211. // clear out any previous timers and have the infobox hide after the pre-set time period
  212. window.clearTimeout(timeoutID);
  213. timeoutID = window.setTimeout(() => {
  214. infobox.style.visibility = 'hidden';
  215. }, getVal('displayTimeMilliSec'));
  216. }
  217.  
  218. function setPlaybackRate(rate, shouldShowInfobox) {
  219. showDisplay = false;
  220. // grab the video elements and set their playback rate.
  221. const videoElement = document.getElementsByTagName('video')[0];
  222. videoElement.playbackRate = rate;
  223. // add infobox to dom if it doesn't already exist.
  224. if (videoElement && !document.getElementById('playbackrate-indicator')) {
  225. videoElement.parentElement.appendChild(infobox);
  226. }
  227. if (shouldShowInfobox) {
  228. showInfobox(rate);
  229.  
  230. }
  231. }
  232.  
  233. // mimic vlc keyboard shortcuts
  234. function addKeyListener() {
  235. window.addEventListener('keydown', (event) => {
  236. const key = event.key;
  237. const videoElement = document.getElementsByTagName('video')[0];
  238. let currentPlaybackRate = new Big(videoElement.playbackRate);
  239. speedStep = new Big(getVal('speedStep'));
  240. if (key === keyReduceSpeed) {
  241. // decrease playback rate if '[' is pressed
  242. // currentPlaybackRate -= speedStep;
  243. currentPlaybackRate = currentPlaybackRate.minus(speedStep);
  244. // console.log('Setting "currentPlaybackRate" to ' + currentPlaybackRate);
  245. showDisplay = true;
  246. setPlaybackRate(currentPlaybackRate, showDisplay);
  247. } else if (key === keyIncreaseSpeed) {
  248. // increase playback rate if ']' is pressed
  249. // currentPlaybackRate += speedStep;
  250. currentPlaybackRate = currentPlaybackRate.add(speedStep);
  251. // console.log('Setting "currentPlaybackRate" to ' + currentPlaybackRate);
  252. showDisplay = true;
  253. setPlaybackRate(currentPlaybackRate, showDisplay);
  254. } else if (key === keyResetSpeed) {
  255. // reset playback rate to default (1) if '\' is pressed
  256. currentPlaybackRate = 1;
  257. showDisplay = true;
  258. setPlaybackRate(currentPlaybackRate, showDisplay);
  259. }
  260. if (getVal('enhancerExtention') === true) {
  261. pref = getVal('pref');
  262. eventName = getVal('eventName');
  263. window.postMessage({
  264. enhancerforyoutube: pref,
  265. name: eventName,
  266. value: currentPlaybackRate,
  267. }, '*');
  268. }
  269. });
  270. }
  271.  
  272. // show the current speed display on the video when mouse wheel is rolled on the speed element if using Enhancer For Youtube
  273. function addWheelListener() {
  274. const enhancerToolbar = document.getElementById('enhancer-for-youtube-toolbar');
  275. const enhancerToolbarChildren = enhancerToolbar.children[0].children;
  276. let speedChild;
  277. eventName = getVal('eventName');
  278. for (let i = 0; i < enhancerToolbarChildren.length; i++) {
  279. if (enhancerToolbarChildren[i].dataset.name === eventName) {
  280. speedChild = enhancerToolbarChildren[i];
  281. }
  282. }
  283. if (speedChild) {
  284. speedChild.addEventListener('wheel', (event) => {
  285. const wDelta = event.wheelDelta < 0 ? 'down' : 'up';
  286. const videoElement = document.getElementsByTagName('video')[0];
  287. const currentPlaybackRate = videoElement.playbackRate;
  288. switch (wDelta) {
  289. case 'down':
  290. // currentPlaybackRate -= speedStep; // uncomment to actually modify the playback speed
  291. showInfobox(currentPlaybackRate);
  292. break;
  293. case 'up':
  294. // currentPlaybackRate += speedStep; // uncomment to actually modify the playback speed
  295. showInfobox(currentPlaybackRate);
  296. break;
  297. default:
  298. break;
  299. }
  300. });
  301. }
  302. }
  303.  
  304. function onExtentionReady() {
  305. addWheelListener();
  306. }
  307.  
  308. async function onReady() {
  309. addKeyListener();
  310. if (getVal('enhancerExtention') === true) {
  311. let i = 0;
  312. do {
  313. await wait(200);
  314. i++;
  315. } while (!document.getElementById('enhancer-for-youtube-toolbar') && i < 50);
  316. onExtentionReady(); // Or setTimeout(onReady, 0); if you want it consistently async
  317. }
  318. }
  319.  
  320. function wait(time) {
  321. return new Promise((resolve) => {
  322. setTimeout(() => {
  323. resolve();
  324. }, time);
  325. });
  326. }
  327.  
  328. function Ext_Detect_NotInstalled(ExtName, ExtID, obj) {
  329. console.log(`${ExtName} Not Installed`);
  330. setVal('enhancerExtention', false);
  331. obj.parentNode.removeChild(obj);
  332. }
  333.  
  334. function Ext_Detect_Installed(ExtName, ExtID, obj) {
  335. console.log(`${ExtName} Installed`);
  336. setVal('enhancerExtention', true);
  337. obj.parentNode.removeChild(obj);
  338. }
  339.  
  340. const Ext_Detect = function (ExtName, ExtID) {
  341. const IsFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
  342. const IsChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
  343. if (IsFirefox === true) {
  344. const sMoz = document.createElement('script');
  345. sMoz.onload = function () { Ext_Detect_Installed(ExtName, ExtID, sMoz); };
  346. sMoz.onerror = function () { Ext_Detect_NotInstalled(ExtName, ExtID, sMoz); };
  347. sMoz.src = `moz-extension://${ExtID}/resources/youtube-polymer.js`;
  348. document.body.appendChild(sMoz);
  349. } else if (IsChrome === true) {
  350. const sChrome = document.createElement('script');
  351. sChrome.onload = function () { Ext_Detect_Installed(ExtName, ExtID); };
  352. sChrome.onerror = function () { Ext_Detect_NotInstalled(ExtName, ExtID); };
  353. sChrome.src = `chrome-extension://${ExtID}/resources/youtube-polymer.js`;
  354. document.body.appendChild(sChrome);
  355. }
  356. };
  357.  
  358. function main() {
  359. init();
  360. // window.onload = function() { Ext_Detect("Enhancer for YouTube", addonID); };
  361. GMsetup();
  362. infobox.setAttribute('id', 'playbackrate-indicator');
  363. infobox.style.position = 'absolute';
  364. infobox.style.top = '10%';
  365. infobox.style.right = '10%';
  366. infobox.style.color = 'rgba(255, 0, 0, 1)';
  367. infobox.style.zIndex = '99999'; // ensures that it shows above other elements.
  368. infobox.style.visibility = 'hidden';
  369. infobox.style.marginTop = '3%';
  370. if (document.readyState !== 'loading') {
  371. onReady(); // Or setTimeout(onReady, 0); if you want it consistently async
  372. } else {
  373. document.addEventListener('DOMContentLoaded', onReady);
  374. }
  375. }
  376.  
  377. main();