From e24b95dd8461ffafcb99870548ba75fcf5af9eab Mon Sep 17 00:00:00 2001 From: Can Arslan Date: Wed, 12 Dec 2018 07:31:44 +0300 Subject: [PATCH] Allow user to define custom shortcuts #347 (#399) - Over-all working structure changed - tc.settings.keyBindings array added - Options page changed Clean up of #350 --- inject.js | 198 ++++++++++++++++++++++++++++++++++++------------- manifest.json | 2 +- options.css | 12 ++- options.html | 113 +++++++++++++++++----------- options.js | 202 +++++++++++++++++++++++++++++++++----------------- 5 files changed, 357 insertions(+), 170 deletions(-) diff --git a/inject.js b/inject.js index 89f27f6..5aab37e 100644 --- a/inject.js +++ b/inject.js @@ -2,20 +2,31 @@ chrome.runtime.sendMessage({}, function(response) { var tc = { settings: { speed: 1.0, // default 1x - resetSpeed: 1.0, // default 1x - speedStep: 0.1, // default 0.1x - fastSpeed: 1.8, // default 1.8x - rewindTime: 10, // default 10s - advanceTime: 10, // default 10s - resetKeyCode: 82, // default: R - slowerKeyCode: 83, // default: S - fasterKeyCode: 68, // default: D - rewindKeyCode: 90, // default: Z - advanceKeyCode: 88, // default: X + + /** + * these are not used and deprecated, will be removed in next update + * but should be stay there because chrome.storage.sync.get needs them + */ + resetSpeed: 1.0, // default 1.0 + speedStep: null, // default 0.1x just for buttons + fastSpeed: null, // default 1.8x + rewindTime: null, // default 10s just for buttons + advanceTime: null, // default 10s just for buttons + resetKeyCode: null, // default: R + slowerKeyCode: null, // default: S + fasterKeyCode: null, // default: D + rewindKeyCode: null, // default: Z + advanceKeyCode: null, // default: X + fastKeyCode: null, // default: G + /** + * these(above) are not used and deprecated, will be removed in next update + * but should be stay there because chrome.storage.sync.get needs them. + */ + displayKeyCode: 86, // default: V - fastKeyCode: 71, // default: G rememberSpeed: false, // default: false startHidden: false, // default: false + keyBindings: [], blacklist: ` www.instagram.com twitter.com @@ -25,20 +36,66 @@ chrome.runtime.sendMessage({}, function(response) { } }; - chrome.storage.sync.get(tc.settings, function(storage) { + chrome.storage.sync.get(tc.settings, function (storage) { + tc.settings.keyBindings = storage.keyBindings; // Array + if (storage.keyBindings.length == 0) // if first initialization of 0.5.3 + { + // UPDATE + tc.settings.keyBindings.push({ + action: "slower", + key: Number(storage.slowerKeyCode) || 83, + value: Number(storage.speedStep) || 0.1, + force: false, + predefined: true + }); // default S + tc.settings.keyBindings.push({ + action: "faster", + key: Number(storage.fasterKeyCode) || 68, + value: Number(storage.speedStep) || 0.1, + force: false, + predefined: true + }); // default: D + tc.settings.keyBindings.push({ + action: "rewind", + key: Number(storage.rewindKeyCode) || 90, + value: Number(storage.rewindTime) || 10, + force: false, + predefined: true + }); // default: Z + tc.settings.keyBindings.push({ + action: "advance", + key: Number(storage.advanceKeyCode) || 88, + value: Number(storage.advanceTime) || 10, + force: false, + predefined: true + }); // default: X + tc.settings.keyBindings.push({ + action: "reset", + key: Number(storage.resetKeyCode) || 82, + value: 1.0, + force: false, + predefined: true + }); // default: R + tc.settings.keyBindings.push({ + action: "fast", + key: Number(storage.fastKeyCode) || 71, + value: Number(storage.fastSpeed) || 1.8, + force: false, + predefined: true + }); // default: G + tc.settings.version = "0.5.3"; + + chrome.storage.sync.set({ + keyBindings: tc.settings.keyBindings, + version: tc.settings.version, + displayKeyCode: tc.settings.displayKeyCode, + rememberSpeed: tc.settings.rememberSpeed, + startHidden: tc.settings.startHidden, + blacklist: tc.settings.blacklist.replace(/^\s+|\s+$/gm, '') + }); + } tc.settings.speed = Number(storage.speed); - tc.settings.resetSpeed = Number(storage.resetSpeed); - tc.settings.speedStep = Number(storage.speedStep); - tc.settings.fastSpeed = Number(storage.fastSpeed); - tc.settings.rewindTime = Number(storage.rewindTime); - tc.settings.advanceTime = Number(storage.advanceTime); - tc.settings.resetKeyCode = Number(storage.resetKeyCode); - tc.settings.rewindKeyCode = Number(storage.rewindKeyCode); - tc.settings.slowerKeyCode = Number(storage.slowerKeyCode); - tc.settings.fasterKeyCode = Number(storage.fasterKeyCode); - tc.settings.fastKeyCode = Number(storage.fastKeyCode); tc.settings.displayKeyCode = Number(storage.displayKeyCode); - tc.settings.advanceKeyCode = Number(storage.advanceKeyCode); tc.settings.rememberSpeed = Boolean(storage.rememberSpeed); tc.settings.startHidden = Boolean(storage.startHidden); tc.settings.blacklist = String(storage.blacklist); @@ -48,6 +105,18 @@ chrome.runtime.sendMessage({}, function(response) { var forEach = Array.prototype.forEach; + function getKeyBindings(action, what = "value") { + try { + return tc.settings.keyBindings.find(item => item.action === action)[what]; + } catch (e) { + return false; + } + } + + function setKeyBindings(action, value) { + tc.settings.keyBindings.find(item => item.action === action)["value"] = value; + } + function defineVideoController() { tc.videoController = function(target, parent) { if (target.dataset['vscid']) { @@ -60,7 +129,7 @@ chrome.runtime.sendMessage({}, function(response) { this.id = Math.random().toString(36).substr(2, 9); if (!tc.settings.rememberSpeed) { tc.settings.speed = 1.0; - tc.settings.resetSpeed = tc.settings.fastSpeed; + setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed } this.initializeControls(); @@ -130,7 +199,7 @@ chrome.runtime.sendMessage({}, function(response) { forEach.call(shadow.querySelectorAll('button'), function(button) { button.onclick = (e) => { - runAction(e.target.dataset['action'], document, false, e); + runAction(e.target.dataset['action'], document, getKeyBindings(e.target.dataset['action']), e); } }); @@ -225,7 +294,7 @@ chrome.runtime.sendMessage({}, function(response) { docs.push(window.top.document); } catch (e) { } - + docs.forEach(function(doc) { doc.addEventListener('keydown', function(event) { var keyCode = event.keyCode; @@ -242,28 +311,23 @@ chrome.runtime.sendMessage({}, function(response) { } // Ignore keydown event if typing in an input box - if ((document.activeElement.nodeName === 'INPUT' - && document.activeElement.getAttribute('type') === 'text') + if (document.activeElement.nodeName === 'INPUT' || document.activeElement.nodeName === 'TEXTAREA' || document.activeElement.isContentEditable) { return false; } - if (keyCode == tc.settings.rewindKeyCode) { - runAction('rewind', document, true) - } else if (keyCode == tc.settings.advanceKeyCode) { - runAction('advance', document, true) - } else if (keyCode == tc.settings.fasterKeyCode) { - runAction('faster', document, true) - } else if (keyCode == tc.settings.slowerKeyCode) { - runAction('slower', document, true) - } else if (keyCode == tc.settings.resetKeyCode) { - runAction('reset', document, true) - } else if (keyCode == tc.settings.displayKeyCode) { + if (keyCode == tc.settings.displayKeyCode) { runAction('display', document, true) - } else if (keyCode == tc.settings.fastKeyCode) { - runAction('fast', document, true); } + var item = tc.settings.keyBindings.find(item => item.key === keyCode); + if (item) { + runAction(item.action, document, item.value); + if (item.force === "true") {// disable websites key bindings + event.preventDefault(); + event.stopPropagation(); + } + } return false; }, true); @@ -324,7 +388,7 @@ chrome.runtime.sendMessage({}, function(response) { }); } - function runAction(action, document, keyboard, e) { + function runAction(action, document, value, e) { var videoTags = document.getElementsByTagName('video'); videoTags.forEach = Array.prototype.forEach; @@ -336,18 +400,18 @@ chrome.runtime.sendMessage({}, function(response) { if (!v.classList.contains('vsc-cancelled')) { if (action === 'rewind') { - v.currentTime -= tc.settings.rewindTime; + v.currentTime -= value; } else if (action === 'advance') { - v.currentTime += tc.settings.advanceTime; + v.currentTime += value; } else if (action === 'faster') { // Maximum playback speed in Chrome is set to 16: // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/html/media/HTMLMediaElement.cpp?l=168 - var s = Math.min( (v.playbackRate < 0.1 ? 0.0 : v.playbackRate) + tc.settings.speedStep, 16); + var s = Math.min((v.playbackRate < 0.1 ? 0.0 : v.playbackRate) + value, 16); v.playbackRate = Number(s.toFixed(2)); } else if (action === 'slower') { // Video min rate is 0.0625: // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/html/media/HTMLMediaElement.cpp?l=167 - var s = Math.max(v.playbackRate - tc.settings.speedStep, 0.07); + var s = Math.max(v.playbackRate - value, 0.07); v.playbackRate = Number(s.toFixed(2)); } else if (action === 'reset') { resetSpeed(v, 1.0); @@ -357,33 +421,61 @@ chrome.runtime.sendMessage({}, function(response) { } else if (action === 'drag') { handleDrag(v, controller, e); } else if (action === 'fast') { - resetSpeed(v, tc.settings.fastSpeed); + resetSpeed(v, value); + } else if (action === 'pause') { + pauseSpeed(v, value); + } else if (action === 'muted') { + muted(v, value); } } }); } + function pauseSpeed(v, target) { + // not working as expected in youtube for now + if (v.playbackRate === target) { + v.play() + } + resetSpeed(v, target) + } + function resetSpeed(v, target) { if (v.playbackRate === target) { - if(v.playbackRate === tc.settings.resetSpeed) - { + if (v.playbackRate === getKeyBindings("reset")) { // resetSpeed if (target !== 1.0) { v.playbackRate = 1.0; } else { - v.playbackRate = tc.settings.fastSpeed; + v.playbackRate = getKeyBindings("fast"); // fastSpeed } } else { - v.playbackRate = tc.settings.resetSpeed; + v.playbackRate = getKeyBindings("reset"); // resetSpeed } } else { - tc.settings.resetSpeed = v.playbackRate; - chrome.storage.sync.set({'resetSpeed': v.playbackRate}); + setKeyBindings("reset", v.playbackRate);// resetSpeed + // chrome.storage.sync.set({'resetSpeed': v.playbackRate}); v.playbackRate = target; } } + function muted(v, value) { + v.muted = v.muted !== true; //reverse muted status + /* this can be used if someone wants just mute button + switch (value) { + case 2: + v.muted = false; + break; + case 1: + v.muted = true; + break; + default: + v.muted = v.muted !== true; + break; + } + */ + } + function handleDrag(video, controller, e) { const shadowController = controller.shadowRoot.querySelector('#controller'); diff --git a/manifest.json b/manifest.json index 22948d8..ad5de12 100755 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "Video Speed Controller", "short_name": "videospeed", - "version": "0.5.2", + "version": "0.5.3", "manifest_version": 2, "description": "Speed up, slow down, advance and rewind any HTML5 video with quick shortcuts.", "homepage_url": "https://github.com/igrigorik/videospeed", diff --git a/options.css b/options.css index dd72980..22ad04e 100644 --- a/options.css +++ b/options.css @@ -84,10 +84,6 @@ label { vertical-align: top; } -label[for=rememberSpeed] { - width: 200px; -} - #status { color: #9D9D9D; display: inline-block; @@ -97,3 +93,11 @@ label[for=rememberSpeed] { #faq { margin-top: 2em; } + +select { + width: 170px; +} + +.customForce { + display: none; +} \ No newline at end of file diff --git a/options.html b/options.html index 5c73e73..1b3c0eb 100644 --- a/options.html +++ b/options.html @@ -10,59 +10,81 @@

Video Speed Controller

-
+

Shortcuts

-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+ +

Other

-
- - -
-
- - -
-
- - -
-
- - +
+ +
@@ -75,7 +97,8 @@
- + +
diff --git a/options.js b/options.js index c1136e0..165b945 100644 --- a/options.js +++ b/options.js @@ -1,32 +1,35 @@ var tcDefaults = { - speed: 1.0, // default 1x - speedStep: 0.1, // default 0.1x - rewindTime: 10, // default 10s - advanceTime: 10, // default 10s - fastSpeed: 1.8, // default 1.8x - resetKeyCode: 82, // default: R - slowerKeyCode: 83, // default: S - fasterKeyCode: 68, // default: D - fastKeyCode: 71, // default: G - rewindKeyCode: 90, // default: Z - advanceKeyCode: 88, // default: X + speed: 1.0, // default: displayKeyCode: 86, // default: V rememberSpeed: false, // default: false startHidden: false, // default: false + keyBindings: [ + {action: "slower", key: 83, value: 0.1, force: false, predefined: true}, // S + {action: "faster", key: 68, value: 0.1, force: false, predefined: true}, // D + {action: "rewind", key: 90, value: 10, force: false, predefined: true}, // Z + {action: "advance", key: 88, value: 10, force: false, predefined: true}, // X + {action: "reset", key: 82, value: 1, force: false, predefined: true}, // R + {action: "fast", key: 71, value: 1.8, force: false, predefined: true} // G + ], blacklist: ` www.instagram.com twitter.com vine.co imgur.com - `.replace(/^\s+|\s+$/gm,'') + `.replace(/^\s+|\s+$/gm, '') }; +var keyBindings = []; + var keyCodeAliases = { - 32: 'Space', - 96: 'Num 0', - 97: 'Num 1', - 98: 'Num 2', - 99: 'Num 3', + 0: 'null', + null: 'null', + undefined: 'null', + 32: 'Space', + 96: 'Num 0', + 97: 'Num 1', + 98: 'Num 2', + 99: 'Num 3', 100: 'Num 4', 101: 'Num 5', 102: 'Num 6', @@ -64,6 +67,9 @@ function recordKeyPress(e) { e.stopPropagation(); } else if (e.keyCode === 8) { // Clear input when backspace pressed e.target.value = ''; + } else if (e.keyCode === 27) { // When esc clicked, clear input + e.target.value = 'null'; + e.target.keyCode = null; } }; @@ -88,50 +94,64 @@ function updateShortcutInputText(inputId, keyCode) { document.getElementById(inputId).keyCode = keyCode; } +function updateCustomShortcutInputText(inputItem, keyCode) { + inputItem.value = keyCodeAliases[keyCode] || String.fromCharCode(keyCode); + inputItem.keyCode = keyCode; +} + +function add_shortcut() { + var html = ` + + + + `; + var div = document.createElement('div'); + div.setAttribute('class', 'row customs'); + div.innerHTML = html; + var customs_element = document.getElementById("customs"); + customs_element.insertBefore(div, customs_element.children[customs_element.childElementCount - 1]); +} + +function createKeyBindings(item) { + const action = item.querySelector(".customDo").value; + const key = item.querySelector(".customKey").keyCode; + const value = Number(item.querySelector(".customValue").value); + const force = item.querySelector(".customForce").value; + const predefined = !!item.id;//item.id ? true : false; + + keyBindings.push({action: action, key: key, value: value, force: force, predefined: predefined}); +} + // Saves options to chrome.storage function save_options() { + keyBindings = []; + Array.from(document.querySelectorAll(".customs")).forEach(item => createKeyBindings(item)); // Remove added shortcuts - var speedStep = document.getElementById('speedStep').value; - var rewindTime = document.getElementById('rewindTime').value; - var advanceTime = document.getElementById('advanceTime').value; - var fastSpeed = document.getElementById('fastSpeed').value; - var resetKeyCode = document.getElementById('resetKeyInput').keyCode; - var rewindKeyCode = document.getElementById('rewindKeyInput').keyCode; - var advanceKeyCode = document.getElementById('advanceKeyInput').keyCode; - var slowerKeyCode = document.getElementById('slowerKeyInput').keyCode; - var fasterKeyCode = document.getElementById('fasterKeyInput').keyCode; - var fastKeyCode = document.getElementById('fastKeyInput').keyCode; var displayKeyCode = document.getElementById('displayKeyInput').keyCode; var rememberSpeed = document.getElementById('rememberSpeed').checked; var startHidden = document.getElementById('startHidden').checked; var blacklist = document.getElementById('blacklist').value; - speedStep = isNaN(speedStep) ? tcDefaults.speedStep : Number(speedStep); - rewindTime = isNaN(rewindTime) ? tcDefaults.rewindTime : Number(rewindTime); - advanceTime = isNaN(advanceTime) ? tcDefaults.advanceTime : Number(advanceTime); - fastSpeed = isNaN(fastSpeed) ? tcDefaults.fastSpeed : Number(fastSpeed); - resetKeyCode = isNaN(resetKeyCode) ? tcDefaults.resetKeyCode : resetKeyCode; - rewindKeyCode = isNaN(rewindKeyCode) ? tcDefaults.rewindKeyCode : rewindKeyCode; - advanceKeyCode = isNaN(advanceKeyCode) ? tcDefaults.advanceKeyCode : advanceKeyCode; - slowerKeyCode = isNaN(slowerKeyCode) ? tcDefaults.slowerKeyCode : slowerKeyCode; - fasterKeyCode = isNaN(fasterKeyCode) ? tcDefaults.fasterKeyCode : fasterKeyCode; - fastKeyCode = isNaN(fastKeyCode) ? tcDefaults.fastKeyCode : fastKeyCode; displayKeyCode = isNaN(displayKeyCode) ? tcDefaults.displayKeyCode : displayKeyCode; + chrome.storage.sync.remove(["resetSpeed", "speedStep", "fastSpeed", "rewindTime", "advanceTime", "resetKeyCode", "slowerKeyCode", "fasterKeyCode", "rewindKeyCode", "advanceKeyCode", "fastKeyCode"]); chrome.storage.sync.set({ - speedStep: speedStep, - rewindTime: rewindTime, - advanceTime: advanceTime, - fastSpeed: fastSpeed, - resetKeyCode: resetKeyCode, - rewindKeyCode: rewindKeyCode, - advanceKeyCode: advanceKeyCode, - slowerKeyCode: slowerKeyCode, - fasterKeyCode: fasterKeyCode, - fastKeyCode: fastKeyCode, displayKeyCode: displayKeyCode, rememberSpeed: rememberSpeed, startHidden: startHidden, + keyBindings: keyBindings, blacklist: blacklist.replace(/^\s+|\s+$/gm,'') }, function() { // Update status to let user know options were saved. @@ -146,26 +166,41 @@ function save_options() { // Restores options from chrome.storage function restore_options() { chrome.storage.sync.get(tcDefaults, function(storage) { - document.getElementById('speedStep').value = storage.speedStep.toFixed(2); - document.getElementById('rewindTime').value = storage.rewindTime; - document.getElementById('advanceTime').value = storage.advanceTime; - document.getElementById('fastSpeed').value = storage.fastSpeed; - updateShortcutInputText('resetKeyInput', storage.resetKeyCode); - updateShortcutInputText('rewindKeyInput', storage.rewindKeyCode); - updateShortcutInputText('advanceKeyInput', storage.advanceKeyCode); - updateShortcutInputText('slowerKeyInput', storage.slowerKeyCode); - updateShortcutInputText('fasterKeyInput', storage.fasterKeyCode); - updateShortcutInputText('fastKeyInput', storage.fastKeyCode); updateShortcutInputText('displayKeyInput', storage.displayKeyCode); document.getElementById('rememberSpeed').checked = storage.rememberSpeed; document.getElementById('startHidden').checked = storage.startHidden; document.getElementById('blacklist').value = storage.blacklist; + + for (let i in storage.keyBindings) { + var item = storage.keyBindings[i]; + if (item.predefined) { + //do predefined ones because their value needed for overlay + // document.querySelector("#" + item["action"] + " .customDo").value = item["action"]; + updateCustomShortcutInputText(document.querySelector("#" + item["action"] + " .customKey"), item["key"]); + document.querySelector("#" + item["action"] + " .customValue").value = item["value"]; + document.querySelector("#" + item["action"] + " .customForce").value = item["force"]; + } + else { + // new ones + add_shortcut(); + const dom = document.querySelector(".customs:last-of-type") + dom.querySelector(".customDo").value = item["action"]; + + if (item["action"] === "pause" || item["action"] === "muted") + dom.querySelector(".customValue").disabled = true; + + updateCustomShortcutInputText(dom.querySelector(".customKey"), item["key"]); + dom.querySelector(".customValue").value = item["value"]; + dom.querySelector(".customForce").value = item["force"]; + } + } }); } function restore_defaults() { chrome.storage.sync.set(tcDefaults, function() { restore_options(); + document.querySelectorAll(".removeParent").forEach(button => button.click()); // Remove added shortcuts // Update status to let user know options were saved. var status = document.getElementById('status'); status.textContent = 'Default options restored'; @@ -175,6 +210,10 @@ function restore_defaults() { }); } +function show_experimental() { + document.querySelectorAll(".customForce").forEach(item => item.style.display = 'inline-block'); +} + function initShortcutInput(inputId) { document.getElementById(inputId).addEventListener('focus', inputFocus); document.getElementById(inputId).addEventListener('blur', inputBlur); @@ -185,18 +224,47 @@ document.addEventListener('DOMContentLoaded', function () { restore_options(); document.getElementById('save').addEventListener('click', save_options); + document.getElementById('add').addEventListener('click', add_shortcut); document.getElementById('restore').addEventListener('click', restore_defaults); + document.getElementById('experimental').addEventListener('click', show_experimental); - initShortcutInput('resetKeyInput'); - initShortcutInput('rewindKeyInput'); - initShortcutInput('advanceKeyInput'); - initShortcutInput('slowerKeyInput'); - initShortcutInput('fasterKeyInput'); - initShortcutInput('fastKeyInput'); initShortcutInput('displayKeyInput'); - document.getElementById('rewindTime').addEventListener('keypress', inputFilterNumbersOnly); - document.getElementById('advanceTime').addEventListener('keypress', inputFilterNumbersOnly); - document.getElementById('speedStep').addEventListener('keypress', inputFilterNumbersOnly); - document.getElementById('fastSpeed').addEventListener('keypress', inputFilterNumbersOnly); + function eventCaller(event, className, funcName) { + if (!event.target.classList.contains(className)) { + return + } + funcName(event); + } + + document.addEventListener('keypress', (event) => { + eventCaller(event, "customValue", inputFilterNumbersOnly) + }); + document.addEventListener('focus', (event) => { + eventCaller(event, "customKey", inputFocus) + }); + document.addEventListener('blur', (event) => { + eventCaller(event, "customKey", inputBlur) + }); + document.addEventListener('keydown', (event) => { + eventCaller(event, "customKey", recordKeyPress) + }); + document.addEventListener('click', (event) => { + eventCaller(event, "removeParent", function () { + event.target.parentNode.remove() + }) + }); + document.addEventListener('change', (event) => { + eventCaller(event, "customDo", function () { + switch (event.target.value) { + case "muted": + case "pause": + event.target.nextElementSibling.nextElementSibling.disabled = true; + event.target.nextElementSibling.nextElementSibling.value = 0; + break; + default: + event.target.nextElementSibling.nextElementSibling.disabled = false; + } + }) + }); })