Merge upstream up to 0.5.9

This commit is contained in:
codebicycle
2020-03-26 19:16:39 +02:00
15 changed files with 737 additions and 348 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.DS_Store
local
# IntelliJ IDEA
.idea/

View File

@@ -19,14 +19,17 @@ Once the extension is installed simply navigate to any page that offers HTML5 vi
* **S** - decrease playback speed.
* **D** - increase playback speed.
* **R** - reset playback speed.
* **R** - reset playback speed to 1.0x.
* **Z** - rewind video by 10 seconds.
* **X** - advance video by 10 seconds.
* **G** - toggle between current and user configurable preferred speed.
* **V** - show/hide the controller.
Some sites may assign other functionality to same shortcut keys these collisions are inevitable, unfortunately. As a workaround, we listen both for lower and upper case values (i.e. you can use `Shift-<shortcut>`) if there is other functionality assigned to the lowercase key. This is not a perfect solution, as some sites may listen to both, but works most of the time.
You can customize and reassign the default shortcut keys in the extensions settings page, as well as add additional shortcut keys to match your preferences. For example, you can assign multiple different "preferred speed" shortcuts with different values, which will allow you to quickly toggle between your most commonly used speeds. To add a new shortcut, open extension settings and click "Add New".
_Note: you can customize these shortcut keys in the extension settings page and even make the extension remember your current playback speed._
![settings Add New shortcut](https://user-images.githubusercontent.com/121805/50726471-50242200-1172-11e9-902f-0e5958387617.jpg)
Some sites may assign other functionality to one of the assigned shortcut keys — these collisions are inevitable, unfortunately. As a workaround, the extension listens both for lower and upper case values (i.e. you can use `Shift-<shortcut>`) if there is other functionality assigned to the lowercase key. This is not a perfect solution, as some sites may listen to both, but works most of the time.
### FAQ

BIN
icons/icon19_disabled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
icons/icon38_disabled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
icons/icon48_disabled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -1,3 +1,4 @@
.vsc-nosource { display: none !important; }
.vsc-hidden { display: none !important; }
.vsc-manual {
visibility: visible !important;
@@ -26,7 +27,13 @@
/* e.g. https://www.igvita.com/2012/09/12/web-fonts-performance-making-pretty-fast/ */
.html5-video-player:not(.ytp-hide-info-bar) .vsc-controller {
position: relative;
top: 40px;
top: 60px;
}
/* Facebook player */
#facebook .vsc-controller {
position: relative;
top: 40px;
}
/* Google Photos player */

527
inject.js
View File

@@ -1,87 +1,200 @@
chrome.runtime.sendMessage({}, function(response) {
var regStrip = /^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm;
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
lastSpeed: 1.0, // default 1x
enabled: true, // default enabled
speeds: {}, // empty object to hold speed for each source
displayKeyCode: 86, // default: V
fastKeyCode: 71, // default: G
rememberSpeed: false, // default: false
audioBoolean: false, // default: false
startHidden: false, // default: false
controllerOpacity: 0.3, // default: 0.3
keyBindings: [],
blacklist: `
www.instagram.com
twitter.com
vine.co
imgur.com
`.replace(/^\s+|\s+$/gm,'')
teams.microsoft.com
`.replace(regStrip,'')
}
};
chrome.storage.local.get(tc.settings, function(storage) {
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);
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,
audioBoolean: tc.settings.audioBoolean,
startHidden: tc.settings.startHidden,
enabled: tc.settings.enabled,
controllerOpacity: tc.settings.controllerOpacity,
blacklist: tc.settings.blacklist.replace(regStrip, '')
});
}
tc.settings.lastSpeed = Number(storage.lastSpeed);
tc.settings.displayKeyCode = Number(storage.displayKeyCode);
tc.settings.advanceKeyCode = Number(storage.advanceKeyCode);
tc.settings.rememberSpeed = Boolean(storage.rememberSpeed);
tc.settings.audioBoolean = Boolean(storage.audioBoolean);
tc.settings.enabled = Boolean(storage.enabled);
tc.settings.startHidden = Boolean(storage.startHidden);
tc.settings.controllerOpacity = Number(storage.controllerOpacity);
tc.settings.blacklist = String(storage.blacklist);
// ensure that there is a "display" binding (for upgrades from versions that had it as a separate binding)
if (tc.settings.keyBindings.filter(x => x.action == "display").length == 0) {
tc.settings.keyBindings.push({
action: "display",
key: Number(storage.displayKeyCode) || 86,
value: 0,
force: false,
predefined: true
}); // default V
}
initializeWhenReady(document);
});
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']) {
return;
return target.vsc;
}
this.video = target;
this.parent = target.parentElement || parent;
this.document = target.ownerDocument;
this.id = Math.random().toString(36).substr(2, 9);
// settings.speeds[] ensures that same source used across video tags (e.g. fullscreen on YT) retains speed setting
// this.speed is a controller level variable that retains speed setting across source switches (e.g. video quality, playlist change)
this.speed = 1.0;
if (!tc.settings.rememberSpeed) {
tc.settings.speed = 1.0;
tc.settings.resetSpeed = tc.settings.fastSpeed;
if (!tc.settings.speeds[target.currentSrc]) {
tc.settings.speeds[target.currentSrc] = this.speed;
}
setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed
} else {
tc.settings.speeds[target.currentSrc] = tc.settings.lastSpeed;
}
this.initializeControls();
target.addEventListener('play', function(event) {
target.playbackRate = tc.settings.speed;
});
target.playbackRate = tc.settings.speeds[target.currentSrc];
target.addEventListener('ratechange', function(event) {
this.div=this.initializeControls();
target.addEventListener('play', this.handlePlay = function(event) {
if (!tc.settings.rememberSpeed) {
if (!tc.settings.speeds[target.currentSrc]) {
tc.settings.speeds[target.currentSrc] = this.speed;
}
setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed
} else {
tc.settings.speeds[target.currentSrc] = tc.settings.lastSpeed;
}
target.playbackRate = tc.settings.speeds[target.currentSrc];
}.bind(this));
target.addEventListener('ratechange', this.handleRatechange = function(event) {
// Ignore ratechange events on unitialized videos.
// 0 == No information is available about the media resource.
if (event.target.readyState > 0) {
var speed = this.getSpeed();
this.speedIndicator.textContent = speed;
tc.settings.speed = speed;
chrome.storage.local.set({'speed': speed}, function() {
tc.settings.speeds[this.video.currentSrc] = speed;
tc.settings.lastSpeed = speed;
this.speed = speed;
chrome.storage.sync.set({'lastSpeed': speed}, function() {
console.log('Speed setting saved: ' + speed);
});
// show the controller for 1000ms if it's hidden.
runAction('blink', document, null, null);
}
}.bind(this));
target.playbackRate = tc.settings.speed;
var observer=new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && (mutation.attributeName === 'src' || mutation.attributeName === 'currentSrc')){
var controller = document.querySelector(`div[data-vscid="${this.id}"]`);
if(!controller){
return;
}
if (!mutation.target.src && !mutation.target.currentSrc) {
controller.classList.add('vsc-nosource');
} else {
controller.classList.remove('vsc-nosource');
}
}
});
});
observer.observe(target, {
attributeFilter: ["src", "currentSrc"]
});
};
tc.videoController.prototype.getSpeed = function() {
@@ -89,90 +202,56 @@ chrome.runtime.sendMessage({}, function(response) {
}
tc.videoController.prototype.remove = function() {
this.parentElement.removeChild(this);
this.div.remove();
this.video.removeEventListener('play',this.handlePlay);
this.video.removeEventListener('ratechange',this.handleRatechange);
delete this.video.dataset['vscid'];
delete this.video.vsc;
}
tc.videoController.prototype.initializeControls = function() {
var document = this.document;
var speed = parseFloat(tc.settings.speed).toFixed(2),
var speed = parseFloat(tc.settings.speeds[this.video.currentSrc]).toFixed(2),
top = Math.max(this.video.offsetTop, 0) + "px",
left = Math.max(this.video.offsetLeft, 0) + "px";
var prevent = function(e) {
e.preventDefault();
e.stopPropagation();
}
var wrapper = document.createElement('div');
wrapper.classList.add('vsc-controller');
wrapper.dataset['vscid'] = this.id;
wrapper.addEventListener('dblclick', prevent, false);
wrapper.addEventListener('mousedown', prevent, false);
wrapper.addEventListener('click', prevent, false);
if (!this.video.currentSrc) {
wrapper.classList.add('vsc-nosource');
}
if (tc.settings.startHidden) {
wrapper.classList.add('vsc-hidden');
}
var styleElem = document.createElement('style')
var shadowCSS = chrome.runtime.getURL('shadow.css')
var textElem = document.createTextNode(`@import "${shadowCSS}";`)
styleElem.appendChild(textElem)
wrapper.appendChild(styleElem)
var divElem = document.createElement('div')
divElem.setAttribute('id', 'controller')
divElem.setAttribute('style', `top:${top}; left:${left};`)
var spanElem1 = document.createElement('span')
spanElem1.setAttribute('data-action', 'drag')
spanElem1.setAttribute('class', 'draggable')
spanElem1.appendChild(document.createTextNode(speed.toString()))
divElem.appendChild(spanElem1)
var spanElem2 = document.createElement('span')
spanElem2.setAttribute('id', 'controls')
var buttonElem1 = document.createElement('button')
buttonElem1.setAttribute('data-action', 'rewind')
buttonElem1.setAttribute('class', 'rw')
buttonElem1.appendChild(document.createTextNode('«'))
spanElem2.appendChild(buttonElem1)
var buttonElem2 = document.createElement('button')
buttonElem2.setAttribute('data-action', 'slower')
buttonElem2.appendChild(document.createTextNode('-'))
spanElem2.appendChild(buttonElem2)
var buttonElem3 = document.createElement('button')
buttonElem3.setAttribute('data-action', 'faster')
buttonElem3.appendChild(document.createTextNode('+'))
spanElem2.appendChild(buttonElem3)
var buttonElem4 = document.createElement('button')
buttonElem4.setAttribute('data-action', 'advance')
buttonElem4.setAttribute('class', 'rw')
buttonElem4.appendChild(document.createTextNode('»'))
spanElem2.appendChild(buttonElem4)
var buttonElem5 = document.createElement('button')
buttonElem5.setAttribute('data-action', 'display')
buttonElem5.setAttribute('class', 'hideButton')
buttonElem5.appendChild(document.createTextNode('x'))
spanElem2.appendChild(buttonElem5)
divElem.appendChild(spanElem2)
wrapper.appendChild(divElem)
var shadow = wrapper
var shadow = wrapper.attachShadow({ mode: 'open' });
var shadowTemplate = `
<style>
@import "${chrome.runtime.getURL('shadow.css')}";
</style>
<div id="controller" style="top:${top}; left:${left}; opacity:${tc.settings.controllerOpacity}">
<span data-action="drag" class="draggable">${speed}</span>
<span id="controls">
<button data-action="rewind" class="rw">«</button>
<button data-action="slower">-</button>
<button data-action="faster">+</button>
<button data-action="advance" class="rw">»</button>
<button data-action="display" class="hideButton">x</button>
</span>
</div>
`;
shadow.innerHTML = shadowTemplate;
shadow.querySelector('.draggable').addEventListener('mousedown', (e) => {
runAction(e.target.dataset['action'], document, false, e);
});
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);
}
});
@@ -180,11 +259,11 @@ chrome.runtime.sendMessage({}, function(response) {
var fragment = document.createDocumentFragment();
fragment.appendChild(wrapper);
this.video.classList.add('vsc-initialized');
this.video.dataset['vscid'] = this.id;
switch (true) {
case (location.hostname == 'www.amazon.com'):
case (location.hostname == 'www.reddit.com'):
case (/hbogo\./).test(location.hostname):
// insert before parent to bypass overlay
this.parent.parentElement.insertBefore(fragment, this.parent);
@@ -196,6 +275,7 @@ chrome.runtime.sendMessage({}, function(response) {
// the first element of the target, which may not be the parent.
this.parent.insertBefore(fragment, this.parent.firstChild);
}
return wrapper;
}
}
@@ -207,12 +287,21 @@ chrome.runtime.sendMessage({}, function(response) {
var blacklisted = false;
tc.settings.blacklist.split("\n").forEach(match => {
match = match.replace(/^\s+|\s+$/g,'')
match = match.replace(regStrip,'')
if (match.length == 0) {
return;
}
var regexp = new RegExp(escapeStringRegExp(match));
if (match.startsWith('/')) {
try {
var regexp = new RegExp(match);
} catch(err) {
return;
}
} else {
var regexp = new RegExp(escapeStringRegExp(match));
}
if (regexp.test(location.href)) {
blacklisted = true;
return;
@@ -222,10 +311,9 @@ chrome.runtime.sendMessage({}, function(response) {
if (blacklisted)
return;
window.addEventListener('load', function () {
initializeNow(window.document);
}, false);
window.onload = () => {
initializeNow(window.document)
};
if (document) {
if (document.readyState === "complete") {
initializeNow(document);
@@ -238,8 +326,15 @@ chrome.runtime.sendMessage({}, function(response) {
}
}
}
function inIframe () {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
function initializeNow(document) {
if (!tc.settings.enabled) return;
// enforce init-once due to redundant callers
if (!document.body || document.body.classList.contains('vsc-initialized')) {
return;
@@ -255,61 +350,66 @@ chrome.runtime.sendMessage({}, function(response) {
link.rel = 'stylesheet';
document.head.appendChild(link);
}
var docs = Array(document)
try {
if (inIframe())
docs.push(window.top.document);
} catch (e) {
}
document.addEventListener('keydown', function(event) {
var keyCode = event.keyCode;
docs.forEach(function(doc) {
doc.addEventListener('keydown', function(event) {
var keyCode = event.keyCode;
// Ignore if following modifier is active.
if (!event.getModifierState
|| event.getModifierState("Alt")
|| event.getModifierState("Control")
|| event.getModifierState("Fn")
|| event.getModifierState("Meta")
|| event.getModifierState("Hyper")
|| event.getModifierState("OS")) {
return;
// Ignore if following modifier is active.
if (!event.getModifierState
|| event.getModifierState("Alt")
|| event.getModifierState("Control")
|| event.getModifierState("Fn")
|| event.getModifierState("Meta")
|| event.getModifierState("Hyper")
|| event.getModifierState("OS")) {
return;
}
// Ignore keydown event if typing in an input box
if (event.target.nodeName === 'INPUT'
|| event.target.nodeName === 'TEXTAREA'
|| event.target.isContentEditable) {
return false;
}
// Ignore keydown event if typing in a page without vsc
if (!document.querySelector(".vsc-controller")) {
return false;
}
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();
}
}
// Ignore keydown event if typing in an input box
if ((document.activeElement.nodeName === 'INPUT'
&& document.activeElement.getAttribute('type') === 'text')
|| document.activeElement.nodeName === 'TEXTAREA'
|| document.activeElement.isContentEditable) {
return false;
}
}, true);
});
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) {
runAction('display', document, true)
} else if (keyCode == tc.settings.fastKeyCode) {
runAction('fast', document, true);
}
return false;
}, true);
function checkForVideo(node, parent, added) {
if (node.nodeName === 'VIDEO') {
// Only proceed with supposed removal if node is missing from DOM
if (!added && document.body.contains(node)) {
return;
}
if (node.nodeName === 'VIDEO' || (node.nodeName === 'AUDIO' && tc.settings.audioBoolean)) {
if (added) {
new tc.videoController(node, parent);
node.vsc = new tc.videoController(node, parent);
} else {
if (node.classList.contains('vsc-initialized')) {
let id = node.dataset['vscid'];
let ctrl = document.querySelector(`div[data-vscid="${id}"]`)
if (ctrl) {
ctrl.remove();
}
node.classList.remove('vsc-initialized');
delete node.dataset['vscid'];
let id = node.dataset['vscid'];
if (id) {
node.vsc.remove();
}
}
} else if (node.children != undefined) {
@@ -321,7 +421,8 @@ chrome.runtime.sendMessage({}, function(response) {
}
var observer = new MutationObserver(function(mutations) {
function mutationObserverHandler() {
// Process the DOM nodes lazily
requestIdleCallback(_ => {
mutations.forEach(function(mutation) {
forEach.call(mutation.addedNodes, function(node) {
if (typeof node === "function")
@@ -334,19 +435,18 @@ chrome.runtime.sendMessage({}, function(response) {
checkForVideo(node, node.parentNode || mutation.target, false);
});
});
}
// Process the DOM nodes lazily
if ('requestIdleCallback' in window) {
requestIdleCallback(mutationObserverHandler, {timeout: 1000});
} else {
mutationObserverHandler();
}
}, {timeout: 1000});
});
observer.observe(document, { childList: true, subtree: true });
var videoTags = document.getElementsByTagName('video');
forEach.call(videoTags, function(video) {
new tc.videoController(video);
if (tc.settings.audioBoolean) {
var mediaTags = document.querySelectorAll('video,audio');
} else {
var mediaTags = document.querySelectorAll('video');
}
forEach.call(mediaTags, function(video) {
video.vsc = new tc.videoController(video);
});
var frameTags = document.getElementsByTagName('iframe');
@@ -357,68 +457,122 @@ chrome.runtime.sendMessage({}, function(response) {
});
}
function runAction(action, document, keyboard, e) {
var videoTags = document.getElementsByTagName('video');
videoTags.forEach = Array.prototype.forEach;
function runAction(action, document, value, e) {
if (tc.settings.audioBoolean) {
var mediaTags = document.querySelectorAll('video,audio');
} else {
var mediaTags = document.querySelectorAll('video');
}
videoTags.forEach(function(v) {
mediaTags.forEach = Array.prototype.forEach;
// Get the controller that was used if called from a button press event e
if (e) {
var targetController = e.target.getRootNode().host;
}
mediaTags.forEach(function(v) {
var id = v.dataset['vscid'];
var controller = document.querySelector(`div[data-vscid="${id}"]`);
showController(controller);
// Don't change video speed if the video has a different controller
if (e && !(targetController == controller)) {
return;
}
// Controller may have been (force) removed by the site, guard to prevent crashes but run the command
if (controller) {
showController(controller);
}
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);
// https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/media/html_media_element.cc?gsn=kMinRate&l=166
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);
// https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/media/html_media_element.cc?gsn=kMinRate&l=165
var s = Math.max(v.playbackRate - value, 0.07);
v.playbackRate = Number(s.toFixed(2));
} else if (action === 'reset') {
resetSpeed(v, 1.0);
} else if (action === 'display') {
controller.classList.add('vsc-manual');
controller.classList.toggle('vsc-hidden');
} else if (action === 'blink') {
// if vsc is hidden, show it briefly to give the use visual feedback that the action is excuted.
if(controller.classList.contains('vsc-hidden') || controller.blinkTimeOut !== undefined){
clearTimeout(controller.blinkTimeOut);
controller.classList.remove('vsc-hidden');
controller.blinkTimeOut = setTimeout(()=>{
controller.classList.add('vsc-hidden');
controller.blinkTimeOut = undefined;
}, value ? value : 1000);
}
} else if (action === 'drag') {
handleDrag(v, controller, e);
} else if (action === 'fast') {
resetSpeed(v, tc.settings.fastSpeed);
resetSpeed(v, value);
} else if (action === 'pause') {
pause(v);
} else if (action === 'muted') {
muted(v, value);
} else if (action === 'mark') {
setMark(v);
} else if (action === 'jump') {
jumpToMark(v);
}
}
});
}
function pause(v) {
if (v.paused) {
v.play();
} else {
v.pause();
}
}
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;
} else {
v.playbackRate = getKeyBindings("reset"); // resetSpeed
}
} else {
tc.settings.resetSpeed = v.playbackRate;
chrome.storage.local.set({'resetSpeed': v.playbackRate});
setKeyBindings("reset", v.playbackRate); // resetSpeed
v.playbackRate = target;
}
}
function muted(v, value) {
v.muted = v.muted !== true;
}
function setMark(v) {
v.vsc.mark = v.currentTime;
}
function jumpToMark(v) {
if (v.vsc.mark && typeof v.vsc.mark === "number") {
v.currentTime = v.vsc.mark;
}
}
function handleDrag(video, controller, e) {
const shadowController = controller.querySelector('#controller');
const shadowController = controller.shadowRoot.querySelector('#controller');
// Find nearest parent of same size as video parent.
var parentElement = controller.parentElement;
@@ -473,4 +627,3 @@ chrome.runtime.sendMessage({}, function(response) {
animation = false;
}, 2000);
}
});

View File

@@ -1,7 +1,7 @@
{
"name": "Video Speed Controller",
"short_name": "videospeed",
"version": "0.5.1",
"version": "0.5.9",
"manifest_version": 2,
"description": "Speed up, slow down, advance and rewind any HTML5 video with quick shortcuts.",
"homepage_url": "https://github.com/codebicycle/videospeed",
@@ -26,9 +26,10 @@
"content_scripts": [{
"all_frames": true,
"matches": [ "http://*/*", "https://*/*", "file:///*" ],
"match_about_blank": true,
"exclude_matches": [
"https://plus.google.com/hangouts/*",
"https://hangouts.google.com/hangouts/*",
"https://hangouts.google.com/*",
"https://meet.google.com/*",
"https://teamtreehouse.com/*",
"http://www.hitbox.tv/*"

View File

@@ -83,10 +83,6 @@ label {
width: 170px;
}
label[for=rememberSpeed] {
width: 200px;
}
#status {
color: #9D9D9D;
display: inline-block;
@@ -96,3 +92,17 @@ label[for=rememberSpeed] {
#faq {
margin-top: 2em;
}
select {
width: 170px;
}
.customForce {
display: none;
width: 250px;
}
.customKey {
color: transparent;
text-shadow: 0 0 0 #000000;
}

View File

@@ -1,81 +1,127 @@
<!DOCTYPE html>
<html>
<head>
<title>Video Speed Controller: Options</title>
<link rel="stylesheet" href="options.css" />
<script src="options.js"></script>
</head>
<body>
<header>
<h1>Video Speed Controller</h1>
</header>
<head>
<title>Video Speed Controller: Options</title>
<link rel="stylesheet" href="options.css" />
<script src="options.js"></script>
</head>
<body>
<header>
<h1>Video Speed Controller</h1>
</header>
<section>
<section id="customs">
<h3>Shortcuts</h3>
<div class="row">
<label for="rewindKeyInput">Rewind</label>
<input id="rewindKeyInput" placeholder="press a key" type="text" value=""/>
</div>
<div class="row">
<label for="advanceKeyInput">Advance</label>
<input id="advanceKeyInput" placeholder="press a key" type="text" value=""/>
</div>
<div class="row">
<label for="resetKeyInput">Reset speed</label>
<input id="resetKeyInput" placeholder="press a key" type="text" value=""/>
</div>
<div class="row">
<label for="slowerKeyInput">Decrease speed</label>
<input id="slowerKeyInput" placeholder="press a key" type="text" value=""/>
</div>
<div class="row">
<label for="fasterKeyInput">Increase speed</label>
<input id="fasterKeyInput" placeholder="press a key" type="text" value=""/>
</div>
<div class="row">
<label for="fastKeyInput">Preferred speed</label>
<input id="fastKeyInput" placeholder="press a key" type="text" value=""/>
</div>
<div class="row">
<label for="displayKeyInput">Show/hide controller</label>
<input id="displayKeyInput" placeholder="press a key" type="text" value=""/>
</div>
<div class="row">
<label for="startHidden">Hide controller by default</label>
<input id="startHidden" type="checkbox"/>
</div>
<div class="row customs" id="display">
<select class="customDo">
<option value="display">Show/hide controller</option>
</select>
<input class="customKey" type="text" value="" placeholder="press a key">
<input class="customValue" type="text" placeholder="value (0.10)">
<select class="customForce">
<option value="false">Do not disable website key bindings</option>
<option value="true">Disable websites key bindings</option>
</select></div>
<div class="row customs" id="slower">
<select class="customDo">
<option value="slower">Decrease speed</option>
</select>
<input class="customKey" type="text" value="" placeholder="press a key">
<input class="customValue" type="text" placeholder="value (0.10)">
<select class="customForce">
<option value="false">Do not disable website key bindings</option>
<option value="true">Disable websites key bindings</option>
</select></div>
<div class="row customs" id="faster">
<select class="customDo">
<option value="faster">Increase speed</option>
</select>
<input class="customKey" type="text" value="" placeholder="press a key">
<input class="customValue" type="text" placeholder="value (0.10)">
<select class="customForce">
<option value="false">Do not disable website key bindings</option>
<option value="true">Disable websites key bindings</option>
</select></div>
<div class="row customs" id="rewind">
<select class="customDo">
<option value="rewind">Rewind</option>
</select>
<input class="customKey" type="text" value="" placeholder="press a key">
<input class="customValue" type="text" placeholder="value (10)">
<select class="customForce">
<option value="false">Do not disable website key bindings</option>
<option value="true">Disable websites key bindings</option>
</select></div>
<div class="row customs" id="advance">
<select class="customDo">
<option value="advance">Advance</option>
</select>
<input class="customKey" type="text" value="" placeholder="press a key">
<input class="customValue" type="text" placeholder="value (10)">
<select class="customForce">
<option value="false">Do not disable website key bindings</option>
<option value="true">Disable websites key bindings</option>
</select></div>
<div class="row customs" id="reset">
<select class="customDo">
<option value="reset">Reset speed</option>
</select>
<input class="customKey" type="text" value="" placeholder="press a key">
<input class="customValue" type="text" placeholder="value (1.00)" disabled>
<select class="customForce">
<option value="false">Do not disable website key bindings</option>
<option value="true">Disable websites key bindings</option>
</select></div>
<div class="row customs" id="fast">
<select class="customDo">
<option value="fast">Preferred speed</option>
</select>
<input class="customKey" type="text" value="" placeholder="press a key">
<input class="customValue" type="text" placeholder="value (1.80)">
<select class="customForce">
<option value="false">Do not disable website key bindings</option>
<option value="true">Disable websites key bindings</option>
</select></div>
<button id="add">Add New</button>
</section>
<section>
<h3>Other</h3>
<div class="row">
<label for="rewindTime">Rewind Time (s)</label>
<input id="rewindTime" type="text" value=""/>
</div>
<div class="row">
<label for="advanceTime">Advance Time (s)</label>
<input id="advanceTime" type="text" value=""/>
<h3>Other</h3>
<div class="row">
<label for="enabled">Enabled</label>
<input id="enabled" type="checkbox"/>
</div>
<div class="row">
<label for="speedStep">Speed Change Step</label>
<input id="speedStep" type="text" value=""/>
<label for="startHidden">Hide controller by default</label>
<input id="startHidden" type="checkbox"/>
</div>
<div class="row">
<label for="fastSpeed">Preferred Speed (x)</label>
<input id="fastSpeed" type="text" value=""/>
<label for="rememberSpeed">Remember Playback Speed</label>
<input id="rememberSpeed" type="checkbox"/>
</div>
<div class="row">
<label for="rememberSpeed">Remember Playback Speed</label>
<input id="rememberSpeed" type="checkbox"/>
<label for="audioBoolean">Work on audio</label>
<input id="audioBoolean" type="checkbox"/>
</div>
<div class="row">
<label for="blacklist">Blacklisted sites on which extension is disabled<br/>(one per line)</label>
<textarea id="blacklist" rows="10" cols="50"></textarea>
<label for="controllerOpacity">Controller opacity</label>
<input id="controllerOpacity" type="text" value="">
</div>
<div class="row">
<label for="blacklist">Blacklisted sites on which extension is disabled<br/>
(one per line)<br/>
<br/>
<em><a href="https://www.regexpal.com/">Regex</a> is supported. Be sure it is in "//g" format.<br/>
ie: /(.+)youtube\.com(\/*)$/gi</em>
</label>
<textarea id="blacklist" rows="10" cols="50"></textarea>
</div>
</section>
<button id="save">Save</button>
<button id="restore">Restore Defaults</button>
<button id="restore">Restore Defaults</button>
<button id="experimental">Show experimental features</button>
<div id="status"></div>

View File

@@ -1,32 +1,46 @@
var regStrip=/^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm;
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
audioBoolean: false, // default: false
startHidden: false, // default: false
enabled: true, // default enabled
controllerOpacity: 0.3, // default: 0.3
keyBindings: [
{action: "display", key: 86, value: 0, force: false, predefined: true }, // V
{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,'')
teams.microsoft.com
`.replace(regStrip, '')
};
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',
37: 'Left',
38: 'Up',
39: 'Right',
40: 'Down',
96: 'Num 0',
97: 'Num 1',
98: 'Num 2',
99: 'Num 3',
100: 'Num 4',
101: 'Num 5',
102: 'Num 6',
@@ -38,6 +52,18 @@ var keyCodeAliases = {
109: 'Num -',
110: 'Num .',
111: 'Num /',
112: 'F1',
113: 'F2',
114: 'F3',
115: 'F4',
116: 'F5',
117: 'F6',
118: 'F7',
119: 'F8',
120: 'F9',
121: 'F10',
122: 'F11',
123: 'F12',
186: ';',
188: '<',
189: '-',
@@ -49,14 +75,8 @@ var keyCodeAliases = {
220: '\\',
221: ']',
222: '\'',
59: ';',
173: '-',
61: '+',
}
var whiteList = ['Backspace', 'Delete', 'Tab', 'Escape', 'Enter', 'Home', 'End',
'ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown']
function recordKeyPress(e) {
if (
(e.keyCode >= 48 && e.keyCode <= 57) // Numbers 0-9
@@ -70,14 +90,14 @@ 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;
}
};
function inputFilterNumbersOnly(e) {
var char = e.key;
if (whiteList.includes(char)) {
return
}
var char = String.fromCharCode(e.keyCode);
if (!/[\d\.]$/.test(char) || !/^\d+(\.\d*)?$/.test(e.target.value + char)) {
e.preventDefault();
e.stopPropagation();
@@ -97,51 +117,96 @@ function updateShortcutInputText(inputId, keyCode) {
document.getElementById(inputId).keyCode = keyCode;
}
function updateCustomShortcutInputText(inputItem, keyCode) {
inputItem.value = keyCodeAliases[keyCode] || String.fromCharCode(keyCode);
inputItem.keyCode = keyCode;
}
// List of custom actions for which customValue should be disabled
var customActionsNoValues=["pause","muted","mark","jump","display"];
function add_shortcut() {
var html = `<select class="customDo">
<option value="slower">Decrease speed</option>
<option value="faster">Increase speed</option>
<option value="rewind">Rewind</option>
<option value="advance">Advance</option>
<option value="reset">Reset speed</option>
<option value="fast">Preferred speed</option>
<option value="muted">Mute</option>
<option value="pause">Pause</option>
<option value="mark">Set marker</option>
<option value="jump">Jump to marker</option>
<option value="display">Show/hide controller</option>
</select>
<input class="customKey" type="text" placeholder="press a key"/>
<input class="customValue" type="text" placeholder="value (0.10)"/>
<select class="customForce">
<option value="false">Do not disable website key bindings</option>
<option value="true">Disable websites key bindings</option>
</select>
<button class="removeParent">X</button>`;
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});
}
// Validates settings before saving
function validate() {
var valid = true;
var status = document.getElementById('status');
document.getElementById('blacklist').value.split("\n").forEach(match => {
match = match.replace(regStrip,'')
if (match.startsWith('/')) {
try {
var regexp = new RegExp(match);
} catch(err) {
status.textContent = 'Error: Invalid Regex: ' + match
+ '. Unable to save';
valid = false;
return;
}
}
})
return valid;
}
// Saves options to chrome.storage
function save_options() {
if (validate() === false) {
return;
}
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 audioBoolean = document.getElementById('audioBoolean').checked;
var enabled = document.getElementById('enabled').checked;
var startHidden = document.getElementById('startHidden').checked;
var controllerOpacity = document.getElementById('controllerOpacity').value;
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.local.set({
speedStep: speedStep,
rewindTime: rewindTime,
advanceTime: advanceTime,
fastSpeed: fastSpeed,
resetKeyCode: resetKeyCode,
rewindKeyCode: rewindKeyCode,
advanceKeyCode: advanceKeyCode,
slowerKeyCode: slowerKeyCode,
fasterKeyCode: fasterKeyCode,
fastKeyCode: fastKeyCode,
displayKeyCode: displayKeyCode,
chrome.storage.sync.remove(["resetSpeed", "speedStep", "fastSpeed", "rewindTime", "advanceTime", "resetKeyCode", "slowerKeyCode", "fasterKeyCode", "rewindKeyCode", "advanceKeyCode", "fastKeyCode"]);
chrome.storage.sync.set({
rememberSpeed: rememberSpeed,
audioBoolean: audioBoolean,
enabled: enabled,
startHidden: startHidden,
blacklist: blacklist.replace(/^\s+|\s+$/gm,'')
controllerOpacity: controllerOpacity,
keyBindings: keyBindings,
blacklist: blacklist.replace(regStrip,'')
}, function() {
// Update status to let user know options were saved.
var status = document.getElementById('status');
@@ -154,27 +219,56 @@ function save_options() {
// Restores options from chrome.storage
function restore_options() {
chrome.storage.local.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);
chrome.storage.sync.get(tcDefaults, function(storage) {
document.getElementById('rememberSpeed').checked = storage.rememberSpeed;
document.getElementById('audioBoolean').checked = storage.audioBoolean;
document.getElementById('enabled').checked = storage.enabled;
document.getElementById('startHidden').checked = storage.startHidden;
document.getElementById('controllerOpacity').value = storage.controllerOpacity;
document.getElementById('blacklist').value = storage.blacklist;
// ensure that there is a "display" binding for upgrades from versions that had it as a separate binding
if(storage.keyBindings.filter(x => x.action == "display").length == 0){
storage.keyBindings.push({ action: "display", value: 0, force: false, predefined: true });
}
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"];
if (item["action"] == "display" && typeof (item["key"]) === "undefined"){
item["key"] = storage.displayKeyCode || tcDefaults.displayKeyCode; // V
}
if (customActionsNoValues.includes(item["action"]))
document.querySelector("#" + item["action"] + " .customValue").disabled = true;
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 (customActionsNoValues.includes(item["action"]))
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.local.set(tcDefaults, function() {
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';
@@ -184,28 +278,50 @@ function restore_defaults() {
});
}
function initShortcutInput(inputId) {
document.getElementById(inputId).addEventListener('focus', inputFocus);
document.getElementById(inputId).addEventListener('blur', inputBlur);
document.getElementById(inputId).addEventListener('keydown', recordKeyPress);
function show_experimental() {
document.querySelectorAll(".customForce").forEach(item => item.style.display = 'inline-block');
}
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');
function eventCaller(event, className, funcName) {
if (!event.target.classList.contains(className)) {
return
}
funcName(event);
}
document.getElementById('rewindTime').addEventListener('keypress', inputFilterNumbersOnly);
document.getElementById('advanceTime').addEventListener('keypress', inputFilterNumbersOnly);
document.getElementById('speedStep').addEventListener('keypress', inputFilterNumbersOnly);
document.getElementById('fastSpeed').addEventListener('keypress', inputFilterNumbersOnly);
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 () {
if (customActionsNoValues.includes(event.target.value)) {
event.target.nextElementSibling.nextElementSibling.disabled = true;
event.target.nextElementSibling.nextElementSibling.value = 0;
} else {
event.target.nextElementSibling.nextElementSibling.disabled = false;
}
})
});
})

View File

@@ -27,3 +27,7 @@ button {
font-size: 0.95em;
margin: 0.15em 0;
}
.hide {
display: none;
}

View File

@@ -6,6 +6,10 @@
<script src="popup.js"></script>
</head>
<body>
<button id="enable" class="hide">Enable</button>
<button id="disable">Disable</button>
<span id="status" class="hide"></span>
<hr />
<button id="config">Settings</button>
<hr />
<button id="feedback" class="secondary">Send feedback</button>

View File

@@ -10,4 +10,49 @@ document.addEventListener('DOMContentLoaded', function () {
document.querySelector('#feedback').addEventListener('click', function() {
window.open("https://github.com/codebicycle/videospeed/issues");
});
document.querySelector('#enable').addEventListener('click', function() {
toggleEnabled(true, settingsSavedReloadMessage);
});
document.querySelector('#disable').addEventListener('click', function() {
toggleEnabled(false, settingsSavedReloadMessage);
});
chrome.storage.sync.get({enabled: true}, function(storage) {
toggleEnabledUI(storage.enabled);
});
function toggleEnabled(enabled, callback){
chrome.storage.sync.set({
enabled: enabled,
}, function() {
toggleEnabledUI(enabled);
if(callback) callback(enabled);
});
}
function toggleEnabledUI(enabled){
document.querySelector('#enable').classList.toggle("hide", enabled);
document.querySelector('#disable').classList.toggle("hide", !enabled);
const suffix = `${(enabled ? "" : "_disabled")}.png`
chrome.browserAction.setIcon({
"path": {
"19": "icons/icon19" + suffix,
"38": "icons/icon38" + suffix,
"48": "icons/icon48" + suffix
}
});
}
function settingsSavedReloadMessage(enabled){
setStatusMessage(`${enabled ? "Enabled" : "Disabled"}. Reload page to see changes`);
}
function setStatusMessage(str){
const status_element = document.querySelector('#status')
status_element.classList.toggle("hide", false);
status_element.innerText = str;
}
});

View File

@@ -22,7 +22,6 @@
cursor: default;
z-index: 9999999;
opacity: 0.3;
}
.vsc-controller #controller:hover {