mirror of
https://github.com/SoPat712/videospeed.git
synced 2026-02-09 19:15:09 -05:00
Merge upstream
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ local
|
|||||||
|
|
||||||
# IntelliJ IDEA
|
# IntelliJ IDEA
|
||||||
.idea/
|
.idea/
|
||||||
|
node_modules
|
||||||
|
|||||||
15
.pre-commit-config.yaml
Normal file
15
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v2.5.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
|
||||||
|
- repo: https://github.com/prettier/prettier
|
||||||
|
rev: 1.19.1 # Use the sha or tag you want to point at
|
||||||
|
hooks:
|
||||||
|
- id: prettier
|
||||||
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "none",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 80,
|
||||||
|
"semi": true,
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"proseWrap": "always"
|
||||||
|
}
|
||||||
65
README.md
65
README.md
@@ -2,38 +2,73 @@
|
|||||||
|
|
||||||
**TL;DR: faster playback translates to better engagement and retention.**
|
**TL;DR: faster playback translates to better engagement and retention.**
|
||||||
|
|
||||||
Average adult reads prose text at [250 to 300 words per minute](http://www.paperbecause.com/PIOP/files/f7/f7bb6bc5-2c4a-466f-9ae7-b483a2c0dca4.pdf) (wpm). By contrast, the average rate of speech for English speakers is ~150 wpm, with slide presentations often closer to 100 wpm. As a result, when given the choice, many viewers [speed up video playback to ~1.3\~1.5 its recorded rate](http://research.microsoft.com/en-us/um/redmond/groups/coet/compression/chi99/paper.pdf) to compensate for the difference.
|
Average adult reads prose text at
|
||||||
|
[250 to 300 words per minute](http://www.paperbecause.com/PIOP/files/f7/f7bb6bc5-2c4a-466f-9ae7-b483a2c0dca4.pdf)
|
||||||
Many viewers report that [accelerated viewing keeps their attention longer](http://www.enounce.com/docs/BYUPaper020319.pdf): faster delivery keeps the viewer more engaged with the content. In fact, with a little training many end up watching videos at 2x+ the recorded speed. Some studies report that after being exposed to accelerated playback, [listeners become uncomfortable](http://alumni.media.mit.edu/~barons/html/avios92.html#beasleyalteredspeech) if they are forced to return to normal rate of presentation.
|
(wpm). By contrast, the average rate of speech for English speakers is ~150 wpm,
|
||||||
|
with slide presentations often closer to 100 wpm. As a result, when given the
|
||||||
|
choice, many viewers
|
||||||
|
[speed up video playback to ~1.3\~1.5 its recorded rate](http://research.microsoft.com/en-us/um/redmond/groups/coet/compression/chi99/paper.pdf)
|
||||||
|
to compensate for the difference.
|
||||||
|
|
||||||
|
Many viewers report that
|
||||||
|
[accelerated viewing keeps their attention longer](http://www.enounce.com/docs/BYUPaper020319.pdf):
|
||||||
|
faster delivery keeps the viewer more engaged with the content. In fact, with a
|
||||||
|
little training many end up watching videos at 2x+ the recorded speed. Some
|
||||||
|
studies report that after being exposed to accelerated playback,
|
||||||
|
[listeners become uncomfortable](http://alumni.media.mit.edu/~barons/html/avios92.html#beasleyalteredspeech)
|
||||||
|
if they are forced to return to normal rate of presentation.
|
||||||
|
|
||||||
## Faster HTML5 Video
|
## Faster HTML5 Video
|
||||||
|
|
||||||
HTML5 video provides a native API to accelerate playback of any video. The problem is, many players either hide, or limit this functionality. For best results playback speed adjustments should be easy and frequent to match the pace and content being covered: we don't read at a fixed speed, and similarly, we need an easy way to accelerate the video, slow it down, and quickly rewind the last point to listen to it a few more times.
|
HTML5 video provides a native API to accelerate playback of any video. The
|
||||||
|
problem is, many players either hide, or limit this functionality. For best
|
||||||
|
results playback speed adjustments should be easy and frequent to match the pace
|
||||||
|
and content being covered: we don't read at a fixed speed, and similarly, we
|
||||||
|
need an easy way to accelerate the video, slow it down, and quickly rewind the
|
||||||
|
last point to listen to it a few more times.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### *Install [Chrome](https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk) or [Firefox](https://addons.mozilla.org/en-us/firefox/addon/videospeed/) Extension*
|
#### *Install [Chrome](https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk) or [Firefox](https://addons.mozilla.org/en-us/firefox/addon/videospeed/) Extension*
|
||||||
|
|
||||||
Once the extension is installed simply navigate to any page that offers HTML5 video ([example](http://www.youtube.com/watch?v=E9FxNzv1Tr8)), and you'll see a speed indicator in top left corner. Hover over the indicator to reveal the controls to accelerate, slowdown, and quickly rewind or advance the video. Or, even better, simply use your keyboard:
|
\*\* Once the extension is installed simply navigate to any page that offers
|
||||||
|
HTML5 video ([example](http://www.youtube.com/watch?v=E9FxNzv1Tr8)), and you'll
|
||||||
|
see a speed indicator in top left corner. Hover over the indicator to reveal the
|
||||||
|
controls to accelerate, slowdown, and quickly rewind or advance the video. Or,
|
||||||
|
even better, simply use your keyboard:
|
||||||
|
|
||||||
* **S** - decrease playback speed.
|
- **S** - decrease playback speed.
|
||||||
* **D** - increase playback speed.
|
- **D** - increase playback speed.
|
||||||
* **R** - reset playback speed to 1.0x.
|
- **R** - reset playback speed to 1.0x.
|
||||||
* **Z** - rewind video by 10 seconds.
|
- **Z** - rewind video by 10 seconds.
|
||||||
* **X** - advance video by 10 seconds.
|
- **X** - advance video by 10 seconds.
|
||||||
* **G** - toggle between current and user configurable preferred speed.
|
- **G** - toggle between current and user configurable preferred speed.
|
||||||
* **V** - show/hide the controller.
|
- **V** - show/hide the controller.
|
||||||
|
|
||||||
You can customize and reassign the default shortcut keys in the extensions settings page, as well as add additional shortcut keys to match your preferences. For example, you can assign multiple different "preferred speed" shortcuts with different values, which will allow you to quickly toggle between your most commonly used speeds. To add a new shortcut, open extension settings and click "Add New".
|
You can customize and reassign the default shortcut keys in the extensions
|
||||||
|
settings page, as well as add additional shortcut keys to match your
|
||||||
|
preferences. For example, you can assign multiple different "preferred speed"
|
||||||
|
shortcuts with different values, which will allow you to quickly toggle between
|
||||||
|
your most commonly used speeds. To add a new shortcut, open extension settings
|
||||||
|
and click "Add New".
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Some sites may assign other functionality to one of the assigned shortcut keys — these collisions are inevitable, unfortunately. As a workaround, the extension listens both for lower and upper case values (i.e. you can use `Shift-<shortcut>`) if there is other functionality assigned to the lowercase key. This is not a perfect solution, as some sites may listen to both, but works most of the time.
|
Some sites may assign other functionality to one of the assigned shortcut keys —
|
||||||
|
these collisions are inevitable, unfortunately. As a workaround, the extension
|
||||||
|
listens both for lower and upper case values (i.e. you can use
|
||||||
|
`Shift-<shortcut>`) if there is other functionality assigned to the lowercase
|
||||||
|
key. This is not a perfect solution, as some sites may listen to both, but works
|
||||||
|
most of the time.
|
||||||
|
|
||||||
### FAQ
|
### FAQ
|
||||||
|
|
||||||
**The video controls are not showing up?** This extension is only compatible with HTML5 video. If you don't see the controls showing up, chances are you are viewing a Flash video. If you want to confirm, try right-clicking on the video and inspect the menu: if it mentions flash, then that's the issue. That said, most sites will fallback to HTML5 if they detect that Flash is not available. You can try manually disabling Flash from the browser.
|
**The video controls are not showing up?** This extension is only compatible
|
||||||
|
with HTML5 video. If you don't see the controls showing up, chances are you are
|
||||||
|
viewing a Flash video. If you want to confirm, try right-clicking on the video
|
||||||
|
and inspect the menu: if it mentions flash, then that's the issue. That said,
|
||||||
|
most sites will fallback to HTML5 if they detect that Flash is not available.
|
||||||
|
You can try manually disabling Flash from the browser.
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
|||||||
21
inject.css
21
inject.css
@@ -1,8 +1,12 @@
|
|||||||
.vsc-nosource { display: none !important; }
|
.vsc-nosource {
|
||||||
.vsc-hidden { display: none !important; }
|
display: none !important;
|
||||||
|
}
|
||||||
|
.vsc-hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
.vsc-manual {
|
.vsc-manual {
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
opacity: 1.0 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Origin specific overrides */
|
/* Origin specific overrides */
|
||||||
@@ -14,13 +18,13 @@
|
|||||||
|
|
||||||
.ytp-autohide .vsc-controller {
|
.ytp-autohide .vsc-controller {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transition: opacity .25s cubic-bezier(0.4,0,0.2,1);
|
transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ytp-autohide .vcs-show {
|
.ytp-autohide .vcs-show {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1.0;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* YouTube embedded player */
|
/* YouTube embedded player */
|
||||||
@@ -32,8 +36,8 @@
|
|||||||
|
|
||||||
/* Facebook player */
|
/* Facebook player */
|
||||||
#facebook .vsc-controller {
|
#facebook .vsc-controller {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 40px;
|
top: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Google Photos player */
|
/* Google Photos player */
|
||||||
@@ -76,7 +80,8 @@ div.video-wrapper + div.target {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Fix black overlay on Kickstarter */
|
/* Fix black overlay on Kickstarter */
|
||||||
div.video-player.has_played.vertically_center:before, div.legacy-video-player.has_played.vertically_center:before {
|
div.video-player.has_played.vertically_center:before,
|
||||||
|
div.legacy-video-player.has_played.vertically_center:before {
|
||||||
content: none !important;
|
content: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1299
inject.js
1299
inject.js
@@ -1,239 +1,280 @@
|
|||||||
var regStrip = /^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm;
|
var regStrip = /^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm;
|
||||||
|
|
||||||
var tc = {
|
var tc = {
|
||||||
settings: {
|
settings: {
|
||||||
lastSpeed: 1.0, // default 1x
|
lastSpeed: 1.0, // default 1x
|
||||||
enabled: true, // default enabled
|
enabled: true, // default enabled
|
||||||
speeds: {}, // empty object to hold speed for each source
|
speeds: {}, // empty object to hold speed for each source
|
||||||
|
|
||||||
displayKeyCode: 86, // default: V
|
displayKeyCode: 86, // default: V
|
||||||
rememberSpeed: false, // default: false
|
rememberSpeed: 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
|
||||||
keyBindings: [],
|
keyBindings: [],
|
||||||
blacklist: `
|
blacklist: `\
|
||||||
www.instagram.com
|
www.instagram.com
|
||||||
twitter.com
|
twitter.com
|
||||||
vine.co
|
vine.co
|
||||||
imgur.com
|
imgur.com
|
||||||
teams.microsoft.com
|
teams.microsoft.com
|
||||||
`.replace(regStrip,'')
|
`.replace(regStrip, ""),
|
||||||
}
|
defaultLogLevel: 4,
|
||||||
};
|
logLevel: 3
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
chrome.storage.sync.get(tc.settings, function (storage) {
|
/* Log levels (depends on caller specifying the correct level)
|
||||||
tc.settings.keyBindings = storage.keyBindings; // Array
|
1 - none
|
||||||
if (storage.keyBindings.length == 0) // if first initialization of 0.5.3
|
2 - error
|
||||||
{
|
3 - warning
|
||||||
// UPDATE
|
4 - info
|
||||||
tc.settings.keyBindings.push({
|
5 - debug
|
||||||
action: "slower",
|
6 - debug high verbosity + stack trace on each message
|
||||||
key: Number(storage.slowerKeyCode) || 83,
|
*/
|
||||||
value: Number(storage.speedStep) || 0.1,
|
function log(message, level) {
|
||||||
force: false,
|
verbosity = tc.settings.logLevel;
|
||||||
predefined: true
|
if (typeof level === "undefined") {
|
||||||
}); // default S
|
level = tc.settings.defaultLogLevel;
|
||||||
tc.settings.keyBindings.push({
|
}
|
||||||
action: "faster",
|
if (verbosity >= level) {
|
||||||
key: Number(storage.fasterKeyCode) || 68,
|
if (level === 2) {
|
||||||
value: Number(storage.speedStep) || 0.1,
|
console.log("ERROR:" + message);
|
||||||
force: false,
|
} else if (level === 3) {
|
||||||
predefined: true
|
console.log("WARNING:" + message);
|
||||||
}); // default: D
|
} else if (level === 4) {
|
||||||
tc.settings.keyBindings.push({
|
console.log("INFO:" + message);
|
||||||
action: "rewind",
|
} else if (level === 5) {
|
||||||
key: Number(storage.rewindKeyCode) || 90,
|
console.log("DEBUG:" + message);
|
||||||
value: Number(storage.rewindTime) || 10,
|
} else if (level === 6) {
|
||||||
force: false,
|
console.log("DEBUG (VERBOSE):" + message);
|
||||||
predefined: true
|
console.trace();
|
||||||
}); // default: Z
|
|
||||||
tc.settings.keyBindings.push({
|
|
||||||
action: "advance",
|
|
||||||
key: Number(storage.advanceKeyCode) || 88,
|
|
||||||
value: Number(storage.advanceTime) || 10,
|
|
||||||
force: false,
|
|
||||||
predefined: true
|
|
||||||
}); // default: X
|
|
||||||
tc.settings.keyBindings.push({
|
|
||||||
action: "reset",
|
|
||||||
key: Number(storage.resetKeyCode) || 82,
|
|
||||||
value: 1.0,
|
|
||||||
force: false,
|
|
||||||
predefined: true
|
|
||||||
}); // default: R
|
|
||||||
tc.settings.keyBindings.push({
|
|
||||||
action: "fast",
|
|
||||||
key: Number(storage.fastKeyCode) || 71,
|
|
||||||
value: Number(storage.fastSpeed) || 1.8,
|
|
||||||
force: false,
|
|
||||||
predefined: true
|
|
||||||
}); // default: G
|
|
||||||
tc.settings.version = "0.5.3";
|
|
||||||
|
|
||||||
chrome.storage.sync.set({
|
|
||||||
keyBindings: tc.settings.keyBindings,
|
|
||||||
version: tc.settings.version,
|
|
||||||
displayKeyCode: tc.settings.displayKeyCode,
|
|
||||||
rememberSpeed: tc.settings.rememberSpeed,
|
|
||||||
audioBoolean: tc.settings.audioBoolean,
|
|
||||||
startHidden: tc.settings.startHidden,
|
|
||||||
enabled: tc.settings.enabled,
|
|
||||||
controllerOpacity: tc.settings.controllerOpacity,
|
|
||||||
blacklist: tc.settings.blacklist.replace(regStrip, '')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
tc.settings.lastSpeed = Number(storage.lastSpeed);
|
|
||||||
tc.settings.displayKeyCode = Number(storage.displayKeyCode);
|
|
||||||
tc.settings.rememberSpeed = Boolean(storage.rememberSpeed);
|
|
||||||
tc.settings.audioBoolean = Boolean(storage.audioBoolean);
|
|
||||||
tc.settings.enabled = Boolean(storage.enabled);
|
|
||||||
tc.settings.startHidden = Boolean(storage.startHidden);
|
|
||||||
tc.settings.controllerOpacity = Number(storage.controllerOpacity);
|
|
||||||
tc.settings.blacklist = String(storage.blacklist);
|
|
||||||
|
|
||||||
// ensure that there is a "display" binding (for upgrades from versions that had it as a separate binding)
|
|
||||||
if (tc.settings.keyBindings.filter(x => x.action == "display").length == 0) {
|
|
||||||
tc.settings.keyBindings.push({
|
|
||||||
action: "display",
|
|
||||||
key: Number(storage.displayKeyCode) || 86,
|
|
||||||
value: 0,
|
|
||||||
force: false,
|
|
||||||
predefined: true
|
|
||||||
}); // default V
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeWhenReady(document);
|
|
||||||
});
|
|
||||||
|
|
||||||
var forEach = Array.prototype.forEach;
|
|
||||||
|
|
||||||
function getKeyBindings(action, what = "value") {
|
|
||||||
try {
|
|
||||||
return tc.settings.keyBindings.find(item => item.action === action)[what];
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setKeyBindings(action, value) {
|
chrome.storage.sync.get(tc.settings, function(storage) {
|
||||||
tc.settings.keyBindings.find(item => item.action === action)["value"] = value;
|
tc.settings.keyBindings = storage.keyBindings; // Array
|
||||||
|
if (storage.keyBindings.length == 0) {
|
||||||
|
// if first initialization of 0.5.3
|
||||||
|
// UPDATE
|
||||||
|
tc.settings.keyBindings.push({
|
||||||
|
action: "slower",
|
||||||
|
key: Number(storage.slowerKeyCode) || 83,
|
||||||
|
value: Number(storage.speedStep) || 0.1,
|
||||||
|
force: false,
|
||||||
|
predefined: true
|
||||||
|
}); // default S
|
||||||
|
tc.settings.keyBindings.push({
|
||||||
|
action: "faster",
|
||||||
|
key: Number(storage.fasterKeyCode) || 68,
|
||||||
|
value: Number(storage.speedStep) || 0.1,
|
||||||
|
force: false,
|
||||||
|
predefined: true
|
||||||
|
}); // default: D
|
||||||
|
tc.settings.keyBindings.push({
|
||||||
|
action: "rewind",
|
||||||
|
key: Number(storage.rewindKeyCode) || 90,
|
||||||
|
value: Number(storage.rewindTime) || 10,
|
||||||
|
force: false,
|
||||||
|
predefined: true
|
||||||
|
}); // default: Z
|
||||||
|
tc.settings.keyBindings.push({
|
||||||
|
action: "advance",
|
||||||
|
key: Number(storage.advanceKeyCode) || 88,
|
||||||
|
value: Number(storage.advanceTime) || 10,
|
||||||
|
force: false,
|
||||||
|
predefined: true
|
||||||
|
}); // default: X
|
||||||
|
tc.settings.keyBindings.push({
|
||||||
|
action: "reset",
|
||||||
|
key: Number(storage.resetKeyCode) || 82,
|
||||||
|
value: 1.0,
|
||||||
|
force: false,
|
||||||
|
predefined: true
|
||||||
|
}); // default: R
|
||||||
|
tc.settings.keyBindings.push({
|
||||||
|
action: "fast",
|
||||||
|
key: Number(storage.fastKeyCode) || 71,
|
||||||
|
value: Number(storage.fastSpeed) || 1.8,
|
||||||
|
force: false,
|
||||||
|
predefined: true
|
||||||
|
}); // default: G
|
||||||
|
tc.settings.version = "0.5.3";
|
||||||
|
|
||||||
|
chrome.storage.sync.set({
|
||||||
|
keyBindings: tc.settings.keyBindings,
|
||||||
|
version: tc.settings.version,
|
||||||
|
displayKeyCode: tc.settings.displayKeyCode,
|
||||||
|
rememberSpeed: tc.settings.rememberSpeed,
|
||||||
|
audioBoolean: tc.settings.audioBoolean,
|
||||||
|
startHidden: tc.settings.startHidden,
|
||||||
|
enabled: tc.settings.enabled,
|
||||||
|
controllerOpacity: tc.settings.controllerOpacity,
|
||||||
|
blacklist: tc.settings.blacklist.replace(regStrip, "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tc.settings.lastSpeed = Number(storage.lastSpeed);
|
||||||
|
tc.settings.displayKeyCode = Number(storage.displayKeyCode);
|
||||||
|
tc.settings.rememberSpeed = Boolean(storage.rememberSpeed);
|
||||||
|
tc.settings.audioBoolean = Boolean(storage.audioBoolean);
|
||||||
|
tc.settings.enabled = Boolean(storage.enabled);
|
||||||
|
tc.settings.startHidden = Boolean(storage.startHidden);
|
||||||
|
tc.settings.controllerOpacity = Number(storage.controllerOpacity);
|
||||||
|
tc.settings.blacklist = String(storage.blacklist);
|
||||||
|
|
||||||
|
// ensure that there is a "display" binding (for upgrades from versions that had it as a separate binding)
|
||||||
|
if (tc.settings.keyBindings.filter(x => x.action == "display").length == 0) {
|
||||||
|
tc.settings.keyBindings.push({
|
||||||
|
action: "display",
|
||||||
|
key: Number(storage.displayKeyCode) || 86,
|
||||||
|
value: 0,
|
||||||
|
force: false,
|
||||||
|
predefined: true
|
||||||
|
}); // default V
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineVideoController() {
|
initializeWhenReady(document);
|
||||||
tc.videoController = function(target, parent) {
|
});
|
||||||
if (target.dataset['vscid']) {
|
|
||||||
return target.vsc;
|
var forEach = Array.prototype.forEach;
|
||||||
|
|
||||||
|
function getKeyBindings(action, what = "value") {
|
||||||
|
try {
|
||||||
|
return tc.settings.keyBindings.find(item => item.action === action)[what];
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setKeyBindings(action, value) {
|
||||||
|
tc.settings.keyBindings.find(item => item.action === action)["value"] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function defineVideoController() {
|
||||||
|
tc.videoController = function(target, parent) {
|
||||||
|
if (target.dataset["vscid"]) {
|
||||||
|
return target.vsc;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
log(
|
||||||
|
"Overwriting stored speed to 1.0 due to rememberSpeed being disabled",
|
||||||
|
5
|
||||||
|
);
|
||||||
|
storedSpeed = 1.0;
|
||||||
}
|
}
|
||||||
|
setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed
|
||||||
|
} else {
|
||||||
|
log("Recalling stored speed due to rememberSpeed being enabled", 5);
|
||||||
|
storedSpeed = tc.settings.lastSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
this.video = target;
|
log("Explicitly setting playbackRate to: " + storedSpeed, 5);
|
||||||
this.parent = target.parentElement || parent;
|
target.playbackRate = storedSpeed;
|
||||||
this.document = target.ownerDocument;
|
|
||||||
this.id = Math.random().toString(36).substr(2, 9);
|
|
||||||
|
|
||||||
// settings.speeds[] ensures that same source used across video tags (e.g. fullscreen on YT) retains speed setting
|
this.div = this.initializeControls();
|
||||||
// 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) {
|
target.addEventListener(
|
||||||
if (!tc.settings.speeds[target.currentSrc]) {
|
"play",
|
||||||
tc.settings.speeds[target.currentSrc] = this.speed;
|
(this.handlePlay = function(event) {
|
||||||
}
|
storedSpeed = tc.settings.speeds[event.target.currentSrc];
|
||||||
setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed
|
|
||||||
} else {
|
|
||||||
tc.settings.speeds[target.currentSrc] = tc.settings.lastSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
target.playbackRate = tc.settings.speeds[target.currentSrc];
|
|
||||||
|
|
||||||
this.div=this.initializeControls();
|
|
||||||
|
|
||||||
target.addEventListener('play', this.handlePlay = function(event) {
|
|
||||||
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)",
|
||||||
|
5
|
||||||
|
);
|
||||||
|
storedSpeed = tc.settings.lastSpeed;
|
||||||
}
|
}
|
||||||
target.playbackRate = tc.settings.speeds[target.currentSrc];
|
// TODO: Check if explicitly setting the playback rate to 1.0 is
|
||||||
}.bind(this));
|
// 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))
|
||||||
|
);
|
||||||
|
|
||||||
target.addEventListener('ratechange', this.handleRatechange = function(event) {
|
var observer = new MutationObserver(mutations => {
|
||||||
// Ignore ratechange events on unitialized videos.
|
mutations.forEach(mutation => {
|
||||||
// 0 == No information is available about the media resource.
|
if (
|
||||||
if (event.target.readyState > 0) {
|
mutation.type === "attributes" &&
|
||||||
var speed = this.getSpeed();
|
(mutation.attributeName === "src" ||
|
||||||
this.speedIndicator.textContent = speed;
|
mutation.attributeName === "currentSrc")
|
||||||
tc.settings.speeds[this.video.currentSrc] = speed;
|
) {
|
||||||
tc.settings.lastSpeed = speed;
|
var controller = getController(this.id);
|
||||||
this.speed = speed;
|
if (!controller) {
|
||||||
chrome.storage.sync.set({'lastSpeed': speed}, function() {
|
return;
|
||||||
console.log('Speed setting saved: ' + speed);
|
|
||||||
});
|
|
||||||
// show the controller for 1000ms if it's hidden.
|
|
||||||
runAction('blink', document, null, null);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
var observer=new MutationObserver((mutations) => {
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
if (mutation.type === 'attributes' && (mutation.attributeName === 'src' || mutation.attributeName === 'currentSrc')){
|
|
||||||
var controller = document.querySelector(`div[data-vscid="${this.id}"]`);
|
|
||||||
if(!controller){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!mutation.target.src && !mutation.target.currentSrc) {
|
|
||||||
controller.classList.add('vsc-nosource');
|
|
||||||
} else {
|
|
||||||
controller.classList.remove('vsc-nosource');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
if (!mutation.target.src && !mutation.target.currentSrc) {
|
||||||
|
controller.classList.add("vsc-nosource");
|
||||||
|
} else {
|
||||||
|
controller.classList.remove("vsc-nosource");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
observer.observe(target, {
|
});
|
||||||
attributeFilter: ["src", "currentSrc"]
|
observer.observe(target, {
|
||||||
});
|
attributeFilter: ["src", "currentSrc"]
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
|
||||||
tc.videoController.prototype.getSpeed = function() {
|
tc.videoController.prototype.remove = function() {
|
||||||
return parseFloat(this.video.playbackRate).toFixed(2);
|
this.div.remove();
|
||||||
|
this.video.removeEventListener("play", this.handlePlay);
|
||||||
|
delete this.video.dataset["vscid"];
|
||||||
|
delete this.video.vsc;
|
||||||
|
};
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
tc.videoController.prototype.remove = function() {
|
if (tc.settings.startHidden) {
|
||||||
this.div.remove();
|
wrapper.classList.add("vsc-hidden");
|
||||||
this.video.removeEventListener('play',this.handlePlay);
|
|
||||||
this.video.removeEventListener('ratechange',this.handleRatechange);
|
|
||||||
delete this.video.dataset['vscid'];
|
|
||||||
delete this.video.vsc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tc.videoController.prototype.initializeControls = function() {
|
var shadow = wrapper.attachShadow({ mode: "open" });
|
||||||
var document = this.document;
|
var shadowTemplate = `
|
||||||
var speed = parseFloat(tc.settings.speeds[this.video.currentSrc]).toFixed(2),
|
|
||||||
top = Math.max(this.video.offsetTop, 0) + "px",
|
|
||||||
left = Math.max(this.video.offsetLeft, 0) + "px";
|
|
||||||
|
|
||||||
var wrapper = document.createElement('div');
|
|
||||||
wrapper.classList.add('vsc-controller');
|
|
||||||
wrapper.dataset['vscid'] = this.id;
|
|
||||||
|
|
||||||
if (!this.video.currentSrc) {
|
|
||||||
wrapper.classList.add('vsc-nosource');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tc.settings.startHidden) {
|
|
||||||
wrapper.classList.add('vsc-hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
var shadow = wrapper.attachShadow({ mode: 'open' });
|
|
||||||
var shadowTemplate = `
|
|
||||||
<style>
|
<style>
|
||||||
@import "${chrome.runtime.getURL('shadow.css')}";
|
@import "${chrome.runtime.getURL("shadow.css")}";
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="controller" style="top:${top}; left:${left}; opacity:${tc.settings.controllerOpacity}">
|
<div id="controller" style="top:${top}; left:${left}; opacity:${
|
||||||
|
tc.settings.controllerOpacity
|
||||||
|
}">
|
||||||
<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>
|
||||||
@@ -244,386 +285,570 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
shadow.innerHTML = shadowTemplate;
|
shadow.innerHTML = shadowTemplate;
|
||||||
shadow.querySelector('.draggable').addEventListener('mousedown', (e) => {
|
shadow.querySelector(".draggable").addEventListener("mousedown", e => {
|
||||||
runAction(e.target.dataset['action'], document, false, e);
|
runAction(e.target.dataset["action"], document, false, e);
|
||||||
});
|
});
|
||||||
|
|
||||||
forEach.call(shadow.querySelectorAll('button'), function(button) {
|
forEach.call(shadow.querySelectorAll("button"), function(button) {
|
||||||
button.onclick = (e) => {
|
button.onclick = e => {
|
||||||
runAction(e.target.dataset['action'], document, getKeyBindings(e.target.dataset['action']), e);
|
runAction(
|
||||||
}
|
e.target.dataset["action"],
|
||||||
});
|
document,
|
||||||
|
getKeyBindings(e.target.dataset["action"]),
|
||||||
|
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;
|
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":
|
||||||
case (/hbogo\./).test(location.hostname):
|
case /hbogo\./.test(location.hostname):
|
||||||
// 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 == "tv.apple.com":
|
||||||
|
// insert after parent for correct stacking context
|
||||||
|
this.parent
|
||||||
|
.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
|
||||||
// the first element of the target, which may not be the parent.
|
// the first element of the target, which may not be the parent.
|
||||||
this.parent.insertBefore(fragment, this.parent.firstChild);
|
this.parent.insertBefore(fragment, this.parent.firstChild);
|
||||||
}
|
|
||||||
return wrapper;
|
|
||||||
}
|
}
|
||||||
}
|
return wrapper;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function initializeWhenReady(document) {
|
function escapeStringRegExp(str) {
|
||||||
escapeStringRegExp.matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
|
matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
|
||||||
function escapeStringRegExp(str) {
|
return str.replace(matchOperatorsRe, "\\$&");
|
||||||
return str.replace(escapeStringRegExp.matchOperatorsRe, '\\$&');
|
}
|
||||||
|
|
||||||
|
function isBlacklisted() {
|
||||||
|
blacklisted = false;
|
||||||
|
tc.settings.blacklist.split("\n").forEach(match => {
|
||||||
|
match = match.replace(regStrip, "");
|
||||||
|
if (match.length == 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var blacklisted = false;
|
if (match.startsWith("/")) {
|
||||||
tc.settings.blacklist.split("\n").forEach(match => {
|
try {
|
||||||
match = match.replace(regStrip,'')
|
var regexp = new RegExp(match);
|
||||||
if (match.length == 0) {
|
} catch (err) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
var regexp = new RegExp(escapeStringRegExp(match));
|
||||||
|
}
|
||||||
|
|
||||||
if (match.startsWith('/')) {
|
if (regexp.test(location.href)) {
|
||||||
try {
|
blacklisted = true;
|
||||||
var regexp = new RegExp(match);
|
return;
|
||||||
} catch(err) {
|
}
|
||||||
|
});
|
||||||
|
return blacklisted;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = () => {
|
||||||
|
initializeNow(window.document);
|
||||||
|
};
|
||||||
|
if (document) {
|
||||||
|
if (document.readyState === "complete") {
|
||||||
|
initializeNow(document);
|
||||||
|
} else {
|
||||||
|
document.onreadystatechange = () => {
|
||||||
|
if (document.readyState === "complete") {
|
||||||
|
initializeNow(document);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log("End initializeWhenReady", 5);
|
||||||
|
}
|
||||||
|
function inIframe() {
|
||||||
|
try {
|
||||||
|
return window.self !== window.top;
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 getController(id) {
|
||||||
|
return getShadow(document.body).filter(x => {
|
||||||
|
return (
|
||||||
|
x.attributes["data-vscid"] &&
|
||||||
|
x.tagName == "DIV" &&
|
||||||
|
x.attributes["data-vscid"].value == `${id}`
|
||||||
|
);
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeNow(document) {
|
||||||
|
log("Begin initializeNow", 5);
|
||||||
|
if (!tc.settings.enabled) return;
|
||||||
|
// enforce init-once due to redundant callers
|
||||||
|
if (!document.body || document.body.classList.contains("vsc-initialized")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setupListener();
|
||||||
|
} catch {
|
||||||
|
// no operation
|
||||||
|
}
|
||||||
|
document.body.classList.add("vsc-initialized");
|
||||||
|
log("initializeNow: vsc-initialized added to document body", 5);
|
||||||
|
|
||||||
|
if (document === window.document) {
|
||||||
|
defineVideoController();
|
||||||
|
} else {
|
||||||
|
var link = document.createElement("link");
|
||||||
|
link.href = chrome.runtime.getURL("inject.css");
|
||||||
|
link.type = "text/css";
|
||||||
|
link.rel = "stylesheet";
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
var docs = Array(document);
|
||||||
|
try {
|
||||||
|
if (inIframe()) docs.push(window.top.document);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
docs.forEach(function(doc) {
|
||||||
|
doc.addEventListener(
|
||||||
|
"keydown",
|
||||||
|
function(event) {
|
||||||
|
var keyCode = event.keyCode;
|
||||||
|
log("Processing keydown event: " + keyCode, 6);
|
||||||
|
|
||||||
|
// Ignore if following modifier is active.
|
||||||
|
if (
|
||||||
|
!event.getModifierState ||
|
||||||
|
event.getModifierState("Alt") ||
|
||||||
|
event.getModifierState("Control") ||
|
||||||
|
event.getModifierState("Fn") ||
|
||||||
|
event.getModifierState("Meta") ||
|
||||||
|
event.getModifierState("Hyper") ||
|
||||||
|
event.getModifierState("OS")
|
||||||
|
) {
|
||||||
|
log("Keydown event ignored due to active modifier: " + keyCode, 5);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
var regexp = new RegExp(escapeStringRegExp(match));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (regexp.test(location.href)) {
|
// Ignore keydown event if typing in an input box
|
||||||
blacklisted = true;
|
if (
|
||||||
return;
|
event.target.nodeName === "INPUT" ||
|
||||||
}
|
event.target.nodeName === "TEXTAREA" ||
|
||||||
})
|
event.target.isContentEditable
|
||||||
|
) {
|
||||||
if (blacklisted)
|
return false;
|
||||||
return;
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
initializeNow(window.document)
|
|
||||||
};
|
|
||||||
if (document) {
|
|
||||||
if (document.readyState === "complete") {
|
|
||||||
initializeNow(document);
|
|
||||||
} else {
|
|
||||||
document.onreadystatechange = () => {
|
|
||||||
if (document.readyState === "complete") {
|
|
||||||
initializeNow(document);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function inIframe () {
|
|
||||||
try {
|
|
||||||
return window.self !== window.top;
|
|
||||||
} catch (e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function initializeNow(document) {
|
|
||||||
if (!tc.settings.enabled) return;
|
|
||||||
// enforce init-once due to redundant callers
|
|
||||||
if (!document.body || document.body.classList.contains('vsc-initialized')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
document.body.classList.add('vsc-initialized');
|
|
||||||
|
|
||||||
if (document === window.document) {
|
// Ignore keydown event if typing in a page without vsc
|
||||||
defineVideoController();
|
if (
|
||||||
} else {
|
!getShadow(document.body).filter(x => x.tagName == "vsc-controller")
|
||||||
var link = document.createElement('link');
|
) {
|
||||||
link.href = chrome.runtime.getURL('inject.css');
|
return false;
|
||||||
link.type = 'text/css';
|
}
|
||||||
link.rel = 'stylesheet';
|
|
||||||
document.head.appendChild(link);
|
|
||||||
}
|
|
||||||
var docs = Array(document)
|
|
||||||
try {
|
|
||||||
if (inIframe())
|
|
||||||
docs.push(window.top.document);
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
docs.forEach(function(doc) {
|
|
||||||
doc.addEventListener('keydown', function(event) {
|
|
||||||
var keyCode = event.keyCode;
|
|
||||||
|
|
||||||
// Ignore if following modifier is active.
|
|
||||||
if (!event.getModifierState
|
|
||||||
|| event.getModifierState("Alt")
|
|
||||||
|| event.getModifierState("Control")
|
|
||||||
|| event.getModifierState("Fn")
|
|
||||||
|| event.getModifierState("Meta")
|
|
||||||
|| event.getModifierState("Hyper")
|
|
||||||
|| event.getModifierState("OS")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore keydown event if typing in an input box
|
|
||||||
if (event.target.nodeName === 'INPUT'
|
|
||||||
|| event.target.nodeName === 'TEXTAREA'
|
|
||||||
|| event.target.isContentEditable) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore keydown event if typing in a page without vsc
|
|
||||||
if (!document.querySelector(".vsc-controller")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = tc.settings.keyBindings.find(item => item.key === keyCode);
|
var item = tc.settings.keyBindings.find(item => item.key === keyCode);
|
||||||
if (item) {
|
if (item) {
|
||||||
runAction(item.action, document, item.value);
|
runAction(item.action, document, item.value);
|
||||||
if (item.force === "true") {// disable websites key bindings
|
if (item.force === "true") {
|
||||||
|
// disable websites key bindings
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}, true);
|
},
|
||||||
});
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function checkForVideo(node, parent, added) {
|
||||||
function checkForVideo(node, parent, added) {
|
// Only proceed with supposed removal if node is missing from DOM
|
||||||
// Only proceed with supposed removal if node is missing from DOM
|
if (!added && document.body.contains(node)) {
|
||||||
if (!added && document.body.contains(node)) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (
|
||||||
if (node.nodeName === 'VIDEO' || (node.nodeName === 'AUDIO' && tc.settings.audioBoolean)) {
|
node.nodeName === "VIDEO" ||
|
||||||
if (added) {
|
(node.nodeName === "AUDIO" && tc.settings.audioBoolean)
|
||||||
node.vsc = new tc.videoController(node, parent);
|
) {
|
||||||
} else {
|
if (added) {
|
||||||
let id = node.dataset['vscid'];
|
node.vsc = new tc.videoController(node, parent);
|
||||||
if (id) {
|
|
||||||
node.vsc.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (node.children != undefined) {
|
|
||||||
for (var i = 0; i < node.children.length; i++) {
|
|
||||||
const child = node.children[i];
|
|
||||||
checkForVideo(child, child.parentNode || parent, added);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var observer = new MutationObserver(function(mutations) {
|
|
||||||
// Process the DOM nodes lazily
|
|
||||||
requestIdleCallback(_ => {
|
|
||||||
mutations.forEach(function(mutation) {
|
|
||||||
forEach.call(mutation.addedNodes, function(node) {
|
|
||||||
if (typeof node === "function")
|
|
||||||
return;
|
|
||||||
checkForVideo(node, node.parentNode || mutation.target, true);
|
|
||||||
});
|
|
||||||
forEach.call(mutation.removedNodes, function(node) {
|
|
||||||
if (typeof node === "function")
|
|
||||||
return;
|
|
||||||
checkForVideo(node, node.parentNode || mutation.target, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, {timeout: 1000});
|
|
||||||
});
|
|
||||||
observer.observe(document, { childList: true, subtree: true });
|
|
||||||
|
|
||||||
if (tc.settings.audioBoolean) {
|
|
||||||
var mediaTags = document.querySelectorAll('video,audio');
|
|
||||||
} else {
|
} else {
|
||||||
var mediaTags = document.querySelectorAll('video');
|
let id = node.dataset["vscid"];
|
||||||
|
if (id) {
|
||||||
|
node.vsc.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if (node.children != undefined) {
|
||||||
forEach.call(mediaTags, function(video) {
|
for (var i = 0; i < node.children.length; i++) {
|
||||||
video.vsc = new tc.videoController(video);
|
const child = node.children[i];
|
||||||
});
|
checkForVideo(child, child.parentNode || parent, added);
|
||||||
|
}
|
||||||
var frameTags = document.getElementsByTagName('iframe');
|
}
|
||||||
forEach.call(frameTags, function(frame) {
|
|
||||||
// Ignore frames we don't have permission to access (different origin).
|
|
||||||
try { var childDocument = frame.contentDocument } catch (e) { return }
|
|
||||||
initializeWhenReady(childDocument);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function runAction(action, document, value, e) {
|
var observer = new MutationObserver(function(mutations) {
|
||||||
if (tc.settings.audioBoolean) {
|
// Process the DOM nodes lazily
|
||||||
var mediaTags = document.querySelectorAll('video,audio');
|
requestIdleCallback(
|
||||||
} else {
|
_ => {
|
||||||
var mediaTags = document.querySelectorAll('video');
|
mutations.forEach(function(mutation) {
|
||||||
|
switch (mutation.type) {
|
||||||
|
case "childList":
|
||||||
|
forEach.call(mutation.addedNodes, function(node) {
|
||||||
|
if (typeof node === "function") return;
|
||||||
|
checkForVideo(node, node.parentNode || mutation.target, true);
|
||||||
|
});
|
||||||
|
forEach.call(mutation.removedNodes, 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) {
|
||||||
|
var oldController = flattenedNodes.filter(x =>
|
||||||
|
x.classList.contains("vsc-controller")
|
||||||
|
)[0];
|
||||||
|
if (oldController) {
|
||||||
|
oldController.remove();
|
||||||
|
if (node.vsc) {
|
||||||
|
delete node.dataset.vscid;
|
||||||
|
delete node.vsc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkForVideo(node, node.parentNode || mutation.target, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ timeout: 1000 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
observer.observe(document, {
|
||||||
|
attributeFilter: ["aria-hidden"],
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tc.settings.audioBoolean) {
|
||||||
|
var mediaTags = document.querySelectorAll("video,audio");
|
||||||
|
} else {
|
||||||
|
var mediaTags = document.querySelectorAll("video");
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach.call(mediaTags, function(video) {
|
||||||
|
video.vsc = new tc.videoController(video);
|
||||||
|
});
|
||||||
|
|
||||||
|
var frameTags = document.getElementsByTagName("iframe");
|
||||||
|
forEach.call(frameTags, function(frame) {
|
||||||
|
// Ignore frames we don't have permission to access (different origin).
|
||||||
|
try {
|
||||||
|
var childDocument = frame.contentDocument;
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
initializeWhenReady(childDocument);
|
||||||
|
});
|
||||||
|
log("End initializeNow", 5);
|
||||||
|
}
|
||||||
|
|
||||||
mediaTags.forEach = Array.prototype.forEach;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
// Get the controller that was used if called from a button press event e
|
function runAction(action, document, value, e) {
|
||||||
if (e) {
|
log("runAction Begin", 5);
|
||||||
var targetController = e.target.getRootNode().host;
|
if (tc.settings.audioBoolean) {
|
||||||
}
|
var mediaTags = getShadow(document.body).filter(x => {
|
||||||
|
return x.tagName == "AUDIO" || x.tagName == "VIDEO";
|
||||||
mediaTags.forEach(function(v) {
|
|
||||||
var id = v.dataset['vscid'];
|
|
||||||
var controller = document.querySelector(`div[data-vscid="${id}"]`);
|
|
||||||
|
|
||||||
// Don't change video speed if the video has a different controller
|
|
||||||
if (e && !(targetController == controller)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Controller may have been (force) removed by the site, guard to prevent crashes but run the command
|
|
||||||
if (controller) {
|
|
||||||
showController(controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!v.classList.contains('vsc-cancelled')) {
|
|
||||||
if (action === 'rewind') {
|
|
||||||
v.currentTime -= value;
|
|
||||||
} else if (action === 'advance') {
|
|
||||||
v.currentTime += value;
|
|
||||||
} else if (action === 'faster') {
|
|
||||||
// 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
|
|
||||||
var s = Math.min((v.playbackRate < 0.1 ? 0.0 : v.playbackRate) + value, 16);
|
|
||||||
v.playbackRate = Number(s.toFixed(2));
|
|
||||||
} else if (action === 'slower') {
|
|
||||||
// Video min rate is 0.0625:
|
|
||||||
// https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/media/html_media_element.cc?gsn=kMinRate&l=165
|
|
||||||
var s = Math.max(v.playbackRate - value, 0.07);
|
|
||||||
v.playbackRate = Number(s.toFixed(2));
|
|
||||||
} else if (action === 'reset') {
|
|
||||||
resetSpeed(v, 1.0);
|
|
||||||
} else if (action === 'display') {
|
|
||||||
controller.classList.add('vsc-manual');
|
|
||||||
controller.classList.toggle('vsc-hidden');
|
|
||||||
} else if (action === 'blink') {
|
|
||||||
// if vsc is hidden, show it briefly to give the use visual feedback that the action is excuted.
|
|
||||||
if(controller.classList.contains('vsc-hidden') || controller.blinkTimeOut !== undefined){
|
|
||||||
clearTimeout(controller.blinkTimeOut);
|
|
||||||
controller.classList.remove('vsc-hidden');
|
|
||||||
controller.blinkTimeOut = setTimeout(()=>{
|
|
||||||
controller.classList.add('vsc-hidden');
|
|
||||||
controller.blinkTimeOut = undefined;
|
|
||||||
}, value ? value : 1000);
|
|
||||||
}
|
|
||||||
} else if (action === 'drag') {
|
|
||||||
handleDrag(v, controller, e);
|
|
||||||
} else if (action === 'fast') {
|
|
||||||
resetSpeed(v, value);
|
|
||||||
} else if (action === 'pause') {
|
|
||||||
pause(v);
|
|
||||||
} else if (action === 'muted') {
|
|
||||||
muted(v, value);
|
|
||||||
} else if (action === 'mark') {
|
|
||||||
setMark(v);
|
|
||||||
} else if (action === 'jump') {
|
|
||||||
jumpToMark(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
var mediaTags = getShadow(document.body).filter(x => x.tagName == "VIDEO");
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause(v) {
|
mediaTags.forEach = Array.prototype.forEach;
|
||||||
if (v.paused) {
|
|
||||||
v.play();
|
// Get the controller that was used if called from a button press event e
|
||||||
} else {
|
if (e) {
|
||||||
v.pause();
|
var targetController = e.target.getRootNode().host;
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaTags.forEach(function(v) {
|
||||||
|
var id = v.dataset["vscid"];
|
||||||
|
var controller = getController(id);
|
||||||
|
// Don't change video speed if the video has a different controller
|
||||||
|
if (e && !(targetController == controller)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function resetSpeed(v, target) {
|
// Controller may have been (force) removed by the site, guard to prevent crashes but run the command
|
||||||
if (v.playbackRate === target) {
|
if (controller) {
|
||||||
if (v.playbackRate === getKeyBindings("reset")) { // resetSpeed
|
showController(controller);
|
||||||
if (target !== 1.0) {
|
}
|
||||||
v.playbackRate = 1.0;
|
|
||||||
} else {
|
if (!v.classList.contains("vsc-cancelled")) {
|
||||||
v.playbackRate = getKeyBindings("fast"); // fastSpeed
|
if (action === "rewind") {
|
||||||
|
log("Rewind", 5);
|
||||||
|
v.currentTime -= value;
|
||||||
|
} else if (action === "advance") {
|
||||||
|
log("Fast forward", 5);
|
||||||
|
v.currentTime += value;
|
||||||
|
} else if (action === "faster") {
|
||||||
|
log("Increase speed", 5);
|
||||||
|
// 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
|
||||||
|
var s = Math.min(
|
||||||
|
(v.playbackRate < 0.1 ? 0.0 : v.playbackRate) + value,
|
||||||
|
16
|
||||||
|
);
|
||||||
|
setSpeed(controller, 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);
|
||||||
|
} else if (action === "reset") {
|
||||||
|
log("Reset speed", 5);
|
||||||
|
resetSpeed(v, controller, 1.0);
|
||||||
|
} else if (action === "display") {
|
||||||
|
log("Showing controller", 5);
|
||||||
|
controller.classList.add("vsc-manual");
|
||||||
|
controller.classList.toggle("vsc-hidden");
|
||||||
|
} 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 (
|
||||||
|
controller.classList.contains("vsc-hidden") ||
|
||||||
|
controller.blinkTimeOut !== undefined
|
||||||
|
) {
|
||||||
|
clearTimeout(controller.blinkTimeOut);
|
||||||
|
controller.classList.remove("vsc-hidden");
|
||||||
|
controller.blinkTimeOut = setTimeout(
|
||||||
|
() => {
|
||||||
|
controller.classList.add("vsc-hidden");
|
||||||
|
controller.blinkTimeOut = undefined;
|
||||||
|
},
|
||||||
|
value ? value : 1000
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
} else if (action === "drag") {
|
||||||
|
handleDrag(v, controller, e);
|
||||||
|
} else if (action === "fast") {
|
||||||
|
resetSpeed(v, controller, value);
|
||||||
|
} else if (action === "pause") {
|
||||||
|
pause(v);
|
||||||
|
} else if (action === "muted") {
|
||||||
|
muted(v, value);
|
||||||
|
} else if (action === "mark") {
|
||||||
|
setMark(v);
|
||||||
|
} else if (action === "jump") {
|
||||||
|
jumpToMark(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log("runAction End", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pause(v) {
|
||||||
|
if (v.paused) {
|
||||||
|
log("Resuming video", 5);
|
||||||
|
v.play();
|
||||||
|
} else {
|
||||||
|
log("Pausing video", 5);
|
||||||
|
v.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetSpeed(v, controller, 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);
|
||||||
} else {
|
} else {
|
||||||
v.playbackRate = getKeyBindings("reset"); // resetSpeed
|
log('Toggling playback speed to "fast" speed', 4);
|
||||||
|
setSpeed(controller, v, getKeyBindings("fast"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setKeyBindings("reset", v.playbackRate); // resetSpeed
|
log('Toggling playback speed to "reset" speed', 4);
|
||||||
v.playbackRate = target;
|
setSpeed(controller, v, getKeyBindings("reset"));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log('Toggling playback speed to "reset" speed', 4);
|
||||||
|
setKeyBindings("reset", v.playbackRate);
|
||||||
|
setSpeed(controller, v, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function muted(v, value) {
|
||||||
|
v.muted = v.muted !== true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMark(v) {
|
||||||
|
log("Adding marker", 5);
|
||||||
|
v.vsc.mark = v.currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function jumpToMark(v) {
|
||||||
|
log("Recalling marker", 5);
|
||||||
|
if (v.vsc.mark && typeof v.vsc.mark === "number") {
|
||||||
|
v.currentTime = v.vsc.mark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrag(video, controller, e) {
|
||||||
|
const shadowController = controller.shadowRoot.querySelector("#controller");
|
||||||
|
|
||||||
|
// Find nearest parent of same size as video parent.
|
||||||
|
var parentElement = controller.parentElement;
|
||||||
|
while (
|
||||||
|
parentElement.parentNode &&
|
||||||
|
parentElement.parentNode.offsetHeight === parentElement.offsetHeight &&
|
||||||
|
parentElement.parentNode.offsetWidth === parentElement.offsetWidth
|
||||||
|
) {
|
||||||
|
parentElement = parentElement.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function muted(v, value) {
|
video.classList.add("vcs-dragging");
|
||||||
v.muted = v.muted !== true;
|
shadowController.classList.add("dragging");
|
||||||
}
|
|
||||||
|
|
||||||
function setMark(v) {
|
const initialMouseXY = [e.clientX, e.clientY];
|
||||||
v.vsc.mark = v.currentTime;
|
const initialControllerXY = [
|
||||||
}
|
parseInt(shadowController.style.left),
|
||||||
|
parseInt(shadowController.style.top)
|
||||||
|
];
|
||||||
|
|
||||||
function jumpToMark(v) {
|
const startDragging = e => {
|
||||||
if (v.vsc.mark && typeof v.vsc.mark === "number") {
|
let style = shadowController.style;
|
||||||
v.currentTime = v.vsc.mark;
|
let dx = e.clientX - initialMouseXY[0];
|
||||||
}
|
let dy = e.clientY - initialMouseXY[1];
|
||||||
}
|
style.left = initialControllerXY[0] + dx + "px";
|
||||||
|
style.top = initialControllerXY[1] + dy + "px";
|
||||||
|
};
|
||||||
|
|
||||||
function handleDrag(video, controller, e) {
|
const stopDragging = () => {
|
||||||
const shadowController = controller.shadowRoot.querySelector('#controller');
|
parentElement.removeEventListener("mousemove", startDragging);
|
||||||
|
parentElement.removeEventListener("mouseup", stopDragging);
|
||||||
|
parentElement.removeEventListener("mouseleave", stopDragging);
|
||||||
|
|
||||||
// Find nearest parent of same size as video parent.
|
shadowController.classList.remove("dragging");
|
||||||
var parentElement = controller.parentElement;
|
video.classList.remove("vcs-dragging");
|
||||||
while (parentElement.parentNode &&
|
};
|
||||||
parentElement.parentNode.offsetHeight === parentElement.offsetHeight &&
|
|
||||||
parentElement.parentNode.offsetWidth === parentElement.offsetWidth) {
|
|
||||||
parentElement = parentElement.parentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
video.classList.add('vcs-dragging');
|
parentElement.addEventListener("mouseup", stopDragging);
|
||||||
shadowController.classList.add('dragging');
|
parentElement.addEventListener("mouseleave", stopDragging);
|
||||||
|
parentElement.addEventListener("mousemove", startDragging);
|
||||||
|
}
|
||||||
|
|
||||||
const initialMouseXY = [e.clientX, e.clientY];
|
var timer;
|
||||||
const initialControllerXY = [
|
var animation = false;
|
||||||
parseInt(shadowController.style.left),
|
function showController(controller) {
|
||||||
parseInt(shadowController.style.top)
|
log("Showing controller", 4);
|
||||||
];
|
controller.classList.add("vcs-show");
|
||||||
|
|
||||||
const startDragging = (e) => {
|
if (animation) clearTimeout(timer);
|
||||||
let style = shadowController.style;
|
|
||||||
let dx = e.clientX - initialMouseXY[0];
|
|
||||||
let dy = e.clientY -initialMouseXY[1];
|
|
||||||
style.left = (initialControllerXY[0] + dx) + 'px';
|
|
||||||
style.top = (initialControllerXY[1] + dy) + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopDragging = () => {
|
animation = true;
|
||||||
parentElement.removeEventListener('mousemove', startDragging);
|
timer = setTimeout(function() {
|
||||||
parentElement.removeEventListener('mouseup', stopDragging);
|
controller.classList.remove("vcs-show");
|
||||||
parentElement.removeEventListener('mouseleave', stopDragging);
|
animation = false;
|
||||||
|
log("Hiding controller", 5);
|
||||||
shadowController.classList.remove('dragging');
|
}, 2000);
|
||||||
video.classList.remove('vcs-dragging');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
parentElement.addEventListener('mouseup',stopDragging);
|
|
||||||
parentElement.addEventListener('mouseleave',stopDragging);
|
|
||||||
parentElement.addEventListener('mousemove', startDragging);
|
|
||||||
}
|
|
||||||
|
|
||||||
var timer;
|
|
||||||
var animation = false;
|
|
||||||
function showController(controller) {
|
|
||||||
controller.classList.add('vcs-show');
|
|
||||||
|
|
||||||
if (animation)
|
|
||||||
clearTimeout(timer);
|
|
||||||
|
|
||||||
animation = true;
|
|
||||||
timer = setTimeout(function() {
|
|
||||||
controller.classList.remove('vcs-show');
|
|
||||||
animation = false;
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "Video Speed Controller",
|
"name": "Video Speed Controller",
|
||||||
"short_name": "videospeed",
|
"short_name": "videospeed",
|
||||||
"version": "0.5.9.1",
|
"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 HTML5 audio/video with shortcuts",
|
||||||
"homepage_url": "https://github.com/codebicycle/videospeed",
|
"homepage_url": "https://github.com/codebicycle/videospeed",
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"48": "icons/icon48.png",
|
"48": "icons/icon48.png",
|
||||||
"128": "icons/icon128.png"
|
"128": "icons/icon128.png"
|
||||||
},
|
},
|
||||||
"permissions": [ "activeTab", "storage" ],
|
"permissions": ["activeTab", "storage"],
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "options.html",
|
"page": "options.html",
|
||||||
"open_in_tab": true
|
"open_in_tab": true
|
||||||
@@ -28,22 +28,19 @@
|
|||||||
},
|
},
|
||||||
"default_popup": "popup.html"
|
"default_popup": "popup.html"
|
||||||
},
|
},
|
||||||
"content_scripts": [{
|
"content_scripts": [
|
||||||
|
{
|
||||||
"all_frames": true,
|
"all_frames": true,
|
||||||
"matches": [ "http://*/*", "https://*/*", "file:///*" ],
|
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
||||||
"match_about_blank": true,
|
"match_about_blank": true,
|
||||||
"exclude_matches": [
|
"exclude_matches": [
|
||||||
"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/*"
|
|
||||||
],
|
],
|
||||||
"css": [ "inject.css" ],
|
"css": ["inject.css"],
|
||||||
"js": [ "inject.js" ]
|
"js": ["inject.js"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": ["inject.css", "shadow.css"]
|
||||||
"inject.css", "shadow.css"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
123
options.css
123
options.css
@@ -1,109 +1,112 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
padding-top: 53px;
|
padding-top: 53px;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: rgb(48, 57, 66);
|
color: rgb(48, 57, 66);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3 {
|
h1,
|
||||||
font-weight: normal;
|
h2,
|
||||||
line-height: 1;
|
h3 {
|
||||||
user-select: none;
|
font-weight: normal;
|
||||||
cursor: default;
|
line-height: 1;
|
||||||
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
margin: 21px 0 13px;
|
margin: 21px 0 13px;
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
margin-bottom: 0.8em;
|
margin-bottom: 0.8em;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
margin: 0.65em 0;
|
margin: 0.65em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 15px;
|
left: 15px;
|
||||||
right: 0;
|
right: 0;
|
||||||
border-bottom: 1px solid #EEE;
|
border-bottom: 1px solid #eee;
|
||||||
background: linear-gradient(white, white 40%, rgba(255, 255, 255, 0.92));
|
background: linear-gradient(white, white 40%, rgba(255, 255, 255, 0.92));
|
||||||
}
|
}
|
||||||
header, section {
|
header,
|
||||||
min-width: 600px;
|
section {
|
||||||
max-width: 738px;
|
min-width: 600px;
|
||||||
|
max-width: 738px;
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
section h3 {
|
section h3 {
|
||||||
margin-left: -18px;
|
margin-left: -18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
margin: 0 1px 0 0;
|
margin: 0 1px 0 0;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
min-width: 4em;
|
min-width: 4em;
|
||||||
min-height: 2em;
|
min-height: 2em;
|
||||||
|
|
||||||
background-image: linear-gradient(#EDEDED, #EDEDED 38%, #DEDEDE);
|
background-image: linear-gradient(#ededed, #ededed 38%, #dedede);
|
||||||
border: 1px solid rgba(0,0,0,0.25);
|
border: 1px solid rgba(0, 0, 0, 0.25);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 1px 0 rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.75);
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
|
||||||
color: #444;
|
inset 0 1px 2px rgba(255, 255, 255, 0.75);
|
||||||
text-shadow: 0 1px 0 rgb(240,240,240);
|
color: #444;
|
||||||
font: inherit;
|
text-shadow: 0 1px 0 rgb(240, 240, 240);
|
||||||
|
font: inherit;
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
width: 75px;
|
width: 75px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
margin: 5px 0px;
|
margin: 5px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 170px;
|
width: 170px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
#status {
|
#status {
|
||||||
color: #9D9D9D;
|
color: #9d9d9d;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 50px;
|
margin-left: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#faq {
|
#faq {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
width: 170px;
|
width: 170px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customForce {
|
.customForce {
|
||||||
display: none;
|
display: none;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.customKey {
|
.customKey {
|
||||||
color: transparent;
|
color: transparent;
|
||||||
text-shadow: 0 0 0 #000000;
|
text-shadow: 0 0 0 #000000;
|
||||||
}
|
}
|
||||||
|
|||||||
249
options.html
249
options.html
@@ -1,119 +1,170 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Video Speed Controller: Options</title>
|
<title>Video Speed Controller: Options</title>
|
||||||
<link rel="stylesheet" href="options.css" />
|
<link rel="stylesheet" href="options.css" />
|
||||||
<script src="options.js"></script>
|
<script src="options.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>Video Speed Controller</h1>
|
<h1>Video Speed Controller</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section id="customs">
|
<section id="customs">
|
||||||
<h3>Shortcuts</h3>
|
<h3>Shortcuts</h3>
|
||||||
<div class="row customs" id="display">
|
<div class="row customs" id="display">
|
||||||
<select class="customDo">
|
<select class="customDo">
|
||||||
<option value="display">Show/hide controller</option>
|
<option value="display">Show/hide controller</option>
|
||||||
</select>
|
</select>
|
||||||
<input class="customKey" type="text" value="" placeholder="press a key">
|
<input
|
||||||
<input class="customValue" type="text" placeholder="value (0.10)">
|
class="customKey"
|
||||||
<select class="customForce">
|
type="text"
|
||||||
<option value="false">Do not disable website key bindings</option>
|
value=""
|
||||||
<option value="true">Disable websites key bindings</option>
|
placeholder="press a key"
|
||||||
</select></div>
|
/>
|
||||||
<div class="row customs" id="slower">
|
<input class="customValue" type="text" placeholder="value (0.10)" />
|
||||||
<select class="customDo">
|
<select class="customForce">
|
||||||
<option value="slower">Decrease speed</option>
|
<option value="false">Do not disable website key bindings</option>
|
||||||
</select>
|
<option value="true">Disable website key bindings</option>
|
||||||
<input class="customKey" type="text" value="" placeholder="press a key">
|
</select>
|
||||||
<input class="customValue" type="text" placeholder="value (0.10)">
|
</div>
|
||||||
<select class="customForce">
|
<div class="row customs" id="slower">
|
||||||
<option value="false">Do not disable website key bindings</option>
|
<select class="customDo">
|
||||||
<option value="true">Disable websites key bindings</option>
|
<option value="slower">Decrease speed</option>
|
||||||
</select></div>
|
</select>
|
||||||
<div class="row customs" id="faster">
|
<input
|
||||||
<select class="customDo">
|
class="customKey"
|
||||||
<option value="faster">Increase speed</option>
|
type="text"
|
||||||
</select>
|
value=""
|
||||||
<input class="customKey" type="text" value="" placeholder="press a key">
|
placeholder="press a key"
|
||||||
<input class="customValue" type="text" placeholder="value (0.10)">
|
/>
|
||||||
<select class="customForce">
|
<input class="customValue" type="text" placeholder="value (0.10)" />
|
||||||
<option value="false">Do not disable website key bindings</option>
|
<select class="customForce">
|
||||||
<option value="true">Disable websites key bindings</option>
|
<option value="false">Do not disable website key bindings</option>
|
||||||
</select></div>
|
<option value="true">Disable website key bindings</option>
|
||||||
<div class="row customs" id="rewind">
|
</select>
|
||||||
<select class="customDo">
|
</div>
|
||||||
<option value="rewind">Rewind</option>
|
<div class="row customs" id="faster">
|
||||||
</select>
|
<select class="customDo">
|
||||||
<input class="customKey" type="text" value="" placeholder="press a key">
|
<option value="faster">Increase speed</option>
|
||||||
<input class="customValue" type="text" placeholder="value (10)">
|
</select>
|
||||||
<select class="customForce">
|
<input
|
||||||
<option value="false">Do not disable website key bindings</option>
|
class="customKey"
|
||||||
<option value="true">Disable websites key bindings</option>
|
type="text"
|
||||||
</select></div>
|
value=""
|
||||||
<div class="row customs" id="advance">
|
placeholder="press a key"
|
||||||
<select class="customDo">
|
/>
|
||||||
<option value="advance">Advance</option>
|
<input class="customValue" type="text" placeholder="value (0.10)" />
|
||||||
</select>
|
<select class="customForce">
|
||||||
<input class="customKey" type="text" value="" placeholder="press a key">
|
<option value="false">Do not disable website key bindings</option>
|
||||||
<input class="customValue" type="text" placeholder="value (10)">
|
<option value="true">Disable website key bindings</option>
|
||||||
<select class="customForce">
|
</select>
|
||||||
<option value="false">Do not disable website key bindings</option>
|
</div>
|
||||||
<option value="true">Disable websites key bindings</option>
|
<div class="row customs" id="rewind">
|
||||||
</select></div>
|
<select class="customDo">
|
||||||
<div class="row customs" id="reset">
|
<option value="rewind">Rewind</option>
|
||||||
<select class="customDo">
|
</select>
|
||||||
<option value="reset">Reset speed</option>
|
<input
|
||||||
</select>
|
class="customKey"
|
||||||
<input class="customKey" type="text" value="" placeholder="press a key">
|
type="text"
|
||||||
<input class="customValue" type="text" placeholder="value (1.00)" disabled>
|
value=""
|
||||||
<select class="customForce">
|
placeholder="press a key"
|
||||||
<option value="false">Do not disable website key bindings</option>
|
/>
|
||||||
<option value="true">Disable websites key bindings</option>
|
<input class="customValue" type="text" placeholder="value (10)" />
|
||||||
</select></div>
|
<select class="customForce">
|
||||||
<div class="row customs" id="fast">
|
<option value="false">Do not disable website key bindings</option>
|
||||||
<select class="customDo">
|
<option value="true">Disable website key bindings</option>
|
||||||
<option value="fast">Preferred speed</option>
|
</select>
|
||||||
</select>
|
</div>
|
||||||
<input class="customKey" type="text" value="" placeholder="press a key">
|
<div class="row customs" id="advance">
|
||||||
<input class="customValue" type="text" placeholder="value (1.80)">
|
<select class="customDo">
|
||||||
<select class="customForce">
|
<option value="advance">Advance</option>
|
||||||
<option value="false">Do not disable website key bindings</option>
|
</select>
|
||||||
<option value="true">Disable websites key bindings</option>
|
<input
|
||||||
</select></div>
|
class="customKey"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
placeholder="press a key"
|
||||||
|
/>
|
||||||
|
<input class="customValue" type="text" placeholder="value (10)" />
|
||||||
|
<select class="customForce">
|
||||||
|
<option value="false">Do not disable website key bindings</option>
|
||||||
|
<option value="true">Disable website key bindings</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="row customs" id="reset">
|
||||||
|
<select class="customDo">
|
||||||
|
<option value="reset">Reset speed</option>
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
class="customKey"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
placeholder="press a key"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="customValue"
|
||||||
|
type="text"
|
||||||
|
placeholder="value (1.00)"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<select class="customForce">
|
||||||
|
<option value="false">Do not disable website key bindings</option>
|
||||||
|
<option value="true">Disable website key bindings</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="row customs" id="fast">
|
||||||
|
<select class="customDo">
|
||||||
|
<option value="fast">Preferred speed</option>
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
class="customKey"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
placeholder="press a key"
|
||||||
|
/>
|
||||||
|
<input class="customValue" type="text" placeholder="value (1.80)" />
|
||||||
|
<select class="customForce">
|
||||||
|
<option value="false">Do not disable website key bindings</option>
|
||||||
|
<option value="true">Disable website key bindings</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button id="add">Add New</button>
|
<button id="add">Add New</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h3>Other</h3>
|
<h3>Other</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label for="enabled">Enabled</label>
|
<label for="enabled">Enable</label>
|
||||||
<input id="enabled" type="checkbox"/>
|
<input id="enabled" type="checkbox" />
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label for="startHidden">Hide controller by default</label>
|
<label for="startHidden">Hide controller by default</label>
|
||||||
<input id="startHidden" type="checkbox"/>
|
<input id="startHidden" type="checkbox" />
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<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">
|
<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" />
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label for="controllerOpacity">Controller opacity</label>
|
<label for="controllerOpacity">Controller opacity</label>
|
||||||
<input id="controllerOpacity" type="text" value="">
|
<input id="controllerOpacity" type="text" value="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label for="blacklist">Blacklisted sites on which extension is disabled<br/>
|
<label for="blacklist"
|
||||||
(one per line)<br/>
|
>Sites on which extension is disabled<br />
|
||||||
<br/>
|
(one per line)<br />
|
||||||
<em><a href="https://www.regexpal.com/">Regex</a> is supported. Be sure it is in "//g" format.<br/>
|
<br />
|
||||||
ie: /(.+)youtube\.com(\/*)$/gi</em>
|
<em>
|
||||||
|
<a href="https://www.regexpal.com/">Regex</a> is supported.<br />
|
||||||
|
Be sure it is in "//g" format.<br />
|
||||||
|
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>
|
||||||
@@ -121,16 +172,22 @@
|
|||||||
|
|
||||||
<button id="save">Save</button>
|
<button id="save">Save</button>
|
||||||
<button id="restore">Restore Defaults</button>
|
<button id="restore">Restore Defaults</button>
|
||||||
<button id="experimental">Show experimental features</button>
|
<button id="experimental">Show Experimental Features</button>
|
||||||
|
|
||||||
<div id="status"></div>
|
<div id="status"></div>
|
||||||
|
|
||||||
<div id="faq">
|
<div id="faq">
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<h4>The video controls are not showing up?</h4>
|
<h4>Extension controls not appearing?</h4>
|
||||||
<p>This extension is only compatible with HTML5 video. If you don't see the controls showing up, chances are you are viewing a Flash video. If you want to confirm, try right-clicking on the video and inspect the menu: if it mentions flash, then that's the issue. That said, <b>most sites will fallback to HTML5</b> if they detect that Flash is not available. You can try manually disabling Flash from the browser.</p>
|
<p>
|
||||||
|
This extension is only compatible with HTML5 audio and video. If you don't
|
||||||
|
see the controls showing up, chances are you are viewing a Flash content.
|
||||||
|
If you want to confirm, try right-clicking on the content and inspect the
|
||||||
|
menu: if it mentions flash, then that's the issue. That said, <b>most sites
|
||||||
|
will fallback to HTML5</b> if they detect that Flash is not available. You
|
||||||
|
can try manually disabling Flash from the browser.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
400
options.js
400
options.js
@@ -1,103 +1,105 @@
|
|||||||
var regStrip=/^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm;
|
var regStrip = /^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm;
|
||||||
|
|
||||||
var tcDefaults = {
|
var tcDefaults = {
|
||||||
speed: 1.0, // default:
|
speed: 1.0, // default:
|
||||||
displayKeyCode: 86, // default: V
|
displayKeyCode: 86, // default: V
|
||||||
rememberSpeed: false, // default: false
|
rememberSpeed: false, // default: false
|
||||||
audioBoolean: false, // default: false
|
audioBoolean: false, // default: false
|
||||||
startHidden: false, // default: false
|
startHidden: 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: [
|
||||||
{action: "display", key: 86, value: 0, force: false, predefined: true }, // V
|
{ action: "display", key: 86, value: 0, force: false, predefined: true }, // V
|
||||||
{action: "slower", key: 83, value: 0.1, force: false, predefined: true}, // S
|
{ action: "slower", key: 83, value: 0.1, force: false, predefined: true }, // S
|
||||||
{action: "faster", key: 68, value: 0.1, force: false, predefined: true}, // D
|
{ action: "faster", key: 68, value: 0.1, force: false, predefined: true }, // D
|
||||||
{action: "rewind", key: 90, value: 10, force: false, predefined: true}, // Z
|
{ action: "rewind", key: 90, value: 10, force: false, predefined: true }, // Z
|
||||||
{action: "advance", key: 88, value: 10, force: false, predefined: true}, // X
|
{ action: "advance", key: 88, value: 10, force: false, predefined: true }, // X
|
||||||
{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
|
||||||
vine.co
|
|
||||||
imgur.com
|
imgur.com
|
||||||
teams.microsoft.com
|
teams.microsoft.com
|
||||||
`.replace(regStrip, '')
|
`.replace(regStrip, "")
|
||||||
};
|
};
|
||||||
|
|
||||||
var keyBindings = [];
|
var keyBindings = [];
|
||||||
|
|
||||||
var keyCodeAliases = {
|
var keyCodeAliases = {
|
||||||
0: 'null',
|
0: "null",
|
||||||
null: 'null',
|
null: "null",
|
||||||
undefined: 'null',
|
undefined: "null",
|
||||||
32: 'Space',
|
32: "Space",
|
||||||
37: 'Left',
|
37: "Left",
|
||||||
38: 'Up',
|
38: "Up",
|
||||||
39: 'Right',
|
39: "Right",
|
||||||
40: 'Down',
|
40: "Down",
|
||||||
96: 'Num 0',
|
96: "Num 0",
|
||||||
97: 'Num 1',
|
97: "Num 1",
|
||||||
98: 'Num 2',
|
98: "Num 2",
|
||||||
99: 'Num 3',
|
99: "Num 3",
|
||||||
100: 'Num 4',
|
100: "Num 4",
|
||||||
101: 'Num 5',
|
101: "Num 5",
|
||||||
102: 'Num 6',
|
102: "Num 6",
|
||||||
103: 'Num 7',
|
103: "Num 7",
|
||||||
104: 'Num 8',
|
104: "Num 8",
|
||||||
105: 'Num 9',
|
105: "Num 9",
|
||||||
106: 'Num *',
|
106: "Num *",
|
||||||
107: 'Num +',
|
107: "Num +",
|
||||||
109: 'Num -',
|
109: "Num -",
|
||||||
110: 'Num .',
|
110: "Num .",
|
||||||
111: 'Num /',
|
111: "Num /",
|
||||||
112: 'F1',
|
112: "F1",
|
||||||
113: 'F2',
|
113: "F2",
|
||||||
114: 'F3',
|
114: "F3",
|
||||||
115: 'F4',
|
115: "F4",
|
||||||
116: 'F5',
|
116: "F5",
|
||||||
117: 'F6',
|
117: "F6",
|
||||||
118: 'F7',
|
118: "F7",
|
||||||
119: 'F8',
|
119: "F8",
|
||||||
120: 'F9',
|
120: "F9",
|
||||||
121: 'F10',
|
121: "F10",
|
||||||
122: 'F11',
|
122: "F11",
|
||||||
123: 'F12',
|
123: "F12",
|
||||||
186: ';',
|
186: ";",
|
||||||
188: '<',
|
188: "<",
|
||||||
189: '-',
|
189: "-",
|
||||||
187: '+',
|
187: "+",
|
||||||
190: '>',
|
190: ">",
|
||||||
191: '/',
|
191: "/",
|
||||||
192: '~',
|
192: "~",
|
||||||
219: '[',
|
219: "[",
|
||||||
220: '\\',
|
220: "\\",
|
||||||
221: ']',
|
221: "]",
|
||||||
222: '\'',
|
222: "'",
|
||||||
59: ';',
|
59: ";",
|
||||||
61: '+',
|
61: "+",
|
||||||
173: '-',
|
173: "-",
|
||||||
}
|
};
|
||||||
|
|
||||||
function recordKeyPress(e) {
|
function recordKeyPress(e) {
|
||||||
if (
|
if (
|
||||||
(e.keyCode >= 48 && e.keyCode <= 57) // Numbers 0-9
|
(e.keyCode >= 48 && e.keyCode <= 57) || // Numbers 0-9
|
||||||
|| (e.keyCode >= 65 && e.keyCode <= 90) // Letters A-Z
|
(e.keyCode >= 65 && e.keyCode <= 90) || // Letters A-Z
|
||||||
|| keyCodeAliases[e.keyCode] // Other character keys
|
keyCodeAliases[e.keyCode] // Other character keys
|
||||||
) {
|
) {
|
||||||
e.target.value = keyCodeAliases[e.keyCode] || String.fromCharCode(e.keyCode);
|
e.target.value =
|
||||||
|
keyCodeAliases[e.keyCode] || String.fromCharCode(e.keyCode);
|
||||||
e.target.keyCode = e.keyCode;
|
e.target.keyCode = e.keyCode;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
} else if (e.keyCode === 8) { // Clear input when backspace pressed
|
} else if (e.keyCode === 8) {
|
||||||
e.target.value = '';
|
// Clear input when backspace pressed
|
||||||
} else if (e.keyCode === 27) { // When esc clicked, clear input
|
e.target.value = "";
|
||||||
e.target.value = 'null';
|
} else if (e.keyCode === 27) {
|
||||||
|
// When esc clicked, clear input
|
||||||
|
e.target.value = "null";
|
||||||
e.target.keyCode = null;
|
e.target.keyCode = null;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
function inputFilterNumbersOnly(e) {
|
function inputFilterNumbersOnly(e) {
|
||||||
var char = String.fromCharCode(e.keyCode);
|
var char = String.fromCharCode(e.keyCode);
|
||||||
@@ -105,18 +107,20 @@ function inputFilterNumbersOnly(e) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
function inputFocus(e) {
|
function inputFocus(e) {
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
};
|
}
|
||||||
|
|
||||||
function inputBlur(e) {
|
function inputBlur(e) {
|
||||||
e.target.value = keyCodeAliases[e.target.keyCode] || String.fromCharCode(e.target.keyCode);
|
e.target.value =
|
||||||
};
|
keyCodeAliases[e.target.keyCode] || String.fromCharCode(e.target.keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
function updateShortcutInputText(inputId, keyCode) {
|
function updateShortcutInputText(inputId, keyCode) {
|
||||||
document.getElementById(inputId).value = keyCodeAliases[keyCode] || String.fromCharCode(keyCode);
|
document.getElementById(inputId).value =
|
||||||
|
keyCodeAliases[keyCode] || String.fromCharCode(keyCode);
|
||||||
document.getElementById(inputId).keyCode = keyCode;
|
document.getElementById(inputId).keyCode = keyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +130,7 @@ function updateCustomShortcutInputText(inputItem, keyCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List of custom actions for which customValue should be disabled
|
// List of custom actions for which customValue should be disabled
|
||||||
var customActionsNoValues=["pause","muted","mark","jump","display"];
|
var customActionsNoValues = ["pause", "muted", "mark", "jump", "display"];
|
||||||
|
|
||||||
function add_shortcut() {
|
function add_shortcut() {
|
||||||
var html = `<select class="customDo">
|
var html = `<select class="customDo">
|
||||||
@@ -141,19 +145,22 @@ function add_shortcut() {
|
|||||||
<option value="mark">Set marker</option>
|
<option value="mark">Set marker</option>
|
||||||
<option value="jump">Jump to marker</option>
|
<option value="jump">Jump to marker</option>
|
||||||
<option value="display">Show/hide controller</option>
|
<option value="display">Show/hide controller</option>
|
||||||
</select>
|
</select>
|
||||||
<input class="customKey" type="text" placeholder="press a key"/>
|
<input class="customKey" type="text" placeholder="press a key"/>
|
||||||
<input class="customValue" type="text" placeholder="value (0.10)"/>
|
<input class="customValue" type="text" placeholder="value (0.10)"/>
|
||||||
<select class="customForce">
|
<select class="customForce">
|
||||||
<option value="false">Do not disable website key bindings</option>
|
<option value="false">Do not disable website key bindings</option>
|
||||||
<option value="true">Disable websites key bindings</option>
|
<option value="true">Disable website key bindings</option>
|
||||||
</select>
|
</select>
|
||||||
<button class="removeParent">X</button>`;
|
<button class="removeParent">X</button>`;
|
||||||
var div = document.createElement('div');
|
var div = document.createElement("div");
|
||||||
div.setAttribute('class', 'row customs');
|
div.setAttribute("class", "row customs");
|
||||||
div.innerHTML = html;
|
div.innerHTML = html;
|
||||||
var customs_element = document.getElementById("customs");
|
var customs_element = document.getElementById("customs");
|
||||||
customs_element.insertBefore(div, customs_element.children[customs_element.childElementCount - 1]);
|
customs_element.insertBefore(
|
||||||
|
div,
|
||||||
|
customs_element.children[customs_element.childElementCount - 1]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createKeyBindings(item) {
|
function createKeyBindings(item) {
|
||||||
@@ -161,28 +168,37 @@ function createKeyBindings(item) {
|
|||||||
const key = item.querySelector(".customKey").keyCode;
|
const key = item.querySelector(".customKey").keyCode;
|
||||||
const value = Number(item.querySelector(".customValue").value);
|
const value = Number(item.querySelector(".customValue").value);
|
||||||
const force = item.querySelector(".customForce").value;
|
const force = item.querySelector(".customForce").value;
|
||||||
const predefined = !!item.id;//item.id ? true : false;
|
const predefined = !!item.id; //item.id ? true : false;
|
||||||
|
|
||||||
keyBindings.push({action: action, key: key, value: value, force: force, predefined: predefined});
|
keyBindings.push({
|
||||||
|
action: action,
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
force: force,
|
||||||
|
predefined: predefined
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates settings before saving
|
// Validates settings before saving
|
||||||
function validate() {
|
function validate() {
|
||||||
var valid = true;
|
var valid = true;
|
||||||
var status = document.getElementById('status');
|
var status = document.getElementById("status");
|
||||||
document.getElementById('blacklist').value.split("\n").forEach(match => {
|
document
|
||||||
match = match.replace(regStrip,'')
|
.getElementById("blacklist")
|
||||||
if (match.startsWith('/')) {
|
.value.split("\n")
|
||||||
try {
|
.forEach(match => {
|
||||||
var regexp = new RegExp(match);
|
match = match.replace(regStrip, "");
|
||||||
} catch(err) {
|
if (match.startsWith("/")) {
|
||||||
status.textContent = 'Error: Invalid Regex: ' + match
|
try {
|
||||||
+ '. Unable to save';
|
var regexp = new RegExp(match);
|
||||||
valid = false;
|
} catch (err) {
|
||||||
return;
|
status.textContent =
|
||||||
|
"Error: Invalid blacklist regex: " + match + ". Unable to save";
|
||||||
|
valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
})
|
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,47 +208,70 @@ function save_options() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
keyBindings = [];
|
keyBindings = [];
|
||||||
Array.from(document.querySelectorAll(".customs")).forEach(item => createKeyBindings(item)); // Remove added shortcuts
|
Array.from(document.querySelectorAll(".customs")).forEach(item =>
|
||||||
|
createKeyBindings(item)
|
||||||
|
); // Remove added shortcuts
|
||||||
|
|
||||||
var rememberSpeed = document.getElementById('rememberSpeed').checked;
|
var rememberSpeed = document.getElementById("rememberSpeed").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;
|
||||||
var controllerOpacity = document.getElementById('controllerOpacity').value;
|
var controllerOpacity = document.getElementById("controllerOpacity").value;
|
||||||
var blacklist = document.getElementById('blacklist').value;
|
var blacklist = document.getElementById("blacklist").value;
|
||||||
|
|
||||||
chrome.storage.sync.remove(["resetSpeed", "speedStep", "fastSpeed", "rewindTime", "advanceTime", "resetKeyCode", "slowerKeyCode", "fasterKeyCode", "rewindKeyCode", "advanceKeyCode", "fastKeyCode"]);
|
chrome.storage.sync.remove([
|
||||||
chrome.storage.sync.set({
|
"resetSpeed",
|
||||||
rememberSpeed: rememberSpeed,
|
"speedStep",
|
||||||
audioBoolean: audioBoolean,
|
"fastSpeed",
|
||||||
enabled: enabled,
|
"rewindTime",
|
||||||
startHidden: startHidden,
|
"advanceTime",
|
||||||
controllerOpacity: controllerOpacity,
|
"resetKeyCode",
|
||||||
keyBindings: keyBindings,
|
"slowerKeyCode",
|
||||||
blacklist: blacklist.replace(regStrip,'')
|
"fasterKeyCode",
|
||||||
}, function() {
|
"rewindKeyCode",
|
||||||
// Update status to let user know options were saved.
|
"advanceKeyCode",
|
||||||
var status = document.getElementById('status');
|
"fastKeyCode"
|
||||||
status.textContent = 'Options saved';
|
]);
|
||||||
setTimeout(function() {
|
chrome.storage.sync.set(
|
||||||
status.textContent = '';
|
{
|
||||||
}, 1000);
|
rememberSpeed: rememberSpeed,
|
||||||
});
|
audioBoolean: audioBoolean,
|
||||||
|
enabled: enabled,
|
||||||
|
startHidden: startHidden,
|
||||||
|
controllerOpacity: controllerOpacity,
|
||||||
|
keyBindings: keyBindings,
|
||||||
|
blacklist: blacklist.replace(regStrip, "")
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
// Update status to let user know options were saved.
|
||||||
|
var status = document.getElementById("status");
|
||||||
|
status.textContent = "Options saved";
|
||||||
|
setTimeout(function() {
|
||||||
|
status.textContent = "";
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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('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;
|
||||||
document.getElementById('controllerOpacity').value = storage.controllerOpacity;
|
document.getElementById("controllerOpacity").value =
|
||||||
document.getElementById('blacklist').value = storage.blacklist;
|
storage.controllerOpacity;
|
||||||
|
document.getElementById("blacklist").value = storage.blacklist;
|
||||||
|
|
||||||
// ensure that there is a "display" binding for upgrades from versions that had it as a separate binding
|
// 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, force: false, predefined: true });
|
storage.keyBindings.push({
|
||||||
|
action: "display",
|
||||||
|
value: 0,
|
||||||
|
force: false,
|
||||||
|
predefined: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i in storage.keyBindings) {
|
for (let i in storage.keyBindings) {
|
||||||
@@ -240,27 +279,36 @@ function restore_options() {
|
|||||||
if (item.predefined) {
|
if (item.predefined) {
|
||||||
//do predefined ones because their value needed for overlay
|
//do predefined ones because their value needed for overlay
|
||||||
// document.querySelector("#" + item["action"] + " .customDo").value = item["action"];
|
// document.querySelector("#" + item["action"] + " .customDo").value = item["action"];
|
||||||
if (item["action"] == "display" && typeof (item["key"]) === "undefined"){
|
if (item["action"] == "display" && typeof item["key"] === "undefined") {
|
||||||
item["key"] = storage.displayKeyCode || tcDefaults.displayKeyCode; // V
|
item["key"] = storage.displayKeyCode || tcDefaults.displayKeyCode; // V
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customActionsNoValues.includes(item["action"]))
|
if (customActionsNoValues.includes(item["action"]))
|
||||||
document.querySelector("#" + item["action"] + " .customValue").disabled = true;
|
document.querySelector(
|
||||||
|
"#" + item["action"] + " .customValue"
|
||||||
|
).disabled = true;
|
||||||
|
|
||||||
updateCustomShortcutInputText(document.querySelector("#" + item["action"] + " .customKey"), item["key"]);
|
updateCustomShortcutInputText(
|
||||||
document.querySelector("#" + item["action"] + " .customValue").value = item["value"];
|
document.querySelector("#" + item["action"] + " .customKey"),
|
||||||
document.querySelector("#" + item["action"] + " .customForce").value = item["force"];
|
item["key"]
|
||||||
}
|
);
|
||||||
else {
|
document.querySelector("#" + item["action"] + " .customValue").value =
|
||||||
|
item["value"];
|
||||||
|
document.querySelector("#" + item["action"] + " .customForce").value =
|
||||||
|
item["force"];
|
||||||
|
} else {
|
||||||
// new ones
|
// new ones
|
||||||
add_shortcut();
|
add_shortcut();
|
||||||
const dom = document.querySelector(".customs:last-of-type")
|
const dom = document.querySelector(".customs:last-of-type");
|
||||||
dom.querySelector(".customDo").value = item["action"];
|
dom.querySelector(".customDo").value = item["action"];
|
||||||
|
|
||||||
if (customActionsNoValues.includes(item["action"]))
|
if (customActionsNoValues.includes(item["action"]))
|
||||||
dom.querySelector(".customValue").disabled = true;
|
dom.querySelector(".customValue").disabled = true;
|
||||||
|
|
||||||
updateCustomShortcutInputText(dom.querySelector(".customKey"), item["key"]);
|
updateCustomShortcutInputText(
|
||||||
|
dom.querySelector(".customKey"),
|
||||||
|
item["key"]
|
||||||
|
);
|
||||||
dom.querySelector(".customValue").value = item["value"];
|
dom.querySelector(".customValue").value = item["value"];
|
||||||
dom.querySelector(".customForce").value = item["force"];
|
dom.querySelector(".customForce").value = item["force"];
|
||||||
}
|
}
|
||||||
@@ -271,60 +319,68 @@ 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.querySelectorAll(".removeParent").forEach(button => button.click()); // Remove added shortcuts
|
document
|
||||||
|
.querySelectorAll(".removeParent")
|
||||||
|
.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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function show_experimental() {
|
function show_experimental() {
|
||||||
document.querySelectorAll(".customForce").forEach(item => item.style.display = 'inline-block');
|
document
|
||||||
|
.querySelectorAll(".customForce")
|
||||||
|
.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);
|
||||||
document.getElementById('add').addEventListener('click', add_shortcut);
|
document.getElementById("add").addEventListener("click", add_shortcut);
|
||||||
document.getElementById('restore').addEventListener('click', restore_defaults);
|
document
|
||||||
document.getElementById('experimental').addEventListener('click', show_experimental);
|
.getElementById("restore")
|
||||||
|
.addEventListener("click", restore_defaults);
|
||||||
|
document
|
||||||
|
.getElementById("experimental")
|
||||||
|
.addEventListener("click", show_experimental);
|
||||||
|
|
||||||
function eventCaller(event, className, funcName) {
|
function eventCaller(event, className, funcName) {
|
||||||
if (!event.target.classList.contains(className)) {
|
if (!event.target.classList.contains(className)) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
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;
|
||||||
} else {
|
} else {
|
||||||
event.target.nextElementSibling.nextElementSibling.disabled = false;
|
event.target.nextElementSibling.nextElementSibling.disabled = false;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
11
popup.css
11
popup.css
@@ -1,5 +1,5 @@
|
|||||||
body {
|
body {
|
||||||
min-width: 8em
|
min-width: 8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
@@ -12,13 +12,14 @@ hr {
|
|||||||
|
|
||||||
button {
|
button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-image: linear-gradient(#EDEDED, #EDEDED 38%, #DEDEDE);
|
background-image: linear-gradient(#ededed, #ededed 38%, #dedede);
|
||||||
border: 1px solid rgba(0,0,0,0.25);
|
border: 1px solid rgba(0, 0, 0, 0.25);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 1px 0 rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.75);
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
|
||||||
|
inset 0 1px 2px rgba(255, 255, 255, 0.75);
|
||||||
color: #444;
|
color: #444;
|
||||||
text-shadow: 0 1px 0 rgb(240,240,240);
|
text-shadow: 0 1px 0 rgb(240, 240, 240);
|
||||||
font: inherit;
|
font: inherit;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Video Speed Controller: Popup</title>
|
<title>Video Speed Controller: Popup</title>
|
||||||
|
|||||||
51
popup.js
51
popup.js
@@ -1,5 +1,5 @@
|
|||||||
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"));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -11,34 +11,37 @@ document.addEventListener('DOMContentLoaded', 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);
|
||||||
});
|
});
|
||||||
|
|
||||||
function toggleEnabled(enabled, callback){
|
function toggleEnabled(enabled, callback) {
|
||||||
chrome.storage.sync.set({
|
chrome.storage.sync.set(
|
||||||
enabled: enabled,
|
{
|
||||||
}, function() {
|
enabled: enabled
|
||||||
toggleEnabledUI(enabled);
|
},
|
||||||
if(callback) callback(enabled);
|
function() {
|
||||||
});
|
toggleEnabledUI(enabled);
|
||||||
|
if (callback) callback(enabled);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleEnabledUI(enabled){
|
function toggleEnabledUI(enabled) {
|
||||||
document.querySelector('#enable').classList.toggle("hide", enabled);
|
document.querySelector("#enable").classList.toggle("hide", enabled);
|
||||||
document.querySelector('#disable').classList.toggle("hide", !enabled);
|
document.querySelector("#disable").classList.toggle("hide", !enabled);
|
||||||
|
|
||||||
const suffix = `${(enabled ? "" : "_disabled")}.png`
|
const suffix = `${enabled ? "" : "_disabled"}.png`;
|
||||||
chrome.browserAction.setIcon({
|
chrome.browserAction.setIcon({
|
||||||
"path": {
|
path: {
|
||||||
"19": "icons/icon19" + suffix,
|
"19": "icons/icon19" + suffix,
|
||||||
"38": "icons/icon38" + suffix,
|
"38": "icons/icon38" + suffix,
|
||||||
"48": "icons/icon48" + suffix
|
"48": "icons/icon48" + suffix
|
||||||
@@ -46,12 +49,14 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function settingsSavedReloadMessage(enabled){
|
function settingsSavedReloadMessage(enabled) {
|
||||||
setStatusMessage(`${enabled ? "Enabled" : "Disabled"}. Reload page to see changes`);
|
setStatusMessage(
|
||||||
|
`${enabled ? "Enabled" : "Disabled"}. Reload page to see changes`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStatusMessage(str){
|
function setStatusMessage(str) {
|
||||||
const status_element = document.querySelector('#status')
|
const status_element = document.querySelector("#status");
|
||||||
status_element.classList.toggle("hide", false);
|
status_element.classList.toggle("hide", false);
|
||||||
status_element.innerText = str;
|
status_element.innerText = str;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ button:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
opacity: 1.0;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:active {
|
button:active {
|
||||||
|
|||||||
Reference in New Issue
Block a user