mirror of
https://github.com/SoPat712/videospeed.git
synced 2025-08-21 09:58:45 -04:00
Merge upstream 0.6.3
This commit is contained in:
@@ -9,6 +9,11 @@
|
||||
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 */
|
||||
/* YouTube player */
|
||||
.ytp-hide-info-bar .vsc-controller {
|
||||
|
408
inject.js
408
inject.js
@@ -8,6 +8,7 @@ var tc = {
|
||||
|
||||
displayKeyCode: 86, // default: V
|
||||
rememberSpeed: false, // default: false
|
||||
forceLastSavedSpeed: false, //default: false
|
||||
audioBoolean: false, // default: false
|
||||
startHidden: false, // default: false
|
||||
controllerOpacity: 0.3, // default: 0.3
|
||||
@@ -21,7 +22,10 @@ var tc = {
|
||||
`.replace(regStrip, ""),
|
||||
defaultLogLevel: 4,
|
||||
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)
|
||||
@@ -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
|
||||
if (storage.keyBindings.length == 0) {
|
||||
// if first initialization of 0.5.3
|
||||
@@ -107,6 +111,7 @@ chrome.storage.sync.get(tc.settings, function(storage) {
|
||||
version: tc.settings.version,
|
||||
displayKeyCode: tc.settings.displayKeyCode,
|
||||
rememberSpeed: tc.settings.rememberSpeed,
|
||||
forceLastSavedSpeed: tc.settings.forceLastSavedSpeed,
|
||||
audioBoolean: tc.settings.audioBoolean,
|
||||
startHidden: tc.settings.startHidden,
|
||||
enabled: tc.settings.enabled,
|
||||
@@ -117,6 +122,7 @@ chrome.storage.sync.get(tc.settings, function(storage) {
|
||||
tc.settings.lastSpeed = Number(storage.lastSpeed);
|
||||
tc.settings.displayKeyCode = Number(storage.displayKeyCode);
|
||||
tc.settings.rememberSpeed = Boolean(storage.rememberSpeed);
|
||||
tc.settings.forceLastSavedSpeed = Boolean(storage.forceLastSavedSpeed);
|
||||
tc.settings.audioBoolean = Boolean(storage.audioBoolean);
|
||||
tc.settings.enabled = Boolean(storage.enabled);
|
||||
tc.settings.startHidden = Boolean(storage.startHidden);
|
||||
@@ -124,7 +130,9 @@ chrome.storage.sync.get(tc.settings, function(storage) {
|
||||
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) {
|
||||
if (
|
||||
tc.settings.keyBindings.filter((x) => x.action == "display").length == 0
|
||||
) {
|
||||
tc.settings.keyBindings.push({
|
||||
action: "display",
|
||||
key: Number(storage.displayKeyCode) || 86,
|
||||
@@ -137,32 +145,42 @@ chrome.storage.sync.get(tc.settings, function(storage) {
|
||||
initializeWhenReady(document);
|
||||
});
|
||||
|
||||
var forEach = Array.prototype.forEach;
|
||||
|
||||
function getKeyBindings(action, what = "value") {
|
||||
try {
|
||||
return tc.settings.keyBindings.find(item => item.action === action)[what];
|
||||
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;
|
||||
tc.settings.keyBindings.find((item) => item.action === action)[
|
||||
"value"
|
||||
] = value;
|
||||
}
|
||||
|
||||
function defineVideoController() {
|
||||
tc.videoController = function(target, parent) {
|
||||
if (target.dataset["vscid"]) {
|
||||
// Data structures
|
||||
// ---------------
|
||||
// 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;
|
||||
}
|
||||
|
||||
tc.mediaElements.push(target);
|
||||
|
||||
this.video = target;
|
||||
this.parent = target.parentElement || parent;
|
||||
this.document = target.ownerDocument;
|
||||
this.id = Math.random()
|
||||
.toString(36)
|
||||
.substr(2, 9);
|
||||
storedSpeed = tc.settings.speeds[target.currentSrc];
|
||||
if (!tc.settings.rememberSpeed) {
|
||||
if (!storedSpeed) {
|
||||
@@ -183,48 +201,50 @@ function defineVideoController() {
|
||||
|
||||
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(
|
||||
"play",
|
||||
(this.handlePlay = 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);
|
||||
event.target.playbackRate = storedSpeed;
|
||||
}.bind(this))
|
||||
(this.handlePlay = mediaEventAction.bind(this))
|
||||
);
|
||||
|
||||
var observer = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
target.addEventListener(
|
||||
"seeked",
|
||||
(this.handleSeek = mediaEventAction.bind(this))
|
||||
);
|
||||
|
||||
var observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (
|
||||
mutation.type === "attributes" &&
|
||||
(mutation.attributeName === "src" ||
|
||||
mutation.attributeName === "currentSrc")
|
||||
) {
|
||||
var controller = getController(this.id);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
log("mutation of A/V element", 5);
|
||||
var controller = this.div;
|
||||
if (!mutation.target.src && !mutation.target.currentSrc) {
|
||||
controller.classList.add("vsc-nosource");
|
||||
} else {
|
||||
@@ -238,25 +258,29 @@ function defineVideoController() {
|
||||
});
|
||||
};
|
||||
|
||||
tc.videoController.prototype.remove = function() {
|
||||
tc.videoController.prototype.remove = function () {
|
||||
this.div.remove();
|
||||
this.video.removeEventListener("play", this.handlePlay);
|
||||
delete this.video.dataset["vscid"];
|
||||
this.video.removeEventListener("seek", this.handleSeek);
|
||||
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);
|
||||
var document = this.document;
|
||||
var speed = this.video.playbackRate.toFixed(2),
|
||||
top = Math.max(this.video.offsetTop, 0) + "px",
|
||||
left = Math.max(this.video.offsetLeft, 0) + "px";
|
||||
const document = this.video.ownerDocument;
|
||||
const speed = this.video.playbackRate.toFixed(2);
|
||||
const rect = this.video.getBoundingClientRect();
|
||||
const top = Math.max(rect.top, 0) + "px";
|
||||
const left = Math.max(rect.left, 0) + "px";
|
||||
|
||||
log("Speed variable set to: " + speed, 5);
|
||||
|
||||
var wrapper = document.createElement("div");
|
||||
wrapper.classList.add("vsc-controller");
|
||||
wrapper.dataset["vscid"] = this.id;
|
||||
|
||||
if (!this.video.currentSrc) {
|
||||
wrapper.classList.add("vsc-nosource");
|
||||
@@ -278,35 +302,49 @@ function defineVideoController() {
|
||||
<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="slower">−</button>
|
||||
<button data-action="faster">+</button>
|
||||
<button data-action="advance" class="rw">»</button>
|
||||
<button data-action="display" class="hideButton">x</button>
|
||||
<button data-action="display" class="hideButton">×</button>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
shadow.innerHTML = shadowTemplate;
|
||||
shadow.querySelector(".draggable").addEventListener("mousedown", e => {
|
||||
runAction(e.target.dataset["action"], document, false, e);
|
||||
shadow.querySelector(".draggable").addEventListener(
|
||||
"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) {
|
||||
button.onclick = e => {
|
||||
runAction(
|
||||
e.target.dataset["action"],
|
||||
document,
|
||||
getKeyBindings(e.target.dataset["action"]),
|
||||
e
|
||||
);
|
||||
};
|
||||
});
|
||||
shadow
|
||||
.querySelector("#controller")
|
||||
.addEventListener("click", (e) => e.stopPropagation(), false);
|
||||
shadow
|
||||
.querySelector("#controller")
|
||||
.addEventListener("mousedown", (e) => e.stopPropagation(), false);
|
||||
|
||||
this.speedIndicator = shadow.querySelector("span");
|
||||
var fragment = document.createDocumentFragment();
|
||||
fragment.appendChild(wrapper);
|
||||
|
||||
this.video.dataset["vscid"] = this.id;
|
||||
|
||||
switch (true) {
|
||||
case location.hostname == "www.amazon.com":
|
||||
case location.hostname == "www.reddit.com":
|
||||
@@ -314,13 +352,17 @@ function defineVideoController() {
|
||||
// insert before parent to bypass overlay
|
||||
this.parent.parentElement.insertBefore(fragment, this.parent);
|
||||
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":
|
||||
// insert after parent for correct stacking context
|
||||
this.parent
|
||||
.getRootNode()
|
||||
.querySelector(".scrim")
|
||||
.prepend(fragment);
|
||||
|
||||
this.parent.getRootNode().querySelector(".scrim").prepend(fragment);
|
||||
default:
|
||||
// Note: when triggered via a MutationRecord, it's possible that the
|
||||
// target is not the immediate parent. This appends the controller as
|
||||
@@ -338,7 +380,7 @@ function escapeStringRegExp(str) {
|
||||
|
||||
function isBlacklisted() {
|
||||
blacklisted = false;
|
||||
tc.settings.blacklist.split("\n").forEach(match => {
|
||||
tc.settings.blacklist.split("\n").forEach((match) => {
|
||||
match = match.replace(regStrip, "");
|
||||
if (match.length == 0) {
|
||||
return;
|
||||
@@ -368,41 +410,68 @@ function refreshCoolDown() {
|
||||
if (coolDown) {
|
||||
clearTimeout(coolDown);
|
||||
}
|
||||
coolDown = setTimeout(function() {
|
||||
coolDown = setTimeout(function () {
|
||||
coolDown = false;
|
||||
}, 1000);
|
||||
log("End refreshCoolDown", 5);
|
||||
}
|
||||
|
||||
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",
|
||||
function(event) {
|
||||
function (event) {
|
||||
if (coolDown) {
|
||||
log("Speed event propagation blocked", 4);
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
var controller = event.target.parentElement.querySelector(
|
||||
".vsc-controller"
|
||||
);
|
||||
var speedIndicator = controller.shadowRoot.querySelector("span");
|
||||
var video = controller.parentElement.querySelector("video");
|
||||
var src = video.currentSrc;
|
||||
var speed = video.playbackRate.toFixed(2);
|
||||
var video = event.target;
|
||||
|
||||
log("Playback rate changed to " + speed, 4);
|
||||
|
||||
log("Updating controller with new speed", 5);
|
||||
speedIndicator.textContent = speed;
|
||||
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", document, null, null);
|
||||
/**
|
||||
* If the last speed is forced, only update the speed based on events created by
|
||||
* video speed instead of all video speed change events.
|
||||
*/
|
||||
if (tc.settings.forceLastSavedSpeed) {
|
||||
if (event.detail && event.detail.origin === "videoSpeed") {
|
||||
video.playbackRate = event.detail.speed;
|
||||
updateSpeedFromEvent(video);
|
||||
} else {
|
||||
video.playbackRate = tc.settings.lastSpeed;
|
||||
}
|
||||
event.stopImmediatePropagation();
|
||||
} else {
|
||||
updateSpeedFromEvent(video);
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
@@ -436,9 +505,23 @@ function inIframe() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function getController(id) {
|
||||
return document.querySelector(`div[data-vscid="${id}"]`);
|
||||
function getShadow(parent) {
|
||||
let result = [];
|
||||
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) {
|
||||
@@ -470,10 +553,10 @@ function initializeNow(document) {
|
||||
if (inIframe()) docs.push(window.top.document);
|
||||
} catch (e) {}
|
||||
|
||||
docs.forEach(function(doc) {
|
||||
docs.forEach(function (doc) {
|
||||
doc.addEventListener(
|
||||
"keydown",
|
||||
function(event) {
|
||||
function (event) {
|
||||
var keyCode = event.keyCode;
|
||||
log("Processing keydown event: " + keyCode, 6);
|
||||
|
||||
@@ -501,15 +584,13 @@ function initializeNow(document) {
|
||||
}
|
||||
|
||||
// Ignore keydown event if typing in a page without vsc
|
||||
if (
|
||||
!document.querySelector(".vsc-controller")
|
||||
) {
|
||||
if (!tc.mediaElements.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var item = tc.settings.keyBindings.find(item => item.key === keyCode);
|
||||
var item = tc.settings.keyBindings.find((item) => item.key === keyCode);
|
||||
if (item) {
|
||||
runAction(item.action, document, item.value);
|
||||
runAction(item.action, item.value);
|
||||
if (item.force === "true") {
|
||||
// disable websites key bindings
|
||||
event.preventDefault();
|
||||
@@ -535,8 +616,7 @@ function initializeNow(document) {
|
||||
if (added) {
|
||||
node.vsc = new tc.videoController(node, parent);
|
||||
} else {
|
||||
let id = node.dataset["vscid"];
|
||||
if (id) {
|
||||
if (node.vsc) {
|
||||
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
|
||||
requestIdleCallback(
|
||||
_ => {
|
||||
mutations.forEach(function(mutation) {
|
||||
(_) => {
|
||||
mutations.forEach(function (mutation) {
|
||||
switch (mutation.type) {
|
||||
case "childList":
|
||||
forEach.call(mutation.addedNodes, function(node) {
|
||||
mutation.addedNodes.forEach(function (node) {
|
||||
if (typeof node === "function") return;
|
||||
checkForVideo(node, node.parentNode || mutation.target, true);
|
||||
});
|
||||
forEach.call(mutation.removedNodes, function(node) {
|
||||
mutation.removedNodes.forEach(function (node) {
|
||||
if (typeof node === "function") return;
|
||||
checkForVideo(node, node.parentNode || mutation.target, false);
|
||||
});
|
||||
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");
|
||||
}
|
||||
|
||||
forEach.call(mediaTags, function(video) {
|
||||
mediaTags.forEach(function (video) {
|
||||
video.vsc = new tc.videoController(video);
|
||||
});
|
||||
|
||||
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).
|
||||
try {
|
||||
var childDocument = frame.contentDocument;
|
||||
@@ -599,41 +695,44 @@ function initializeNow(document) {
|
||||
log("End initializeNow", 5);
|
||||
}
|
||||
|
||||
function setSpeed(controller, video, speed) {
|
||||
function setSpeed(video, speed) {
|
||||
log("setSpeed started: " + speed, 5);
|
||||
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();
|
||||
log("setSpeed finished: " + speed, 5);
|
||||
}
|
||||
|
||||
function runAction(action, document, value, e) {
|
||||
function runAction(action, value, e) {
|
||||
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
|
||||
if (e) {
|
||||
var targetController = e.target.getRootNode().host;
|
||||
}
|
||||
|
||||
mediaTags.forEach(function(v) {
|
||||
var id = v.dataset["vscid"];
|
||||
var controller = getController(id);
|
||||
mediaTags.forEach(function (v) {
|
||||
var controller = v.vsc.div;
|
||||
|
||||
// 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);
|
||||
}
|
||||
showController(controller);
|
||||
|
||||
if (!v.classList.contains("vsc-cancelled")) {
|
||||
if (action === "rewind") {
|
||||
@@ -650,16 +749,16 @@ function runAction(action, document, value, e) {
|
||||
(v.playbackRate < 0.1 ? 0.0 : v.playbackRate) + value,
|
||||
16
|
||||
);
|
||||
setSpeed(controller, v, s);
|
||||
setSpeed(v, s);
|
||||
} else if (action === "slower") {
|
||||
log("Decrease speed", 5);
|
||||
// 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
|
||||
var s = Math.max(v.playbackRate - value, 0.07);
|
||||
setSpeed(controller, v, s);
|
||||
setSpeed(v, s);
|
||||
} else if (action === "reset") {
|
||||
log("Reset speed", 5);
|
||||
resetSpeed(v, controller, 1.0);
|
||||
resetSpeed(v, 1.0);
|
||||
} else if (action === "display") {
|
||||
log("Showing controller", 5);
|
||||
controller.classList.add("vsc-manual");
|
||||
@@ -682,13 +781,13 @@ function runAction(action, document, value, e) {
|
||||
);
|
||||
}
|
||||
} else if (action === "drag") {
|
||||
handleDrag(v, controller, e);
|
||||
handleDrag(v, e);
|
||||
} else if (action === "fast") {
|
||||
resetSpeed(v, controller, value);
|
||||
resetSpeed(v, value);
|
||||
} else if (action === "pause") {
|
||||
pause(v);
|
||||
} else if (action === "muted") {
|
||||
muted(v, value);
|
||||
muted(v);
|
||||
} else if (action === "mark") {
|
||||
setMark(v);
|
||||
} 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 === getKeyBindings("reset")) {
|
||||
if (target !== 1.0) {
|
||||
log("Resetting playback speed to 1.0", 4);
|
||||
setSpeed(controller, v, 1.0);
|
||||
setSpeed(v, 1.0);
|
||||
} else {
|
||||
log('Toggling playback speed to "fast" speed', 4);
|
||||
setSpeed(controller, v, getKeyBindings("fast"));
|
||||
setSpeed(v, getKeyBindings("fast"));
|
||||
}
|
||||
} else {
|
||||
log('Toggling playback speed to "reset" speed', 4);
|
||||
setSpeed(controller, v, getKeyBindings("reset"));
|
||||
setSpeed(v, getKeyBindings("reset"));
|
||||
}
|
||||
} else {
|
||||
log('Toggling playback speed to "reset" speed', 4);
|
||||
setKeyBindings("reset", v.playbackRate);
|
||||
setSpeed(controller, v, target);
|
||||
setSpeed(v, target);
|
||||
}
|
||||
}
|
||||
|
||||
function muted(v, value) {
|
||||
function muted(v) {
|
||||
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");
|
||||
|
||||
// Find nearest parent of same size as video parent.
|
||||
@@ -768,7 +868,7 @@ function handleDrag(video, controller, e) {
|
||||
parseInt(shadowController.style.top)
|
||||
];
|
||||
|
||||
const startDragging = e => {
|
||||
const startDragging = (e) => {
|
||||
let style = shadowController.style;
|
||||
let dx = e.clientX - initialMouseXY[0];
|
||||
let dy = e.clientY - initialMouseXY[1];
|
||||
@@ -790,18 +890,16 @@ function handleDrag(video, controller, e) {
|
||||
parentElement.addEventListener("mousemove", startDragging);
|
||||
}
|
||||
|
||||
var timer;
|
||||
var animation = false;
|
||||
var timer = null;
|
||||
function showController(controller) {
|
||||
log("Showing controller", 4);
|
||||
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");
|
||||
animation = false;
|
||||
timer = false;
|
||||
log("Hiding controller", 5);
|
||||
}, 2000);
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Video Speed Controller",
|
||||
"short_name": "videospeed",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.3",
|
||||
"manifest_version": 2,
|
||||
"description": "Speed up, slow down, advance and rewind HTML5 audio/video with shortcuts",
|
||||
"homepage_url": "https://github.com/codebicycle/videospeed",
|
||||
|
@@ -147,6 +147,11 @@
|
||||
<label for="rememberSpeed">Remember playback speed</label>
|
||||
<input id="rememberSpeed" type="checkbox" />
|
||||
</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">
|
||||
<label for="audioBoolean">Work on audio</label>
|
||||
<input id="audioBoolean" type="checkbox" />
|
||||
|
45
options.js
45
options.js
@@ -6,6 +6,7 @@ var tcDefaults = {
|
||||
rememberSpeed: false, // default: false
|
||||
audioBoolean: false, // default: false
|
||||
startHidden: false, // default: false
|
||||
forceLastSavedSpeed: false, //default: false
|
||||
enabled: true, // default enabled
|
||||
controllerOpacity: 0.3, // default: 0.3
|
||||
keyBindings: [
|
||||
@@ -17,8 +18,7 @@ var tcDefaults = {
|
||||
{ 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
|
||||
blacklist: `www.instagram.com
|
||||
twitter.com
|
||||
imgur.com
|
||||
teams.microsoft.com
|
||||
@@ -186,7 +186,7 @@ function validate() {
|
||||
document
|
||||
.getElementById("blacklist")
|
||||
.value.split("\n")
|
||||
.forEach(match => {
|
||||
.forEach((match) => {
|
||||
match = match.replace(regStrip, "");
|
||||
if (match.startsWith("/")) {
|
||||
try {
|
||||
@@ -208,11 +208,12 @@ function save_options() {
|
||||
return;
|
||||
}
|
||||
keyBindings = [];
|
||||
Array.from(document.querySelectorAll(".customs")).forEach(item =>
|
||||
Array.from(document.querySelectorAll(".customs")).forEach((item) =>
|
||||
createKeyBindings(item)
|
||||
); // Remove added shortcuts
|
||||
|
||||
var rememberSpeed = document.getElementById("rememberSpeed").checked;
|
||||
var forceLastSavedSpeed = document.getElementById("forceLastSavedSpeed").checked;
|
||||
var audioBoolean = document.getElementById("audioBoolean").checked;
|
||||
var enabled = document.getElementById("enabled").checked;
|
||||
var startHidden = document.getElementById("startHidden").checked;
|
||||
@@ -235,6 +236,7 @@ function save_options() {
|
||||
chrome.storage.sync.set(
|
||||
{
|
||||
rememberSpeed: rememberSpeed,
|
||||
forceLastSavedSpeed: forceLastSavedSpeed,
|
||||
audioBoolean: audioBoolean,
|
||||
enabled: enabled,
|
||||
startHidden: startHidden,
|
||||
@@ -242,11 +244,11 @@ function save_options() {
|
||||
keyBindings: keyBindings,
|
||||
blacklist: blacklist.replace(regStrip, "")
|
||||
},
|
||||
function() {
|
||||
function () {
|
||||
// Update status to let user know options were saved.
|
||||
var status = document.getElementById("status");
|
||||
status.textContent = "Options saved";
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
status.textContent = "";
|
||||
}, 1000);
|
||||
}
|
||||
@@ -255,8 +257,9 @@ function save_options() {
|
||||
|
||||
// Restores options from chrome.storage
|
||||
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("forceLastSavedSpeed").checked = storage.forceLastSavedSpeed;
|
||||
document.getElementById("audioBoolean").checked = storage.audioBoolean;
|
||||
document.getElementById("enabled").checked = storage.enabled;
|
||||
document.getElementById("startHidden").checked = storage.startHidden;
|
||||
@@ -265,7 +268,7 @@ function restore_options() {
|
||||
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) {
|
||||
if (storage.keyBindings.filter((x) => x.action == "display").length == 0) {
|
||||
storage.keyBindings.push({
|
||||
action: "display",
|
||||
value: 0,
|
||||
@@ -317,15 +320,15 @@ function restore_options() {
|
||||
}
|
||||
|
||||
function restore_defaults() {
|
||||
chrome.storage.sync.set(tcDefaults, function() {
|
||||
chrome.storage.sync.set(tcDefaults, function () {
|
||||
restore_options();
|
||||
document
|
||||
.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.
|
||||
var status = document.getElementById("status");
|
||||
status.textContent = "Default options restored";
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
status.textContent = "";
|
||||
}, 1000);
|
||||
});
|
||||
@@ -334,10 +337,10 @@ function restore_defaults() {
|
||||
function show_experimental() {
|
||||
document
|
||||
.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();
|
||||
|
||||
document.getElementById("save").addEventListener("click", save_options);
|
||||
@@ -356,25 +359,25 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
funcName(event);
|
||||
}
|
||||
|
||||
document.addEventListener("keypress", event => {
|
||||
document.addEventListener("keypress", (event) => {
|
||||
eventCaller(event, "customValue", inputFilterNumbersOnly);
|
||||
});
|
||||
document.addEventListener("focus", event => {
|
||||
document.addEventListener("focus", (event) => {
|
||||
eventCaller(event, "customKey", inputFocus);
|
||||
});
|
||||
document.addEventListener("blur", event => {
|
||||
document.addEventListener("blur", (event) => {
|
||||
eventCaller(event, "customKey", inputBlur);
|
||||
});
|
||||
document.addEventListener("keydown", event => {
|
||||
document.addEventListener("keydown", (event) => {
|
||||
eventCaller(event, "customKey", recordKeyPress);
|
||||
});
|
||||
document.addEventListener("click", event => {
|
||||
eventCaller(event, "removeParent", function() {
|
||||
document.addEventListener("click", (event) => {
|
||||
eventCaller(event, "removeParent", function () {
|
||||
event.target.parentNode.remove();
|
||||
});
|
||||
});
|
||||
document.addEventListener("change", event => {
|
||||
eventCaller(event, "customDo", function() {
|
||||
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;
|
||||
|
18
popup.js
18
popup.js
@@ -1,25 +1,25 @@
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
document.querySelector("#config").addEventListener("click", function() {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.querySelector("#config").addEventListener("click", function () {
|
||||
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");
|
||||
});
|
||||
|
||||
document.querySelector('#feedback').addEventListener('click', function() {
|
||||
document.querySelector("#feedback").addEventListener("click", function () {
|
||||
window.open("https://github.com/codebicycle/videospeed/issues");
|
||||
});
|
||||
|
||||
document.querySelector("#enable").addEventListener("click", function() {
|
||||
document.querySelector("#enable").addEventListener("click", function () {
|
||||
toggleEnabled(true, settingsSavedReloadMessage);
|
||||
});
|
||||
|
||||
document.querySelector("#disable").addEventListener("click", function() {
|
||||
document.querySelector("#disable").addEventListener("click", function () {
|
||||
toggleEnabled(false, settingsSavedReloadMessage);
|
||||
});
|
||||
|
||||
chrome.storage.sync.get({ enabled: true }, function(storage) {
|
||||
chrome.storage.sync.get({ enabled: true }, function (storage) {
|
||||
toggleEnabledUI(storage.enabled);
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
{
|
||||
enabled: enabled
|
||||
},
|
||||
function() {
|
||||
function () {
|
||||
toggleEnabledUI(enabled);
|
||||
if (callback) callback(enabled);
|
||||
}
|
||||
|
26
shadow.css
26
shadow.css
@@ -1,6 +1,6 @@
|
||||
* {
|
||||
line-height: 1.8em;
|
||||
font-family: Verdana, Geneva, sans-serif;
|
||||
font-family: sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
background: black;
|
||||
color: white;
|
||||
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
border-radius: 30px;
|
||||
padding: 6px 12px 6px 12px;
|
||||
margin: 10px 10px 10px 15px;
|
||||
|
||||
cursor: default;
|
||||
@@ -57,17 +57,19 @@
|
||||
}
|
||||
|
||||
button {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
color: black;
|
||||
background: white;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
padding: 1px 6px 3px 6px;
|
||||
font-weight: normal;
|
||||
border-radius: 20px;
|
||||
padding: 1px 5px 3px 5px;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
border: 1px solid white;
|
||||
border: 0px solid white;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
margin-bottom: 2px;
|
||||
margin: 0px 2px 2px 2px;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
@@ -76,10 +78,14 @@ button:focus {
|
||||
|
||||
button:hover {
|
||||
opacity: 1;
|
||||
background: #2196f3;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background: #ccc;
|
||||
background: #2196f3;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button.rw {
|
||||
@@ -87,6 +93,6 @@ button.rw {
|
||||
}
|
||||
|
||||
button.hideButton {
|
||||
opacity: 0.65;
|
||||
margin-right: 2px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
Reference in New Issue
Block a user