Merge pull request #604 from ChadBailey/bugfix/584-twitch-not-working

Added ratechange cooldown to fix #72 #521 and #584
This commit is contained in:
Chad Bailey
2020-03-22 12:08:26 -05:00
committed by GitHub
5 changed files with 184 additions and 69 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ local
# IntelliJ IDEA # IntelliJ IDEA
.idea/ .idea/
node_modules

239
inject.js
View File

@@ -18,10 +18,41 @@ var tc = {
vine.co vine.co
imgur.com imgur.com
teams.microsoft.com teams.microsoft.com
`.replace(regStrip, "") `.replace(regStrip, ""),
defaultLogLevel: 4,
logLevel: 3
} }
}; };
/* Log levels (depends on caller specifying the correct level)
1 - none
2 - error
3 - warning
4 - info
5 - debug
6 - debug high verbosity + stack trace on each message
*/
function log(message, level) {
verbosity = tc.settings.logLevel;
if (typeof level === "undefined") {
level = tc.settings.defaultLogLevel;
}
if (verbosity >= level) {
if (level === 2) {
console.log("ERROR:" + message);
} else if (level === 3) {
console.log("WARNING:" + message);
} else if (level === 4) {
console.log("INFO:" + message);
} else if (level === 5) {
console.log("DEBUG:" + message);
} else if (level === 6) {
console.log("DEBUG (VERBOSE):" + message);
console.trace();
}
}
}
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) {
@@ -132,56 +163,54 @@ function defineVideoController() {
this.id = Math.random() this.id = Math.random()
.toString(36) .toString(36)
.substr(2, 9); .substr(2, 9);
storedSpeed = tc.settings.speeds[target.currentSrc];
// 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) { if (!tc.settings.rememberSpeed) {
if (!tc.settings.speeds[target.currentSrc]) { if (!storedSpeed) {
tc.settings.speeds[target.currentSrc] = this.speed; log(
"Overwriting stored speed to 1.0 due to rememberSpeed being disabled",
5
);
storedSpeed = 1.0;
} }
setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed
} else { } else {
tc.settings.speeds[target.currentSrc] = tc.settings.lastSpeed; log("Recalling stored speed due to rememberSpeed being enabled", 5);
storedSpeed = tc.settings.lastSpeed;
} }
target.playbackRate = tc.settings.speeds[target.currentSrc]; log("Explicitly setting playbackRate to: " + storedSpeed, 5);
target.playbackRate = storedSpeed;
this.div = this.initializeControls(); this.div = this.initializeControls();
target.addEventListener( target.addEventListener(
"play", "play",
(this.handlePlay = function(event) { (this.handlePlay = function(event) {
storedSpeed = tc.settings.speeds[event.target.currentSrc];
if (!tc.settings.rememberSpeed) { if (!tc.settings.rememberSpeed) {
if (!tc.settings.speeds[target.currentSrc]) { if (!storedSpeed) {
tc.settings.speeds[target.currentSrc] = this.speed; 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 setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed
} else { } else {
tc.settings.speeds[target.currentSrc] = tc.settings.lastSpeed; log(
} "Storing lastSpeed into tc.settings.speeds (rememberSpeed enabled)",
target.playbackRate = tc.settings.speeds[target.currentSrc]; 5
}.bind(this)) );
); storedSpeed = tc.settings.lastSpeed;
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.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);
} }
// 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)) }.bind(this))
); );
@@ -209,26 +238,22 @@ function defineVideoController() {
}); });
}; };
tc.videoController.prototype.getSpeed = function() {
return parseFloat(this.video.playbackRate).toFixed(2);
};
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);
this.video.removeEventListener("ratechange", this.handleRatechange);
delete this.video.dataset["vscid"]; delete this.video.dataset["vscid"];
delete this.video.vsc; delete this.video.vsc;
}; };
tc.videoController.prototype.initializeControls = function() { tc.videoController.prototype.initializeControls = function() {
log("initializeControls Begin", 5);
var document = this.document; var document = this.document;
var speed = parseFloat(tc.settings.speeds[this.video.currentSrc]).toFixed( var speed = this.video.playbackRate.toFixed(2),
2
),
top = Math.max(this.video.offsetTop, 0) + "px", top = Math.max(this.video.offsetTop, 0) + "px",
left = Math.max(this.video.offsetLeft, 0) + "px"; left = Math.max(this.video.offsetLeft, 0) + "px";
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; wrapper.dataset["vscid"] = this.id;
@@ -306,13 +331,13 @@ function defineVideoController() {
}; };
} }
function initializeWhenReady(document) { function escapeStringRegExp(str) {
escapeStringRegExp.matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
function escapeStringRegExp(str) { return str.replace(matchOperatorsRe, "\\$&");
return str.replace(escapeStringRegExp.matchOperatorsRe, "\\$&"); }
}
var blacklisted = false; function isBlacklisted() {
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) {
@@ -334,9 +359,60 @@ function initializeWhenReady(document) {
return; return;
} }
}); });
return blacklisted;
}
if (blacklisted) return; var coolDown = false;
function refreshCoolDown() {
log("Begin refreshCoolDown", 5);
if (coolDown) {
clearTimeout(coolDown);
}
coolDown = setTimeout(function() {
coolDown = false;
}, 1000);
log("End refreshCoolDown", 5);
}
function setupListener() {
document.body.addEventListener(
"ratechange",
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);
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);
},
true
);
}
function initializeWhenReady(document) {
log("Begin initializeWhenReady", 5);
if (isBlacklisted()) {
return;
}
window.onload = () => { window.onload = () => {
initializeNow(window.document); initializeNow(window.document);
}; };
@@ -351,6 +427,7 @@ function initializeWhenReady(document) {
}; };
} }
} }
log("End initializeWhenReady", 5);
} }
function inIframe() { function inIframe() {
try { try {
@@ -388,12 +465,19 @@ function getController(id) {
} }
function initializeNow(document) { function initializeNow(document) {
log("Begin initializeNow", 5);
if (!tc.settings.enabled) return; if (!tc.settings.enabled) return;
// enforce init-once due to redundant callers // enforce init-once due to redundant callers
if (!document.body || document.body.classList.contains("vsc-initialized")) { if (!document.body || document.body.classList.contains("vsc-initialized")) {
return; return;
} }
try {
setupListener();
} catch {
// no operation
}
document.body.classList.add("vsc-initialized"); document.body.classList.add("vsc-initialized");
log("initializeNow: vsc-initialized added to document body", 5);
if (document === window.document) { if (document === window.document) {
defineVideoController(); defineVideoController();
@@ -414,6 +498,7 @@ function initializeNow(document) {
"keydown", "keydown",
function(event) { function(event) {
var keyCode = event.keyCode; var keyCode = event.keyCode;
log("Processing keydown event: " + keyCode, 6);
// Ignore if following modifier is active. // Ignore if following modifier is active.
if ( if (
@@ -425,6 +510,7 @@ function initializeNow(document) {
event.getModifierState("Hyper") || event.getModifierState("Hyper") ||
event.getModifierState("OS") event.getModifierState("OS")
) { ) {
log("Keydown event ignored due to active modifier: " + keyCode, 5);
return; return;
} }
@@ -502,8 +588,10 @@ function initializeNow(document) {
}); });
break; break;
case "attributes": case "attributes":
if (mutation.target.attributes["aria-hidden"] && if (
mutation.target.attributes["aria-hidden"].value == "false") { mutation.target.attributes["aria-hidden"] &&
mutation.target.attributes["aria-hidden"].value == "false"
) {
var flattenedNodes = getShadow(document.body); var flattenedNodes = getShadow(document.body);
var node = flattenedNodes.filter(x => x.tagName == "VIDEO")[0]; var node = flattenedNodes.filter(x => x.tagName == "VIDEO")[0];
if (node) { if (node) {
@@ -549,9 +637,19 @@ function initializeNow(document) {
} }
initializeWhenReady(childDocument); initializeWhenReady(childDocument);
}); });
log("End initializeNow", 5);
}
function setSpeed(controller, video, speed) {
log("setSpeed started: " + speed, 5);
var speedvalue = speed.toFixed(2);
video.playbackRate = Number(speedvalue);
refreshCoolDown();
log("setSpeed finished: " + speed, 5);
} }
function runAction(action, document, value, e) { function runAction(action, document, value, e) {
log("runAction Begin", 5);
if (tc.settings.audioBoolean) { if (tc.settings.audioBoolean) {
var mediaTags = getShadow(document.body).filter(x => { var mediaTags = getShadow(document.body).filter(x => {
return x.tagName == "AUDIO" || x.tagName == "VIDEO"; return x.tagName == "AUDIO" || x.tagName == "VIDEO";
@@ -582,28 +680,35 @@ function runAction(action, document, value, e) {
if (!v.classList.contains("vsc-cancelled")) { if (!v.classList.contains("vsc-cancelled")) {
if (action === "rewind") { if (action === "rewind") {
log("Rewind", 5);
v.currentTime -= value; v.currentTime -= value;
} else if (action === "advance") { } else if (action === "advance") {
log("Fast forward", 5);
v.currentTime += value; v.currentTime += value;
} else if (action === "faster") { } else if (action === "faster") {
log("Increase speed", 5);
// Maximum playback speed in Chrome is set to 16: // Maximum playback speed in Chrome is set to 16:
// https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/media/html_media_element.cc?gsn=kMinRate&l=166 // 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( var s = Math.min(
(v.playbackRate < 0.1 ? 0.0 : v.playbackRate) + value, (v.playbackRate < 0.1 ? 0.0 : v.playbackRate) + value,
16 16
); );
v.playbackRate = Number(s.toFixed(2)); setSpeed(controller, v, s);
} else if (action === "slower") { } else if (action === "slower") {
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);
v.playbackRate = Number(s.toFixed(2)); setSpeed(controller, v, s);
} else if (action === "reset") { } else if (action === "reset") {
resetSpeed(v, 1.0); log("Reset speed", 5);
resetSpeed(v, controller, 1.0);
} else if (action === "display") { } else if (action === "display") {
log("Showing controller", 5);
controller.classList.add("vsc-manual"); controller.classList.add("vsc-manual");
controller.classList.toggle("vsc-hidden"); controller.classList.toggle("vsc-hidden");
} else if (action === "blink") { } else if (action === "blink") {
log("Showing controller momentarily", 5);
// if vsc is hidden, show it briefly to give the use visual feedback that the action is excuted. // if vsc is hidden, show it briefly to give the use visual feedback that the action is excuted.
if ( if (
controller.classList.contains("vsc-hidden") || controller.classList.contains("vsc-hidden") ||
@@ -622,7 +727,7 @@ function runAction(action, document, value, e) {
} else if (action === "drag") { } else if (action === "drag") {
handleDrag(v, controller, e); handleDrag(v, controller, e);
} else if (action === "fast") { } else if (action === "fast") {
resetSpeed(v, value); resetSpeed(v, controller, value);
} else if (action === "pause") { } else if (action === "pause") {
pause(v); pause(v);
} else if (action === "muted") { } else if (action === "muted") {
@@ -634,31 +739,37 @@ function runAction(action, document, value, e) {
} }
} }
}); });
log("runAction End", 5);
} }
function pause(v) { function pause(v) {
if (v.paused) { if (v.paused) {
log("Resuming video", 5);
v.play(); v.play();
} else { } else {
log("Pausing video", 5);
v.pause(); v.pause();
} }
} }
function resetSpeed(v, target) { function resetSpeed(v, controller, target) {
if (v.playbackRate === target) { if (v.playbackRate === target) {
if (v.playbackRate === getKeyBindings("reset")) { if (v.playbackRate === getKeyBindings("reset")) {
// resetSpeed
if (target !== 1.0) { if (target !== 1.0) {
v.playbackRate = 1.0; log("Resetting playback speed to 1.0", 4);
setSpeed(controller, v, 1.0);
} else { } else {
v.playbackRate = getKeyBindings("fast"); // fastSpeed log('Toggling playback speed to "fast" speed', 4);
setSpeed(controller, v, getKeyBindings("fast"));
} }
} else { } else {
v.playbackRate = getKeyBindings("reset"); // resetSpeed log('Toggling playback speed to "reset" speed', 4);
setSpeed(controller, v, getKeyBindings("reset"));
} }
} else { } else {
setKeyBindings("reset", v.playbackRate); // resetSpeed log('Toggling playback speed to "reset" speed', 4);
v.playbackRate = target; setKeyBindings("reset", v.playbackRate);
setSpeed(controller, v, target);
} }
} }
@@ -667,10 +778,12 @@ function muted(v, value) {
} }
function setMark(v) { function setMark(v) {
log("Adding marker", 5);
v.vsc.mark = v.currentTime; v.vsc.mark = v.currentTime;
} }
function jumpToMark(v) { function jumpToMark(v) {
log("Recalling marker", 5);
if (v.vsc.mark && typeof v.vsc.mark === "number") { if (v.vsc.mark && typeof v.vsc.mark === "number") {
v.currentTime = v.vsc.mark; v.currentTime = v.vsc.mark;
} }
@@ -723,6 +836,7 @@ function handleDrag(video, controller, e) {
var timer; var timer;
var animation = false; var animation = false;
function showController(controller) { function showController(controller) {
log("Showing controller", 4);
controller.classList.add("vcs-show"); controller.classList.add("vcs-show");
if (animation) clearTimeout(timer); if (animation) clearTimeout(timer);
@@ -731,5 +845,6 @@ function showController(controller) {
timer = setTimeout(function() { timer = setTimeout(function() {
controller.classList.remove("vcs-show"); controller.classList.remove("vcs-show");
animation = false; animation = false;
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.0", "version": "0.6.1",
"manifest_version": 2, "manifest_version": 2,
"description": "Speed up, slow down, advance and rewind any HTML5 video with quick shortcuts.", "description": "Speed up, slow down, advance and rewind any HTML5 video with quick shortcuts.",
"homepage_url": "https://github.com/igrigorik/videospeed", "homepage_url": "https://github.com/igrigorik/videospeed",
@@ -29,7 +29,6 @@
"https://plus.google.com/hangouts/*", "https://plus.google.com/hangouts/*",
"https://hangouts.google.com/*", "https://hangouts.google.com/*",
"https://meet.google.com/*", "https://meet.google.com/*",
"https://teamtreehouse.com/*",
"http://www.hitbox.tv/*" "http://www.hitbox.tv/*"
], ],
"css": ["inject.css"], "css": ["inject.css"],

View File

@@ -160,11 +160,11 @@
>Blacklisted sites on which extension is disabled<br /> >Blacklisted sites on which extension is disabled<br />
(one per line)<br /> (one per line)<br />
<br /> <br />
<em <em>
><a href="https://www.regexpal.com/">Regex</a> is supported. Be sure <a href="https://www.regexpal.com/">Regex</a> is supported. Be sure
it is in "//g" format.<br /> it is in "//g" format.<br />
ie: /(.+)youtube\.com(\/*)$/gi</em ie: /(.+)youtube\.com(\/*)$/gi
> </em>
</label> </label>
<textarea id="blacklist" rows="10" cols="50"></textarea> <textarea id="blacklist" rows="10" cols="50"></textarea>
</div> </div>

View File

@@ -191,7 +191,7 @@ function validate() {
var regexp = new RegExp(match); var regexp = new RegExp(match);
} catch (err) { } catch (err) {
status.textContent = status.textContent =
"Error: Invalid Regex: " + match + ". Unable to save"; "Error: Invalid blacklist regex: " + match + ". Unable to save";
valid = false; valid = false;
return; return;
} }