Merge upstream 0.6.3

This commit is contained in:
codebicycle
2021-02-06 10:01:27 +02:00
parent 3975b74d27
commit 86545a6a34
7 changed files with 313 additions and 196 deletions

View File

@@ -9,6 +9,11 @@
opacity: 1 !important; opacity: 1 !important;
} }
.vsc-controller {
/* In case of pages using `white-space: pre-line` (eg Discord), don't render vsc's whitespace */
white-space: normal;
}
/* Origin specific overrides */ /* Origin specific overrides */
/* YouTube player */ /* YouTube player */
.ytp-hide-info-bar .vsc-controller { .ytp-hide-info-bar .vsc-controller {

408
inject.js
View File

@@ -8,6 +8,7 @@ var tc = {
displayKeyCode: 86, // default: V displayKeyCode: 86, // default: V
rememberSpeed: false, // default: false rememberSpeed: false, // default: false
forceLastSavedSpeed: false, //default: false
audioBoolean: false, // default: false audioBoolean: false, // default: false
startHidden: false, // default: false startHidden: false, // default: false
controllerOpacity: 0.3, // default: 0.3 controllerOpacity: 0.3, // default: 0.3
@@ -21,7 +22,10 @@ var tc = {
`.replace(regStrip, ""), `.replace(regStrip, ""),
defaultLogLevel: 4, defaultLogLevel: 4,
logLevel: 3 logLevel: 3
} },
// Holds a reference to all of the AUDIO/VIDEO DOM elements we've attached to
mediaElements: []
}; };
/* Log levels (depends on caller specifying the correct level) /* Log levels (depends on caller specifying the correct level)
@@ -53,7 +57,7 @@ function log(message, level) {
} }
} }
chrome.storage.sync.get(tc.settings, function(storage) { chrome.storage.sync.get(tc.settings, function (storage) {
tc.settings.keyBindings = storage.keyBindings; // Array tc.settings.keyBindings = storage.keyBindings; // Array
if (storage.keyBindings.length == 0) { if (storage.keyBindings.length == 0) {
// if first initialization of 0.5.3 // if first initialization of 0.5.3
@@ -107,6 +111,7 @@ chrome.storage.sync.get(tc.settings, function(storage) {
version: tc.settings.version, version: tc.settings.version,
displayKeyCode: tc.settings.displayKeyCode, displayKeyCode: tc.settings.displayKeyCode,
rememberSpeed: tc.settings.rememberSpeed, rememberSpeed: tc.settings.rememberSpeed,
forceLastSavedSpeed: tc.settings.forceLastSavedSpeed,
audioBoolean: tc.settings.audioBoolean, audioBoolean: tc.settings.audioBoolean,
startHidden: tc.settings.startHidden, startHidden: tc.settings.startHidden,
enabled: tc.settings.enabled, enabled: tc.settings.enabled,
@@ -117,6 +122,7 @@ chrome.storage.sync.get(tc.settings, function(storage) {
tc.settings.lastSpeed = Number(storage.lastSpeed); tc.settings.lastSpeed = Number(storage.lastSpeed);
tc.settings.displayKeyCode = Number(storage.displayKeyCode); tc.settings.displayKeyCode = Number(storage.displayKeyCode);
tc.settings.rememberSpeed = Boolean(storage.rememberSpeed); tc.settings.rememberSpeed = Boolean(storage.rememberSpeed);
tc.settings.forceLastSavedSpeed = Boolean(storage.forceLastSavedSpeed);
tc.settings.audioBoolean = Boolean(storage.audioBoolean); tc.settings.audioBoolean = Boolean(storage.audioBoolean);
tc.settings.enabled = Boolean(storage.enabled); tc.settings.enabled = Boolean(storage.enabled);
tc.settings.startHidden = Boolean(storage.startHidden); tc.settings.startHidden = Boolean(storage.startHidden);
@@ -124,7 +130,9 @@ chrome.storage.sync.get(tc.settings, function(storage) {
tc.settings.blacklist = String(storage.blacklist); tc.settings.blacklist = String(storage.blacklist);
// ensure that there is a "display" binding (for upgrades from versions that had it as a separate binding) // 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) { if (
tc.settings.keyBindings.filter((x) => x.action == "display").length == 0
) {
tc.settings.keyBindings.push({ tc.settings.keyBindings.push({
action: "display", action: "display",
key: Number(storage.displayKeyCode) || 86, key: Number(storage.displayKeyCode) || 86,
@@ -137,32 +145,42 @@ chrome.storage.sync.get(tc.settings, function(storage) {
initializeWhenReady(document); initializeWhenReady(document);
}); });
var forEach = Array.prototype.forEach;
function getKeyBindings(action, what = "value") { function getKeyBindings(action, what = "value") {
try { try {
return tc.settings.keyBindings.find(item => item.action === action)[what]; return tc.settings.keyBindings.find((item) => item.action === action)[what];
} catch (e) { } catch (e) {
return false; return false;
} }
} }
function setKeyBindings(action, value) { function setKeyBindings(action, value) {
tc.settings.keyBindings.find(item => item.action === action)["value"] = value; tc.settings.keyBindings.find((item) => item.action === action)[
"value"
] = value;
} }
function defineVideoController() { function defineVideoController() {
tc.videoController = function(target, parent) { // Data structures
if (target.dataset["vscid"]) { // ---------------
// videoController (JS object) instances:
// video = AUDIO/VIDEO DOM element
// parent = A/V DOM element's parentElement OR
// (A/V elements discovered from the Mutation Observer)
// A/V element's parentNode OR the node whose children changed.
// div = Controller's DOM element (which happens to be a DIV)
// speedIndicator = DOM element in the Controller of the speed indicator
// added to AUDIO / VIDEO DOM elements
// vsc = reference to the videoController
tc.videoController = function (target, parent) {
if (target.vsc) {
return target.vsc; return target.vsc;
} }
tc.mediaElements.push(target);
this.video = target; this.video = target;
this.parent = target.parentElement || parent; this.parent = target.parentElement || parent;
this.document = target.ownerDocument;
this.id = Math.random()
.toString(36)
.substr(2, 9);
storedSpeed = tc.settings.speeds[target.currentSrc]; storedSpeed = tc.settings.speeds[target.currentSrc];
if (!tc.settings.rememberSpeed) { if (!tc.settings.rememberSpeed) {
if (!storedSpeed) { if (!storedSpeed) {
@@ -183,48 +201,50 @@ function defineVideoController() {
this.div = this.initializeControls(); this.div = this.initializeControls();
var mediaEventAction = function (event) {
storedSpeed = tc.settings.speeds[event.target.currentSrc];
if (!tc.settings.rememberSpeed) {
if (!storedSpeed) {
log("Overwriting stored speed to 1.0 (rememberSpeed not enabled)", 4);
storedSpeed = 1.0;
}
// resetSpeed isn't really a reset, it's a toggle
log("Setting reset keybinding to fast", 5);
setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed
} else {
log(
"Storing lastSpeed into tc.settings.speeds (rememberSpeed enabled)",
5
);
storedSpeed = tc.settings.lastSpeed;
}
// TODO: Check if explicitly setting the playback rate to 1.0 is
// necessary when rememberSpeed is disabled (this may accidentally
// override a website's intentional initial speed setting interfering
// with the site's default behavior)
log("Explicitly setting playbackRate to: " + storedSpeed, 4);
setSpeed(event.target, storedSpeed);
};
target.addEventListener( target.addEventListener(
"play", "play",
(this.handlePlay = function(event) { (this.handlePlay = mediaEventAction.bind(this))
storedSpeed = tc.settings.speeds[event.target.currentSrc];
if (!tc.settings.rememberSpeed) {
if (!storedSpeed) {
log(
"Overwriting stored speed to 1.0 (rememberSpeed not enabled)",
4
);
storedSpeed = 1.0;
}
// resetSpeed isn't really a reset, it's a toggle
log("Setting reset keybinding to fast", 5);
setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed
} else {
log(
"Storing lastSpeed into tc.settings.speeds (rememberSpeed enabled)",
5
);
storedSpeed = tc.settings.lastSpeed;
}
// TODO: Check if explicitly setting the playback rate to 1.0 is
// necessary when rememberSpeed is disabled (this may accidentally
// override a website's intentional initial speed setting interfering
// with the site's default behavior)
log("Explicitly setting playbackRate to: " + storedSpeed, 4);
event.target.playbackRate = storedSpeed;
}.bind(this))
); );
var observer = new MutationObserver(mutations => { target.addEventListener(
mutations.forEach(mutation => { "seeked",
(this.handleSeek = mediaEventAction.bind(this))
);
var observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if ( if (
mutation.type === "attributes" && mutation.type === "attributes" &&
(mutation.attributeName === "src" || (mutation.attributeName === "src" ||
mutation.attributeName === "currentSrc") mutation.attributeName === "currentSrc")
) { ) {
var controller = getController(this.id); log("mutation of A/V element", 5);
if (!controller) { var controller = this.div;
return;
}
if (!mutation.target.src && !mutation.target.currentSrc) { if (!mutation.target.src && !mutation.target.currentSrc) {
controller.classList.add("vsc-nosource"); controller.classList.add("vsc-nosource");
} else { } else {
@@ -238,25 +258,29 @@ function defineVideoController() {
}); });
}; };
tc.videoController.prototype.remove = function() { tc.videoController.prototype.remove = function () {
this.div.remove(); this.div.remove();
this.video.removeEventListener("play", this.handlePlay); this.video.removeEventListener("play", this.handlePlay);
delete this.video.dataset["vscid"]; this.video.removeEventListener("seek", this.handleSeek);
delete this.video.vsc; delete this.video.vsc;
let idx = tc.mediaElements.indexOf(this.video);
if (idx != -1) {
tc.mediaElements.splice(idx, 1);
}
}; };
tc.videoController.prototype.initializeControls = function() { tc.videoController.prototype.initializeControls = function () {
log("initializeControls Begin", 5); log("initializeControls Begin", 5);
var document = this.document; const document = this.video.ownerDocument;
var speed = this.video.playbackRate.toFixed(2), const speed = this.video.playbackRate.toFixed(2);
top = Math.max(this.video.offsetTop, 0) + "px", const rect = this.video.getBoundingClientRect();
left = Math.max(this.video.offsetLeft, 0) + "px"; const top = Math.max(rect.top, 0) + "px";
const left = Math.max(rect.left, 0) + "px";
log("Speed variable set to: " + speed, 5); log("Speed variable set to: " + speed, 5);
var wrapper = document.createElement("div"); var wrapper = document.createElement("div");
wrapper.classList.add("vsc-controller"); wrapper.classList.add("vsc-controller");
wrapper.dataset["vscid"] = this.id;
if (!this.video.currentSrc) { if (!this.video.currentSrc) {
wrapper.classList.add("vsc-nosource"); wrapper.classList.add("vsc-nosource");
@@ -278,35 +302,49 @@ function defineVideoController() {
<span data-action="drag" class="draggable">${speed}</span> <span data-action="drag" class="draggable">${speed}</span>
<span id="controls"> <span id="controls">
<button data-action="rewind" class="rw">«</button> <button data-action="rewind" class="rw">«</button>
<button data-action="slower">-</button> <button data-action="slower">&minus;</button>
<button data-action="faster">+</button> <button data-action="faster">&plus;</button>
<button data-action="advance" class="rw">»</button> <button data-action="advance" class="rw">»</button>
<button data-action="display" class="hideButton">x</button> <button data-action="display" class="hideButton">&times;</button>
</span> </span>
</div> </div>
`; `;
shadow.innerHTML = shadowTemplate; shadow.innerHTML = shadowTemplate;
shadow.querySelector(".draggable").addEventListener("mousedown", e => { shadow.querySelector(".draggable").addEventListener(
runAction(e.target.dataset["action"], document, false, e); "mousedown",
(e) => {
runAction(e.target.dataset["action"], false, e);
e.stopPropagation();
},
true
);
shadow.querySelectorAll("button").forEach(function (button) {
button.addEventListener(
"click",
(e) => {
runAction(
e.target.dataset["action"],
getKeyBindings(e.target.dataset["action"]),
e
);
e.stopPropagation();
},
true
);
}); });
forEach.call(shadow.querySelectorAll("button"), function(button) { shadow
button.onclick = e => { .querySelector("#controller")
runAction( .addEventListener("click", (e) => e.stopPropagation(), false);
e.target.dataset["action"], shadow
document, .querySelector("#controller")
getKeyBindings(e.target.dataset["action"]), .addEventListener("mousedown", (e) => e.stopPropagation(), false);
e
);
};
});
this.speedIndicator = shadow.querySelector("span"); this.speedIndicator = shadow.querySelector("span");
var fragment = document.createDocumentFragment(); var fragment = document.createDocumentFragment();
fragment.appendChild(wrapper); fragment.appendChild(wrapper);
this.video.dataset["vscid"] = this.id;
switch (true) { switch (true) {
case location.hostname == "www.amazon.com": case location.hostname == "www.amazon.com":
case location.hostname == "www.reddit.com": case location.hostname == "www.reddit.com":
@@ -314,13 +352,17 @@ function defineVideoController() {
// insert before parent to bypass overlay // insert before parent to bypass overlay
this.parent.parentElement.insertBefore(fragment, this.parent); this.parent.parentElement.insertBefore(fragment, this.parent);
break; break;
case location.hostname == "www.facebook.com":
// this is a monstrosity but new FB design does not have *any*
// semantic handles for us to traverse the tree, and deep nesting
// that we need to bubble up from to get controller to stack correctly
let p = this.parent.parentElement.parentElement.parentElement
.parentElement.parentElement.parentElement.parentElement;
p.insertBefore(fragment, p.firstChild);
break;
case location.hostname == "tv.apple.com": case location.hostname == "tv.apple.com":
// insert after parent for correct stacking context // insert after parent for correct stacking context
this.parent this.parent.getRootNode().querySelector(".scrim").prepend(fragment);
.getRootNode()
.querySelector(".scrim")
.prepend(fragment);
default: default:
// Note: when triggered via a MutationRecord, it's possible that the // Note: when triggered via a MutationRecord, it's possible that the
// target is not the immediate parent. This appends the controller as // target is not the immediate parent. This appends the controller as
@@ -338,7 +380,7 @@ function escapeStringRegExp(str) {
function isBlacklisted() { function isBlacklisted() {
blacklisted = false; blacklisted = false;
tc.settings.blacklist.split("\n").forEach(match => { tc.settings.blacklist.split("\n").forEach((match) => {
match = match.replace(regStrip, ""); match = match.replace(regStrip, "");
if (match.length == 0) { if (match.length == 0) {
return; return;
@@ -368,41 +410,68 @@ function refreshCoolDown() {
if (coolDown) { if (coolDown) {
clearTimeout(coolDown); clearTimeout(coolDown);
} }
coolDown = setTimeout(function() { coolDown = setTimeout(function () {
coolDown = false; coolDown = false;
}, 1000); }, 1000);
log("End refreshCoolDown", 5); log("End refreshCoolDown", 5);
} }
function setupListener() { function setupListener() {
document.body.addEventListener( /**
* This function is run whenever a video speed rate change occurs.
* It is used to update the speed that shows up in the display as well as save
* that latest speed into the local storage.
*
* @param {*} video The video element to update the speed indicators for.
*/
function updateSpeedFromEvent(video) {
// It's possible to get a rate change on a VIDEO/AUDIO that doesn't have
// a video controller attached to it. If we do, ignore it.
if (!video.vsc)
return;
var speedIndicator = video.vsc.speedIndicator;
var src = video.currentSrc;
var speed = Number(video.playbackRate.toFixed(2));
log("Playback rate changed to " + speed, 4);
log("Updating controller with new speed", 5);
speedIndicator.textContent = speed.toFixed(2);
tc.settings.speeds[src] = speed;
log("Storing lastSpeed in settings for the rememberSpeed feature", 5);
tc.settings.lastSpeed = speed;
log("Syncing chrome settings for lastSpeed", 5);
chrome.storage.sync.set({ lastSpeed: speed }, function () {
log("Speed setting saved: " + speed, 5);
});
// show the controller for 1000ms if it's hidden.
runAction("blink", null, null);
}
document.addEventListener(
"ratechange", "ratechange",
function(event) { function (event) {
if (coolDown) { if (coolDown) {
log("Speed event propagation blocked", 4); log("Speed event propagation blocked", 4);
event.stopImmediatePropagation(); event.stopImmediatePropagation();
} }
var controller = event.target.parentElement.querySelector( var video = event.target;
".vsc-controller"
);
var speedIndicator = controller.shadowRoot.querySelector("span");
var video = controller.parentElement.querySelector("video");
var src = video.currentSrc;
var speed = video.playbackRate.toFixed(2);
log("Playback rate changed to " + speed, 4); /**
* If the last speed is forced, only update the speed based on events created by
log("Updating controller with new speed", 5); * video speed instead of all video speed change events.
speedIndicator.textContent = speed; */
tc.settings.speeds[src] = speed; if (tc.settings.forceLastSavedSpeed) {
log("Storing lastSpeed in settings for the rememberSpeed feature", 5); if (event.detail && event.detail.origin === "videoSpeed") {
tc.settings.lastSpeed = speed; video.playbackRate = event.detail.speed;
log("Syncing chrome settings for lastSpeed", 5); updateSpeedFromEvent(video);
chrome.storage.sync.set({ lastSpeed: speed }, function() { } else {
log("Speed setting saved: " + speed, 5); video.playbackRate = tc.settings.lastSpeed;
}); }
// show the controller for 1000ms if it's hidden. event.stopImmediatePropagation();
runAction("blink", document, null, null); } else {
updateSpeedFromEvent(video);
}
}, },
true true
); );
@@ -436,9 +505,23 @@ function inIframe() {
return true; return true;
} }
} }
function getShadow(parent) {
function getController(id) { let result = [];
return document.querySelector(`div[data-vscid="${id}"]`); function getChild(parent) {
if (parent.firstElementChild) {
var child = parent.firstElementChild;
do {
result.push(child);
getChild(child);
if (child.shadowRoot) {
result.push(getShadow(child.shadowRoot));
}
child = child.nextElementSibling;
} while (child);
}
}
getChild(parent);
return result.flat(Infinity);
} }
function initializeNow(document) { function initializeNow(document) {
@@ -470,10 +553,10 @@ function initializeNow(document) {
if (inIframe()) docs.push(window.top.document); if (inIframe()) docs.push(window.top.document);
} catch (e) {} } catch (e) {}
docs.forEach(function(doc) { docs.forEach(function (doc) {
doc.addEventListener( doc.addEventListener(
"keydown", "keydown",
function(event) { function (event) {
var keyCode = event.keyCode; var keyCode = event.keyCode;
log("Processing keydown event: " + keyCode, 6); log("Processing keydown event: " + keyCode, 6);
@@ -501,15 +584,13 @@ function initializeNow(document) {
} }
// Ignore keydown event if typing in a page without vsc // Ignore keydown event if typing in a page without vsc
if ( if (!tc.mediaElements.length) {
!document.querySelector(".vsc-controller")
) {
return false; return false;
} }
var item = tc.settings.keyBindings.find(item => item.key === keyCode); var item = tc.settings.keyBindings.find((item) => item.key === keyCode);
if (item) { if (item) {
runAction(item.action, document, item.value); runAction(item.action, item.value);
if (item.force === "true") { if (item.force === "true") {
// disable websites key bindings // disable websites key bindings
event.preventDefault(); event.preventDefault();
@@ -535,8 +616,7 @@ function initializeNow(document) {
if (added) { if (added) {
node.vsc = new tc.videoController(node, parent); node.vsc = new tc.videoController(node, parent);
} else { } else {
let id = node.dataset["vscid"]; if (node.vsc) {
if (id) {
node.vsc.remove(); node.vsc.remove();
} }
} }
@@ -548,22 +628,38 @@ function initializeNow(document) {
} }
} }
var observer = new MutationObserver(function(mutations) { var observer = new MutationObserver(function (mutations) {
// Process the DOM nodes lazily // Process the DOM nodes lazily
requestIdleCallback( requestIdleCallback(
_ => { (_) => {
mutations.forEach(function(mutation) { mutations.forEach(function (mutation) {
switch (mutation.type) { switch (mutation.type) {
case "childList": case "childList":
forEach.call(mutation.addedNodes, function(node) { mutation.addedNodes.forEach(function (node) {
if (typeof node === "function") return; if (typeof node === "function") return;
checkForVideo(node, node.parentNode || mutation.target, true); checkForVideo(node, node.parentNode || mutation.target, true);
}); });
forEach.call(mutation.removedNodes, function(node) { mutation.removedNodes.forEach(function (node) {
if (typeof node === "function") return; if (typeof node === "function") return;
checkForVideo(node, node.parentNode || mutation.target, false); checkForVideo(node, node.parentNode || mutation.target, false);
}); });
break; break;
case "attributes":
if (
mutation.target.attributes["aria-hidden"] &&
mutation.target.attributes["aria-hidden"].value == "false"
) {
var flattenedNodes = getShadow(document.body);
var node = flattenedNodes.filter(
(x) => x.tagName == "VIDEO"
)[0];
if (node) {
if (node.vsc)
node.vsc.remove();
checkForVideo(node, node.parentNode || mutation.target, true);
}
}
break;
} }
}); });
}, },
@@ -582,12 +678,12 @@ function initializeNow(document) {
var mediaTags = document.querySelectorAll("video"); var mediaTags = document.querySelectorAll("video");
} }
forEach.call(mediaTags, function(video) { mediaTags.forEach(function (video) {
video.vsc = new tc.videoController(video); video.vsc = new tc.videoController(video);
}); });
var frameTags = document.getElementsByTagName("iframe"); var frameTags = document.getElementsByTagName("iframe");
forEach.call(frameTags, function(frame) { Array.prototype.forEach.call(frameTags, function (frame) {
// Ignore frames we don't have permission to access (different origin). // Ignore frames we don't have permission to access (different origin).
try { try {
var childDocument = frame.contentDocument; var childDocument = frame.contentDocument;
@@ -599,41 +695,44 @@ function initializeNow(document) {
log("End initializeNow", 5); log("End initializeNow", 5);
} }
function setSpeed(controller, video, speed) { function setSpeed(video, speed) {
log("setSpeed started: " + speed, 5); log("setSpeed started: " + speed, 5);
var speedvalue = speed.toFixed(2); var speedvalue = speed.toFixed(2);
video.playbackRate = Number(speedvalue); if (tc.settings.forceLastSavedSpeed) {
video.dispatchEvent(
new CustomEvent("ratechange", {
detail: { origin: "videoSpeed", speed: speedvalue }
})
);
} else {
video.playbackRate = Number(speedvalue);
}
var speedIndicator = video.vsc.speedIndicator;
speedIndicator.textContent = speedvalue;
tc.settings.lastSpeed = speed;
refreshCoolDown(); refreshCoolDown();
log("setSpeed finished: " + speed, 5); log("setSpeed finished: " + speed, 5);
} }
function runAction(action, document, value, e) { function runAction(action, value, e) {
log("runAction Begin", 5); log("runAction Begin", 5);
if (tc.settings.audioBoolean) {
var mediaTags = document.querySelectorAll("video,audio");
} else {
var mediaTags = document.querySelectorAll("video");
}
mediaTags.forEach = Array.prototype.forEach; var mediaTags = tc.mediaElements;
// Get the controller that was used if called from a button press event e // Get the controller that was used if called from a button press event e
if (e) { if (e) {
var targetController = e.target.getRootNode().host; var targetController = e.target.getRootNode().host;
} }
mediaTags.forEach(function(v) { mediaTags.forEach(function (v) {
var id = v.dataset["vscid"]; var controller = v.vsc.div;
var controller = getController(id);
// Don't change video speed if the video has a different controller // Don't change video speed if the video has a different controller
if (e && !(targetController == controller)) { if (e && !(targetController == controller)) {
return; return;
} }
// Controller may have been (force) removed by the site, guard to prevent crashes but run the command showController(controller);
if (controller) {
showController(controller);
}
if (!v.classList.contains("vsc-cancelled")) { if (!v.classList.contains("vsc-cancelled")) {
if (action === "rewind") { if (action === "rewind") {
@@ -650,16 +749,16 @@ function runAction(action, document, value, e) {
(v.playbackRate < 0.1 ? 0.0 : v.playbackRate) + value, (v.playbackRate < 0.1 ? 0.0 : v.playbackRate) + value,
16 16
); );
setSpeed(controller, v, s); setSpeed(v, s);
} else if (action === "slower") { } else if (action === "slower") {
log("Decrease speed", 5); log("Decrease speed", 5);
// Video min rate is 0.0625: // Video min rate is 0.0625:
// https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/media/html_media_element.cc?gsn=kMinRate&l=165 // 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); var s = Math.max(v.playbackRate - value, 0.07);
setSpeed(controller, v, s); setSpeed(v, s);
} else if (action === "reset") { } else if (action === "reset") {
log("Reset speed", 5); log("Reset speed", 5);
resetSpeed(v, controller, 1.0); resetSpeed(v, 1.0);
} else if (action === "display") { } else if (action === "display") {
log("Showing controller", 5); log("Showing controller", 5);
controller.classList.add("vsc-manual"); controller.classList.add("vsc-manual");
@@ -682,13 +781,13 @@ function runAction(action, document, value, e) {
); );
} }
} else if (action === "drag") { } else if (action === "drag") {
handleDrag(v, controller, e); handleDrag(v, e);
} else if (action === "fast") { } else if (action === "fast") {
resetSpeed(v, controller, value); resetSpeed(v, value);
} else if (action === "pause") { } else if (action === "pause") {
pause(v); pause(v);
} else if (action === "muted") { } else if (action === "muted") {
muted(v, value); muted(v);
} else if (action === "mark") { } else if (action === "mark") {
setMark(v); setMark(v);
} else if (action === "jump") { } else if (action === "jump") {
@@ -709,28 +808,28 @@ function pause(v) {
} }
} }
function resetSpeed(v, controller, target) { function resetSpeed(v, target) {
if (v.playbackRate === target) { if (v.playbackRate === target) {
if (v.playbackRate === getKeyBindings("reset")) { if (v.playbackRate === getKeyBindings("reset")) {
if (target !== 1.0) { if (target !== 1.0) {
log("Resetting playback speed to 1.0", 4); log("Resetting playback speed to 1.0", 4);
setSpeed(controller, v, 1.0); setSpeed(v, 1.0);
} else { } else {
log('Toggling playback speed to "fast" speed', 4); log('Toggling playback speed to "fast" speed', 4);
setSpeed(controller, v, getKeyBindings("fast")); setSpeed(v, getKeyBindings("fast"));
} }
} else { } else {
log('Toggling playback speed to "reset" speed', 4); log('Toggling playback speed to "reset" speed', 4);
setSpeed(controller, v, getKeyBindings("reset")); setSpeed(v, getKeyBindings("reset"));
} }
} else { } else {
log('Toggling playback speed to "reset" speed', 4); log('Toggling playback speed to "reset" speed', 4);
setKeyBindings("reset", v.playbackRate); setKeyBindings("reset", v.playbackRate);
setSpeed(controller, v, target); setSpeed(v, target);
} }
} }
function muted(v, value) { function muted(v) {
v.muted = v.muted !== true; v.muted = v.muted !== true;
} }
@@ -746,7 +845,8 @@ function jumpToMark(v) {
} }
} }
function handleDrag(video, controller, e) { function handleDrag(video, e) {
const controller = video.vsc.div;
const shadowController = controller.shadowRoot.querySelector("#controller"); const shadowController = controller.shadowRoot.querySelector("#controller");
// Find nearest parent of same size as video parent. // Find nearest parent of same size as video parent.
@@ -768,7 +868,7 @@ function handleDrag(video, controller, e) {
parseInt(shadowController.style.top) parseInt(shadowController.style.top)
]; ];
const startDragging = e => { const startDragging = (e) => {
let style = shadowController.style; let style = shadowController.style;
let dx = e.clientX - initialMouseXY[0]; let dx = e.clientX - initialMouseXY[0];
let dy = e.clientY - initialMouseXY[1]; let dy = e.clientY - initialMouseXY[1];
@@ -790,18 +890,16 @@ function handleDrag(video, controller, e) {
parentElement.addEventListener("mousemove", startDragging); parentElement.addEventListener("mousemove", startDragging);
} }
var timer; var timer = null;
var animation = false;
function showController(controller) { function showController(controller) {
log("Showing controller", 4); log("Showing controller", 4);
controller.classList.add("vcs-show"); controller.classList.add("vcs-show");
if (animation) clearTimeout(timer); if (timer) clearTimeout(timer);
animation = true; timer = setTimeout(function () {
timer = setTimeout(function() {
controller.classList.remove("vcs-show"); controller.classList.remove("vcs-show");
animation = false; timer = false;
log("Hiding controller", 5); log("Hiding controller", 5);
}, 2000); }, 2000);
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "Video Speed Controller", "name": "Video Speed Controller",
"short_name": "videospeed", "short_name": "videospeed",
"version": "0.6.1", "version": "0.6.3",
"manifest_version": 2, "manifest_version": 2,
"description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts", "description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts",
"homepage_url": "https://github.com/codebicycle/videospeed", "homepage_url": "https://github.com/codebicycle/videospeed",

View File

@@ -147,6 +147,11 @@
<label for="rememberSpeed">Remember playback speed</label> <label for="rememberSpeed">Remember playback speed</label>
<input id="rememberSpeed" type="checkbox" /> <input id="rememberSpeed" type="checkbox" />
</div> </div>
<div class="row">
<label for="forceLastSavedSpeed">Force last saved speed<br />
<em>Useful for video players that override the speeds set by VideoSpeed</em></label>
<input id="forceLastSavedSpeed" type="checkbox" />
</div>
<div class="row"> <div class="row">
<label for="audioBoolean">Work on audio</label> <label for="audioBoolean">Work on audio</label>
<input id="audioBoolean" type="checkbox" /> <input id="audioBoolean" type="checkbox" />

View File

@@ -6,6 +6,7 @@ var tcDefaults = {
rememberSpeed: false, // default: false rememberSpeed: false, // default: false
audioBoolean: false, // default: false audioBoolean: false, // default: false
startHidden: false, // default: false startHidden: false, // default: false
forceLastSavedSpeed: false, //default: false
enabled: true, // default enabled enabled: true, // default enabled
controllerOpacity: 0.3, // default: 0.3 controllerOpacity: 0.3, // default: 0.3
keyBindings: [ keyBindings: [
@@ -17,8 +18,7 @@ var tcDefaults = {
{ action: "reset", key: 82, value: 1, force: false, predefined: true }, // R { action: "reset", key: 82, value: 1, force: false, predefined: true }, // R
{ action: "fast", key: 71, value: 1.8, force: false, predefined: true } // G { action: "fast", key: 71, value: 1.8, force: false, predefined: true } // G
], ],
blacklist: blacklist: `www.instagram.com
`www.instagram.com
twitter.com twitter.com
imgur.com imgur.com
teams.microsoft.com teams.microsoft.com
@@ -186,7 +186,7 @@ function validate() {
document document
.getElementById("blacklist") .getElementById("blacklist")
.value.split("\n") .value.split("\n")
.forEach(match => { .forEach((match) => {
match = match.replace(regStrip, ""); match = match.replace(regStrip, "");
if (match.startsWith("/")) { if (match.startsWith("/")) {
try { try {
@@ -208,11 +208,12 @@ function save_options() {
return; return;
} }
keyBindings = []; keyBindings = [];
Array.from(document.querySelectorAll(".customs")).forEach(item => Array.from(document.querySelectorAll(".customs")).forEach((item) =>
createKeyBindings(item) createKeyBindings(item)
); // Remove added shortcuts ); // Remove added shortcuts
var rememberSpeed = document.getElementById("rememberSpeed").checked; var rememberSpeed = document.getElementById("rememberSpeed").checked;
var forceLastSavedSpeed = document.getElementById("forceLastSavedSpeed").checked;
var audioBoolean = document.getElementById("audioBoolean").checked; var audioBoolean = document.getElementById("audioBoolean").checked;
var enabled = document.getElementById("enabled").checked; var enabled = document.getElementById("enabled").checked;
var startHidden = document.getElementById("startHidden").checked; var startHidden = document.getElementById("startHidden").checked;
@@ -235,6 +236,7 @@ function save_options() {
chrome.storage.sync.set( chrome.storage.sync.set(
{ {
rememberSpeed: rememberSpeed, rememberSpeed: rememberSpeed,
forceLastSavedSpeed: forceLastSavedSpeed,
audioBoolean: audioBoolean, audioBoolean: audioBoolean,
enabled: enabled, enabled: enabled,
startHidden: startHidden, startHidden: startHidden,
@@ -242,11 +244,11 @@ function save_options() {
keyBindings: keyBindings, keyBindings: keyBindings,
blacklist: blacklist.replace(regStrip, "") blacklist: blacklist.replace(regStrip, "")
}, },
function() { function () {
// Update status to let user know options were saved. // Update status to let user know options were saved.
var status = document.getElementById("status"); var status = document.getElementById("status");
status.textContent = "Options saved"; status.textContent = "Options saved";
setTimeout(function() { setTimeout(function () {
status.textContent = ""; status.textContent = "";
}, 1000); }, 1000);
} }
@@ -255,8 +257,9 @@ function save_options() {
// Restores options from chrome.storage // Restores options from chrome.storage
function restore_options() { function restore_options() {
chrome.storage.sync.get(tcDefaults, function(storage) { chrome.storage.sync.get(tcDefaults, function (storage) {
document.getElementById("rememberSpeed").checked = storage.rememberSpeed; document.getElementById("rememberSpeed").checked = storage.rememberSpeed;
document.getElementById("forceLastSavedSpeed").checked = storage.forceLastSavedSpeed;
document.getElementById("audioBoolean").checked = storage.audioBoolean; document.getElementById("audioBoolean").checked = storage.audioBoolean;
document.getElementById("enabled").checked = storage.enabled; document.getElementById("enabled").checked = storage.enabled;
document.getElementById("startHidden").checked = storage.startHidden; document.getElementById("startHidden").checked = storage.startHidden;
@@ -265,7 +268,7 @@ function restore_options() {
document.getElementById("blacklist").value = storage.blacklist; document.getElementById("blacklist").value = storage.blacklist;
// ensure that there is a "display" binding for upgrades from versions that had it as a separate binding // 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) { if (storage.keyBindings.filter((x) => x.action == "display").length == 0) {
storage.keyBindings.push({ storage.keyBindings.push({
action: "display", action: "display",
value: 0, value: 0,
@@ -317,15 +320,15 @@ function restore_options() {
} }
function restore_defaults() { function restore_defaults() {
chrome.storage.sync.set(tcDefaults, function() { chrome.storage.sync.set(tcDefaults, function () {
restore_options(); restore_options();
document document
.querySelectorAll(".removeParent") .querySelectorAll(".removeParent")
.forEach(button => button.click()); // Remove added shortcuts .forEach((button) => button.click()); // Remove added shortcuts
// Update status to let user know options were saved. // Update status to let user know options were saved.
var status = document.getElementById("status"); var status = document.getElementById("status");
status.textContent = "Default options restored"; status.textContent = "Default options restored";
setTimeout(function() { setTimeout(function () {
status.textContent = ""; status.textContent = "";
}, 1000); }, 1000);
}); });
@@ -334,10 +337,10 @@ function restore_defaults() {
function show_experimental() { function show_experimental() {
document document
.querySelectorAll(".customForce") .querySelectorAll(".customForce")
.forEach(item => (item.style.display = "inline-block")); .forEach((item) => (item.style.display = "inline-block"));
} }
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function () {
restore_options(); restore_options();
document.getElementById("save").addEventListener("click", save_options); document.getElementById("save").addEventListener("click", save_options);
@@ -356,25 +359,25 @@ document.addEventListener("DOMContentLoaded", function() {
funcName(event); funcName(event);
} }
document.addEventListener("keypress", event => { document.addEventListener("keypress", (event) => {
eventCaller(event, "customValue", inputFilterNumbersOnly); eventCaller(event, "customValue", inputFilterNumbersOnly);
}); });
document.addEventListener("focus", event => { document.addEventListener("focus", (event) => {
eventCaller(event, "customKey", inputFocus); eventCaller(event, "customKey", inputFocus);
}); });
document.addEventListener("blur", event => { document.addEventListener("blur", (event) => {
eventCaller(event, "customKey", inputBlur); eventCaller(event, "customKey", inputBlur);
}); });
document.addEventListener("keydown", event => { document.addEventListener("keydown", (event) => {
eventCaller(event, "customKey", recordKeyPress); eventCaller(event, "customKey", recordKeyPress);
}); });
document.addEventListener("click", event => { document.addEventListener("click", (event) => {
eventCaller(event, "removeParent", function() { eventCaller(event, "removeParent", function () {
event.target.parentNode.remove(); event.target.parentNode.remove();
}); });
}); });
document.addEventListener("change", event => { document.addEventListener("change", (event) => {
eventCaller(event, "customDo", function() { eventCaller(event, "customDo", function () {
if (customActionsNoValues.includes(event.target.value)) { if (customActionsNoValues.includes(event.target.value)) {
event.target.nextElementSibling.nextElementSibling.disabled = true; event.target.nextElementSibling.nextElementSibling.disabled = true;
event.target.nextElementSibling.nextElementSibling.value = 0; event.target.nextElementSibling.nextElementSibling.value = 0;

View File

@@ -1,25 +1,25 @@
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function () {
document.querySelector("#config").addEventListener("click", function() { document.querySelector("#config").addEventListener("click", function () {
window.open(chrome.runtime.getURL("options.html")); window.open(chrome.runtime.getURL("options.html"));
}); });
document.querySelector('#about').addEventListener('click', function() { document.querySelector("#about").addEventListener("click", function () {
window.open("https://github.com/codebicycle/videospeed"); window.open("https://github.com/codebicycle/videospeed");
}); });
document.querySelector('#feedback').addEventListener('click', function() { document.querySelector("#feedback").addEventListener("click", function () {
window.open("https://github.com/codebicycle/videospeed/issues"); window.open("https://github.com/codebicycle/videospeed/issues");
}); });
document.querySelector("#enable").addEventListener("click", function() { document.querySelector("#enable").addEventListener("click", function () {
toggleEnabled(true, settingsSavedReloadMessage); toggleEnabled(true, settingsSavedReloadMessage);
}); });
document.querySelector("#disable").addEventListener("click", function() { document.querySelector("#disable").addEventListener("click", function () {
toggleEnabled(false, settingsSavedReloadMessage); toggleEnabled(false, settingsSavedReloadMessage);
}); });
chrome.storage.sync.get({ enabled: true }, function(storage) { chrome.storage.sync.get({ enabled: true }, function (storage) {
toggleEnabledUI(storage.enabled); toggleEnabledUI(storage.enabled);
}); });
@@ -28,7 +28,7 @@ document.addEventListener("DOMContentLoaded", function() {
{ {
enabled: enabled enabled: enabled
}, },
function() { function () {
toggleEnabledUI(enabled); toggleEnabledUI(enabled);
if (callback) callback(enabled); if (callback) callback(enabled);
} }

View File

@@ -1,6 +1,6 @@
* { * {
line-height: 1.8em; line-height: 1.8em;
font-family: Verdana, Geneva, sans-serif; font-family: sans-serif;
font-size: 13px; font-size: 13px;
} }
@@ -16,8 +16,8 @@
background: black; background: black;
color: white; color: white;
border-radius: 5px; border-radius: 30px;
padding: 5px; padding: 6px 12px 6px 12px;
margin: 10px 10px 10px 15px; margin: 10px 10px 10px 15px;
cursor: default; cursor: default;
@@ -57,17 +57,19 @@
} }
button { button {
opacity: 1;
cursor: pointer; cursor: pointer;
color: black; color: black;
background: white; background: white;
font-weight: bold; font-weight: normal;
border-radius: 5px; border-radius: 20px;
padding: 1px 6px 3px 6px; padding: 1px 5px 3px 5px;
font-size: 14px; font-size: 14px;
line-height: 14px; line-height: 14px;
border: 1px solid white; border: 0px solid white;
font-family: "Lucida Console", Monaco, monospace; font-family: "Lucida Console", Monaco, monospace;
margin-bottom: 2px; margin: 0px 2px 2px 2px;
transition: background 0.2s, color 0.2s;
} }
button:focus { button:focus {
@@ -76,10 +78,14 @@ button:focus {
button:hover { button:hover {
opacity: 1; opacity: 1;
background: #2196f3;
color: #ffffff;
} }
button:active { button:active {
background: #ccc; background: #2196f3;
color: #ffffff;
font-weight: bold;
} }
button.rw { button.rw {
@@ -87,6 +93,6 @@ button.rw {
} }
button.hideButton { button.hideButton {
opacity: 0.65;
margin-right: 2px; margin-right: 2px;
opacity: 0.5;
} }