diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1501b94 --- /dev/null +++ b/.pre-commit-config.yaml @@ -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 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c8c8ebc --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "trailingComma": "none", + "tabWidth": 2, + "printWidth": 80, + "semi": true, + "endOfLine": "auto", + "proseWrap": "always" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..87df582 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,85 @@ +# Contributing + +Video Speed Controller is an open source project licensed under the MIT license +with many contributers. Contributions are welcome, and greatly appreciated. + +If you would like to help, getting started is easy. + +## Get Started + +1. You must have a github account and be logged in +2. Fork the repo by clicking the "Fork" link on the top-right corner of the page +3. Once the fork is ready, clone to your local PC + + ```sh + $ git clone https://github.com//videospeed.git + Cloning into 'videospeed'... + remote: Enumerating objects: 10, done. + remote: Counting objects: 100% (10/10), done. + remote: Compressing objects: 100% (9/9), done. + remote: Total 877 (delta 3), reused 2 (delta 1), pack-reused 867 + Receiving objects: 100% (877/877), 317.65 KiB | 2.17 MiB/s, done. + Resolving deltas: 100% (543/543), done. + ``` + +4. Create a branch for your changes + + ```sh + $ cd videospeed + videospeed$ git checkout -b bugfix/1-fix-double-click + M .github/workflows/chrome-store-upload.yaml + M README.md + M options.js + Switched to a new branch 'bugfix/1-fix-double-click' + videospeed$ + ``` + +5. Open the code in your favorite code editor, make your changes + + ```sh + echo "Awesome changes" > somefile.js + git add . + ``` + + > Important: Your commit must be formatted using + > [prettier](https://prettier.io/). If it is not it may be autoformatted for + > you or your pull request may be rejected. + +6. Next, open Chrome/Brave/Chromium and enable developer mode via + `Settings > Extensions > Manage Extensions` and toggle `Developer mode` in + the top-right corner. +7. Click `Load unpacked` and browse to the folder you cloned videospeed to. +8. Try out your changes, make sure they work as expected +9. Commit and push your changes to github + + ```sh + git commit -m "Awesome description of some awesome changes." + git push + ``` + +10. Open your branch up on the github website then click `New pull request` and + write up a description of your changes. + +## Optional + +### Run Pre-Commit Checks Locally + +Installing [pre-commit](https://pre-commit.com/) is easy to do (click the link +for instructions on your platform). This repo comes with pre-commit already +configured. Doing this will ensure that your project is properly formatted and +runs some very basic tests. Once you have pre-commit installed on your system, +simply enter `pre-commit install` in your terminal in the folder to have these +checks run automatically each time you commit. + +Even better, after issueing the install command you can now manually run +pre-commit checks before committing via `pre-commit run --all-files` + +### Pull Upstream Changes + +You should always be working with the latest version of the tool to make pull +requests easy. If you want to do this easily, just add a second remote to your +local git repo like this +`git push --set-upstream origin feature/578-prettier.io-formatting` + +Now any time you like to pull the latest version in to your local branch you can +simply issue the command `git pull upstream master` diff --git a/README.md b/README.md index 4b22e20..66b1949 100644 --- a/README.md +++ b/README.md @@ -2,48 +2,86 @@ **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. - -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. +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. +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 -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. ![Player](https://cloud.githubusercontent.com/assets/2400185/24076745/5723e6ae-0c41-11e7-820c-1d8e814a2888.png) -#### *[Install Chrome Extension](https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk)* +### _[Install Chrome Extension](https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk)_ -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. -* **D** - increase playback speed. -* **R** - reset playback speed to 1.0x. -* **Z** - rewind video by 10 seconds. -* **X** - advance video by 10 seconds. -* **G** - toggle between current and user configurable preferred speed. -* **V** - show/hide the controller. +- **S** - decrease playback speed. +- **D** - increase playback speed. +- **R** - reset playback speed to 1.0x. +- **Z** - rewind video by 10 seconds. +- **X** - advance video by 10 seconds. +- **G** - toggle between current and user configurable preferred speed. +- **V** - show/hide the controller. -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". ![settings Add New shortcut](https://user-images.githubusercontent.com/121805/50726471-50242200-1172-11e9-902f-0e5958387617.jpg) -Some sites may assign other functionality to one of the assigned shortcut keys — these collisions are inevitable, unfortunately. As a workaround, the extension listens both for lower and upper case values (i.e. you can use `Shift-`) 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-`) 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 -**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 it not available. You can try manually disabling Flash plugin in Chrome: +**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 it not available. +You can try manually disabling Flash plugin in Chrome: -* In a new tab, navigate to `chrome://settings/content/flash` -* Disable "Allow sites to run Flash" -* Restart your browser and try playing your video again +- In a new tab, navigate to `chrome://settings/content/flash` +- Disable "Allow sites to run Flash" +- Restart your browser and try playing your video again -**The speed controls are not showing up for local videos?** To enable playback of local media (e.g. File > Open File), you need to grant additional permissions to the extension. +**The speed controls are not showing up for local videos?** To enable playback +of local media (e.g. File > Open File), you need to grant additional permissions +to the extension. -* In a new tab, navigate to `chrome://extensions` -* Find "Video Speed Controller" extension in the list and enable "Allow access to file URLs" -* Open a new tab and try opening a local file, the controls should show up +- In a new tab, navigate to `chrome://extensions` +- Find "Video Speed Controller" extension in the list and enable "Allow access + to file URLs" +- Open a new tab and try opening a local file, the controls should show up ### License diff --git a/inject.css b/inject.css index 3487118..33cc655 100644 --- a/inject.css +++ b/inject.css @@ -1,8 +1,12 @@ -.vsc-nosource { display: none !important; } -.vsc-hidden { display: none !important; } +.vsc-nosource { + display: none !important; +} +.vsc-hidden { + display: none !important; +} .vsc-manual { visibility: visible !important; - opacity: 1.0 !important; + opacity: 1 !important; } /* Origin specific overrides */ @@ -14,13 +18,13 @@ .ytp-autohide .vsc-controller { 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; } .ytp-autohide .vcs-show { visibility: visible; - opacity: 1.0; + opacity: 1; } /* YouTube embedded player */ @@ -32,8 +36,8 @@ /* Facebook player */ #facebook .vsc-controller { - position: relative; - top: 40px; + position: relative; + top: 40px; } /* Google Photos player */ @@ -76,7 +80,8 @@ div.video-wrapper + div.target { } /* 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; } diff --git a/inject.js b/inject.js index 0424f38..f1dda0f 100644 --- a/inject.js +++ b/inject.js @@ -1,154 +1,158 @@ - var regStrip = /^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm; +var regStrip = /^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm; - var tc = { - settings: { - lastSpeed: 1.0, // default 1x - enabled: true, // default enabled - speeds: {}, // empty object to hold speed for each source +var tc = { + settings: { + lastSpeed: 1.0, // default 1x + enabled: true, // default enabled + speeds: {}, // empty object to hold speed for each source - displayKeyCode: 86, // default: V - rememberSpeed: false, // default: false - audioBoolean: false, // default: false - startHidden: false, // default: false - controllerOpacity: 0.3, // default: 0.3 - keyBindings: [], - blacklist: ` - www.instagram.com - twitter.com - vine.co - imgur.com - teams.microsoft.com - `.replace(regStrip,'') - } - }; + displayKeyCode: 86, // default: V + rememberSpeed: false, // default: false + audioBoolean: false, // default: false + startHidden: false, // default: false + controllerOpacity: 0.3, // default: 0.3 + keyBindings: [], + blacklist: ` + www.instagram.com + twitter.com + vine.co + imgur.com + teams.microsoft.com + `.replace(regStrip, "") + } +}; - chrome.storage.sync.get(tc.settings, function (storage) { - tc.settings.keyBindings = storage.keyBindings; // Array - if (storage.keyBindings.length == 0) // if first initialization of 0.5.3 - { - // UPDATE - tc.settings.keyBindings.push({ - action: "slower", - key: Number(storage.slowerKeyCode) || 83, - value: Number(storage.speedStep) || 0.1, - force: false, - predefined: true - }); // default S - tc.settings.keyBindings.push({ - action: "faster", - key: Number(storage.fasterKeyCode) || 68, - value: Number(storage.speedStep) || 0.1, - force: false, - predefined: true - }); // default: D - tc.settings.keyBindings.push({ - action: "rewind", - key: Number(storage.rewindKeyCode) || 90, - value: Number(storage.rewindTime) || 10, - force: false, - predefined: true - }); // default: Z - tc.settings.keyBindings.push({ - action: "advance", - key: Number(storage.advanceKeyCode) || 88, - value: Number(storage.advanceTime) || 10, - force: false, - predefined: true - }); // default: X - tc.settings.keyBindings.push({ - action: "reset", - key: Number(storage.resetKeyCode) || 82, - value: 1.0, - force: false, - predefined: true - }); // default: R - tc.settings.keyBindings.push({ - action: "fast", - key: Number(storage.fastKeyCode) || 71, - value: Number(storage.fastSpeed) || 1.8, - force: false, - predefined: true - }); // default: G - tc.settings.version = "0.5.3"; +chrome.storage.sync.get(tc.settings, function(storage) { + tc.settings.keyBindings = storage.keyBindings; // Array + if (storage.keyBindings.length == 0) { + // if first initialization of 0.5.3 + // UPDATE + tc.settings.keyBindings.push({ + action: "slower", + key: Number(storage.slowerKeyCode) || 83, + value: Number(storage.speedStep) || 0.1, + force: false, + predefined: true + }); // default S + tc.settings.keyBindings.push({ + action: "faster", + key: Number(storage.fasterKeyCode) || 68, + value: Number(storage.speedStep) || 0.1, + force: false, + predefined: true + }); // default: D + tc.settings.keyBindings.push({ + action: "rewind", + key: Number(storage.rewindKeyCode) || 90, + value: Number(storage.rewindTime) || 10, + force: false, + predefined: true + }); // default: Z + tc.settings.keyBindings.push({ + action: "advance", + key: Number(storage.advanceKeyCode) || 88, + value: Number(storage.advanceTime) || 10, + force: false, + predefined: true + }); // default: X + tc.settings.keyBindings.push({ + action: "reset", + key: Number(storage.resetKeyCode) || 82, + value: 1.0, + force: false, + predefined: true + }); // default: R + tc.settings.keyBindings.push({ + action: "fast", + key: Number(storage.fastKeyCode) || 71, + value: Number(storage.fastSpeed) || 1.8, + force: false, + predefined: true + }); // default: G + tc.settings.version = "0.5.3"; - chrome.storage.sync.set({ - keyBindings: tc.settings.keyBindings, - version: tc.settings.version, - displayKeyCode: tc.settings.displayKeyCode, - rememberSpeed: tc.settings.rememberSpeed, - audioBoolean: tc.settings.audioBoolean, - startHidden: tc.settings.startHidden, - enabled: tc.settings.enabled, - controllerOpacity: tc.settings.controllerOpacity, - blacklist: tc.settings.blacklist.replace(regStrip, '') - }); - } - tc.settings.lastSpeed = Number(storage.lastSpeed); - tc.settings.displayKeyCode = Number(storage.displayKeyCode); - tc.settings.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); + 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; - } + // 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 setKeyBindings(action, value) { - tc.settings.keyBindings.find(item => item.action === action)["value"] = value; + 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 defineVideoController() { - tc.videoController = function(target, parent) { - if (target.dataset['vscid']) { - return target.vsc; +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); + + // settings.speeds[] ensures that same source used across video tags (e.g. fullscreen on YT) retains speed setting + // this.speed is a controller level variable that retains speed setting across source switches (e.g. video quality, playlist change) + this.speed = 1.0; + + if (!tc.settings.rememberSpeed) { + if (!tc.settings.speeds[target.currentSrc]) { + tc.settings.speeds[target.currentSrc] = this.speed; } + setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed + } else { + tc.settings.speeds[target.currentSrc] = tc.settings.lastSpeed; + } - this.video = target; - this.parent = target.parentElement || parent; - this.document = target.ownerDocument; - this.id = Math.random().toString(36).substr(2, 9); + target.playbackRate = tc.settings.speeds[target.currentSrc]; - // settings.speeds[] ensures that same source used across video tags (e.g. fullscreen on YT) retains speed setting - // this.speed is a controller level variable that retains speed setting across source switches (e.g. video quality, playlist change) - this.speed = 1.0; + this.div = this.initializeControls(); - if (!tc.settings.rememberSpeed) { - if (!tc.settings.speeds[target.currentSrc]) { - tc.settings.speeds[target.currentSrc] = this.speed; - } - setKeyBindings("reset", getKeyBindings("fast")); // resetSpeed = fastSpeed - } else { - tc.settings.speeds[target.currentSrc] = tc.settings.lastSpeed; - } - - target.playbackRate = tc.settings.speeds[target.currentSrc]; - - this.div=this.initializeControls(); - - target.addEventListener('play', this.handlePlay = function(event) { + target.addEventListener( + "play", + (this.handlePlay = function(event) { if (!tc.settings.rememberSpeed) { if (!tc.settings.speeds[target.currentSrc]) { tc.settings.speeds[target.currentSrc] = this.speed; @@ -158,9 +162,12 @@ tc.settings.speeds[target.currentSrc] = tc.settings.lastSpeed; } target.playbackRate = tc.settings.speeds[target.currentSrc]; - }.bind(this)); + }.bind(this)) + ); - target.addEventListener('ratechange', this.handleRatechange = function(event) { + target.addEventListener( + "ratechange", + (this.handleRatechange = function(event) { // Ignore ratechange events on unitialized videos. // 0 == No information is available about the media resource. if (event.target.readyState > 0) { @@ -169,71 +176,80 @@ tc.settings.speeds[this.video.currentSrc] = speed; tc.settings.lastSpeed = speed; this.speed = speed; - chrome.storage.sync.set({'lastSpeed': speed}, function() { - console.log('Speed setting saved: ' + speed); + chrome.storage.sync.set({ lastSpeed: speed }, function() { + console.log("Speed setting saved: " + speed); }); // show the controller for 1000ms if it's hidden. - runAction('blink', document, null, null); + runAction("blink", document, null, null); } - }.bind(this)); + }.bind(this)) + ); - var observer=new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'attributes' && (mutation.attributeName === 'src' || mutation.attributeName === 'currentSrc')){ - var controller = getController(this.id) - if(!controller){ - return; - } - if (!mutation.target.src && !mutation.target.currentSrc) { - controller.classList.add('vsc-nosource'); - } else { - controller.classList.remove('vsc-nosource'); - } + var observer = new MutationObserver(mutations => { + mutations.forEach(mutation => { + if ( + mutation.type === "attributes" && + (mutation.attributeName === "src" || + mutation.attributeName === "currentSrc") + ) { + var controller = getController(this.id); + if (!controller) { + return; } - }); + 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() { - return parseFloat(this.video.playbackRate).toFixed(2); + tc.videoController.prototype.getSpeed = function() { + return parseFloat(this.video.playbackRate).toFixed(2); + }; + + tc.videoController.prototype.remove = function() { + this.div.remove(); + this.video.removeEventListener("play", this.handlePlay); + this.video.removeEventListener("ratechange", this.handleRatechange); + delete this.video.dataset["vscid"]; + delete this.video.vsc; + }; + + tc.videoController.prototype.initializeControls = function() { + var document = this.document; + var speed = parseFloat(tc.settings.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"); } - tc.videoController.prototype.remove = function() { - this.div.remove(); - this.video.removeEventListener('play',this.handlePlay); - this.video.removeEventListener('ratechange',this.handleRatechange); - delete this.video.dataset['vscid']; - delete this.video.vsc; + if (tc.settings.startHidden) { + wrapper.classList.add("vsc-hidden"); } - tc.videoController.prototype.initializeControls = function() { - var document = this.document; - 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 = ` + var shadow = wrapper.attachShadow({ mode: "open" }); + var shadowTemplate = ` -
+
${speed} @@ -244,437 +260,475 @@
`; - shadow.innerHTML = shadowTemplate; - shadow.querySelector('.draggable').addEventListener('mousedown', (e) => { - runAction(e.target.dataset['action'], document, false, e); - }); + shadow.innerHTML = shadowTemplate; + shadow.querySelector(".draggable").addEventListener("mousedown", e => { + runAction(e.target.dataset["action"], document, false, e); + }); - forEach.call(shadow.querySelectorAll('button'), function(button) { - button.onclick = (e) => { - runAction(e.target.dataset['action'], document, getKeyBindings(e.target.dataset['action']), e); - } - }); + forEach.call(shadow.querySelectorAll("button"), function(button) { + button.onclick = e => { + runAction( + e.target.dataset["action"], + document, + getKeyBindings(e.target.dataset["action"]), + e + ); + }; + }); - this.speedIndicator = shadow.querySelector('span'); - var fragment = document.createDocumentFragment(); - fragment.appendChild(wrapper); + this.speedIndicator = shadow.querySelector("span"); + var fragment = document.createDocumentFragment(); + fragment.appendChild(wrapper); - this.video.dataset['vscid'] = this.id; + this.video.dataset["vscid"] = this.id; - switch (true) { - case (location.hostname == 'www.amazon.com'): - case (location.hostname == 'www.reddit.com'): - case (/hbogo\./).test(location.hostname): - // insert before parent to bypass overlay - this.parent.parentElement.insertBefore(fragment, this.parent); - break; - case (location.hostname == 'tv.apple.com'): - // insert after parent for correct stacking context - this.parent.getRootNode().querySelector('.scrim').prepend(fragment); + switch (true) { + case location.hostname == "www.amazon.com": + case location.hostname == "www.reddit.com": + case /hbogo\./.test(location.hostname): + // insert before parent to bypass overlay + this.parent.parentElement.insertBefore(fragment, this.parent); + break; + case location.hostname == "tv.apple.com": + // insert after parent for correct stacking context + this.parent + .getRootNode() + .querySelector(".scrim") + .prepend(fragment); - default: - // Note: when triggered via a MutationRecord, it's possible that the - // target is not the immediate parent. This appends the controller as - // the first element of the target, which may not be the parent. - this.parent.insertBefore(fragment, this.parent.firstChild); - } - return wrapper; + default: + // Note: when triggered via a MutationRecord, it's possible that the + // target is not the immediate parent. This appends the controller as + // the first element of the target, which may not be the parent. + this.parent.insertBefore(fragment, this.parent.firstChild); } + return wrapper; + }; +} + +function initializeWhenReady(document) { + escapeStringRegExp.matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; + function escapeStringRegExp(str) { + return str.replace(escapeStringRegExp.matchOperatorsRe, "\\$&"); } - function initializeWhenReady(document) { - escapeStringRegExp.matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; - function escapeStringRegExp(str) { - return str.replace(escapeStringRegExp.matchOperatorsRe, '\\$&'); + var blacklisted = false; + tc.settings.blacklist.split("\n").forEach(match => { + match = match.replace(regStrip, ""); + if (match.length == 0) { + return; } - var blacklisted = false; - tc.settings.blacklist.split("\n").forEach(match => { - match = match.replace(regStrip,'') - if (match.length == 0) { + if (match.startsWith("/")) { + try { + var regexp = new RegExp(match); + } catch (err) { return; } + } else { + var regexp = new RegExp(escapeStringRegExp(match)); + } - if (match.startsWith('/')) { - try { - var regexp = new RegExp(match); - } catch(err) { + if (regexp.test(location.href)) { + blacklisted = true; + return; + } + }); + + if (blacklisted) 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 getShadow(parent) { + let result = []; + function getChild(parent) { + if (parent.firstElementChild) { + var child = parent.firstElementChild; + do { + result = result.concat(child); + getChild(child); + if (child.shadowRoot) { + result = result.concat(getShadow(child.shadowRoot)); + } + child = child.nextElementSibling; + } while (child); + } + } + getChild(parent); + return result; +} +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) { + 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) { + 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; + + // 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; } - } else { - var regexp = new RegExp(escapeStringRegExp(match)); - } - if (regexp.test(location.href)) { - blacklisted = true; - return; - } - }) - - if (blacklisted) - return; - - window.onload = () => { - initializeNow(window.document) - }; - if (document) { - if (document.readyState === "complete") { - initializeNow(document); - } else { - document.onreadystatechange = () => { - if (document.readyState === "complete") { - initializeNow(document); - } + // Ignore keydown event if typing in an input box + if ( + event.target.nodeName === "INPUT" || + event.target.nodeName === "TEXTAREA" || + event.target.isContentEditable + ) { + return false; } - } - } - } - 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 = result.concat(child) - getChild(child) - if (child.shadowRoot) { - result = result.concat(getShadow(child.shadowRoot)) - } - child = child.nextElementSibling - } while (child) - } - } - getChild(parent) - return result - } - 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) { - 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) { - 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; - - // 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 (!getShadow(document.body).filter(x => x.tagName == 'vsc-controller')) { - return false; - } + // Ignore keydown event if typing in a page without vsc + if ( + !getShadow(document.body).filter(x => x.tagName == "vsc-controller") + ) { + return false; + } var item = tc.settings.keyBindings.find(item => item.key === keyCode); if (item) { runAction(item.action, document, item.value); - if (item.force === "true") {// disable websites key bindings + if (item.force === "true") { + // disable websites key bindings event.preventDefault(); event.stopPropagation(); } } - return false; - }, true); - }); + return false; + }, + true + ); + }); - - function checkForVideo(node, parent, added) { - // Only proceed with supposed removal if node is missing from DOM - if (!added && document.body.contains(node)) { - return; - } - if (node.nodeName === 'VIDEO' || (node.nodeName === 'AUDIO' && tc.settings.audioBoolean)) { - if (added) { - node.vsc = new tc.videoController(node, parent); - } else { - let id = node.dataset['vscid']; - 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); - } + function checkForVideo(node, parent, added) { + // Only proceed with supposed removal if node is missing from DOM + if (!added && document.body.contains(node)) { + return; + } + if ( + node.nodeName === "VIDEO" || + (node.nodeName === "AUDIO" && tc.settings.audioBoolean) + ) { + if (added) { + node.vsc = new tc.videoController(node, parent); + } else { + let id = node.dataset["vscid"]; + 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) { - 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'].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() - } - checkForVideo(node, node.parentNode || mutation.target, true); + var observer = new MutationObserver(function(mutations) { + // Process the DOM nodes lazily + requestIdleCallback( + _ => { + 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"].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(); } + checkForVideo(node, node.parentNode || mutation.target, true); } - break; - }; - }); - }, {timeout: 1000}); - }); - observer.observe(document, { - attributeFilter: ['aria-hidden'], - childList: true, - subtree: 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); - }); + if (tc.settings.audioBoolean) { + var mediaTags = document.querySelectorAll("video,audio"); + } else { + var mediaTags = document.querySelectorAll("video"); } - function runAction(action, document, value, e) { - if (tc.settings.audioBoolean) { - var mediaTags = getShadow(document.body).filter(x => { - return x.tagName == 'AUDIO' || x.tagName == 'VIDEO' - }); - } else { - var mediaTags = getShadow(document.body).filter(x => x.tagName == '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); + }); +} - mediaTags.forEach = Array.prototype.forEach; - - // Get the controller that was used if called from a button press event e - if (e) { - var targetController = e.target.getRootNode().host; - } - - mediaTags.forEach(function(v) { - var id = v.dataset['vscid']; - var controller = getController(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); - } - } +function runAction(action, document, value, e) { + if (tc.settings.audioBoolean) { + var mediaTags = getShadow(document.body).filter(x => { + return x.tagName == "AUDIO" || x.tagName == "VIDEO"; }); + } else { + var mediaTags = getShadow(document.body).filter(x => x.tagName == "VIDEO"); } - function pause(v) { - if (v.paused) { - v.play(); - } else { - v.pause(); + mediaTags.forEach = Array.prototype.forEach; + + // Get the controller that was used if called from a button press event e + if (e) { + var targetController = e.target.getRootNode().host; + } + + mediaTags.forEach(function(v) { + var id = v.dataset["vscid"]; + var controller = getController(id); + // Don't change video speed if the video has a different controller + if (e && !(targetController == controller)) { + return; } - } - function resetSpeed(v, target) { - if (v.playbackRate === target) { - if (v.playbackRate === getKeyBindings("reset")) { // resetSpeed - if (target !== 1.0) { - v.playbackRate = 1.0; - } else { - v.playbackRate = getKeyBindings("fast"); // fastSpeed + // 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); + } + } + }); +} + +function pause(v) { + if (v.paused) { + v.play(); + } else { + v.pause(); + } +} + +function resetSpeed(v, target) { + if (v.playbackRate === target) { + if (v.playbackRate === getKeyBindings("reset")) { + // resetSpeed + if (target !== 1.0) { + v.playbackRate = 1.0; } else { - v.playbackRate = getKeyBindings("reset"); // resetSpeed + v.playbackRate = getKeyBindings("fast"); // fastSpeed } } else { - setKeyBindings("reset", v.playbackRate); // resetSpeed - v.playbackRate = target; + v.playbackRate = getKeyBindings("reset"); // resetSpeed } + } else { + setKeyBindings("reset", v.playbackRate); // resetSpeed + v.playbackRate = target; + } +} + +function muted(v, value) { + v.muted = v.muted !== true; +} + +function setMark(v) { + v.vsc.mark = v.currentTime; +} + +function jumpToMark(v) { + if (v.vsc.mark && typeof v.vsc.mark === "number") { + v.currentTime = v.vsc.mark; + } +} + +function handleDrag(video, controller, e) { + const shadowController = controller.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) { - v.muted = v.muted !== true; - } + video.classList.add("vcs-dragging"); + shadowController.classList.add("dragging"); - function setMark(v) { - v.vsc.mark = v.currentTime; - } + const initialMouseXY = [e.clientX, e.clientY]; + const initialControllerXY = [ + parseInt(shadowController.style.left), + parseInt(shadowController.style.top) + ]; - function jumpToMark(v) { - if (v.vsc.mark && typeof v.vsc.mark === "number") { - v.currentTime = v.vsc.mark; - } - } + const startDragging = e => { + 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"; + }; - function handleDrag(video, controller, e) { - const shadowController = controller.shadowRoot.querySelector('#controller'); + const stopDragging = () => { + parentElement.removeEventListener("mousemove", startDragging); + parentElement.removeEventListener("mouseup", stopDragging); + parentElement.removeEventListener("mouseleave", stopDragging); - // 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; - } + shadowController.classList.remove("dragging"); + video.classList.remove("vcs-dragging"); + }; - video.classList.add('vcs-dragging'); - shadowController.classList.add('dragging'); + parentElement.addEventListener("mouseup", stopDragging); + parentElement.addEventListener("mouseleave", stopDragging); + parentElement.addEventListener("mousemove", startDragging); +} - const initialMouseXY = [e.clientX, e.clientY]; - const initialControllerXY = [ - parseInt(shadowController.style.left), - parseInt(shadowController.style.top) - ]; +var timer; +var animation = false; +function showController(controller) { + controller.classList.add("vcs-show"); - const startDragging = (e) => { - 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'; - } + if (animation) clearTimeout(timer); - const stopDragging = () => { - parentElement.removeEventListener('mousemove', startDragging); - parentElement.removeEventListener('mouseup', stopDragging); - parentElement.removeEventListener('mouseleave', stopDragging); - - shadowController.classList.remove('dragging'); - 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); - } + animation = true; + timer = setTimeout(function() { + controller.classList.remove("vcs-show"); + animation = false; + }, 2000); +} diff --git a/manifest.json b/manifest.json index 0eaab53..b911445 100755 --- a/manifest.json +++ b/manifest.json @@ -10,7 +10,7 @@ "48": "icons/icon48.png", "128": "icons/icon128.png" }, - "permissions": [ "activeTab", "storage" ], + "permissions": ["activeTab", "storage"], "options_page": "options.html", "browser_action": { "default_icon": { @@ -20,9 +20,10 @@ }, "default_popup": "popup.html" }, - "content_scripts": [{ + "content_scripts": [ + { "all_frames": true, - "matches": [ "http://*/*", "https://*/*", "file:///*" ], + "matches": ["http://*/*", "https://*/*", "file:///*"], "match_about_blank": true, "exclude_matches": [ "https://plus.google.com/hangouts/*", @@ -31,11 +32,9 @@ "https://teamtreehouse.com/*", "http://www.hitbox.tv/*" ], - "css": [ "inject.css" ], - "js": [ "inject.js" ] + "css": ["inject.css"], + "js": ["inject.js"] } ], - "web_accessible_resources": [ - "inject.css", "shadow.css" - ] + "web_accessible_resources": ["inject.css", "shadow.css"] } diff --git a/options.css b/options.css index 352ca29..3e5541d 100644 --- a/options.css +++ b/options.css @@ -1,109 +1,112 @@ body { - margin: 0; - padding-left: 15px; - padding-top: 53px; - font-family: sans-serif; - font-size: 12px; - color: rgb(48, 57, 66); + margin: 0; + padding-left: 15px; + padding-top: 53px; + font-family: sans-serif; + font-size: 12px; + color: rgb(48, 57, 66); } -h1, h2, h3 { - font-weight: normal; - line-height: 1; - user-select: none; - cursor: default; +h1, +h2, +h3 { + font-weight: normal; + line-height: 1; + user-select: none; + cursor: default; } h1 { - font-size: 1.5em; - margin: 21px 0 13px; + font-size: 1.5em; + margin: 21px 0 13px; } h3 { - font-size: 1.2em; - margin-bottom: 0.8em; - color: black; + font-size: 1.2em; + margin-bottom: 0.8em; + color: black; } p { - margin: 0.65em 0; + margin: 0.65em 0; } header { - position: fixed; - top: 0; - left: 15px; - right: 0; - border-bottom: 1px solid #EEE; - background: linear-gradient(white, white 40%, rgba(255, 255, 255, 0.92)); + position: fixed; + top: 0; + left: 15px; + right: 0; + border-bottom: 1px solid #eee; + background: linear-gradient(white, white 40%, rgba(255, 255, 255, 0.92)); } -header, section { - min-width: 600px; - max-width: 738px; +header, +section { + min-width: 600px; + max-width: 738px; } section { - padding-left: 18px; - margin-top: 8px; - margin-bottom: 24px; + padding-left: 18px; + margin-top: 8px; + margin-bottom: 24px; } section h3 { - margin-left: -18px; + margin-left: -18px; } button { - -webkit-appearance: none; - position: relative; + -webkit-appearance: none; + position: relative; - margin: 0 1px 0 0; - padding: 0 10px; - min-width: 4em; - min-height: 2em; + margin: 0 1px 0 0; + padding: 0 10px; + min-width: 4em; + min-height: 2em; - background-image: linear-gradient(#EDEDED, #EDEDED 38%, #DEDEDE); - border: 1px solid rgba(0,0,0,0.25); - border-radius: 2px; - outline: none; - box-shadow: 0 1px 0 rgba(0,0,0,0.08), inset 0 1px 2px rgba(255,255,255,0.75); - color: #444; - text-shadow: 0 1px 0 rgb(240,240,240); - font: inherit; + background-image: linear-gradient(#ededed, #ededed 38%, #dedede); + border: 1px solid rgba(0, 0, 0, 0.25); + border-radius: 2px; + outline: none; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08), + inset 0 1px 2px rgba(255, 255, 255, 0.75); + color: #444; + text-shadow: 0 1px 0 rgb(240, 240, 240); + font: inherit; - user-select: none; + user-select: none; } - input[type="text"] { - width: 75px; - text-align: center; + width: 75px; + text-align: center; } .row { - margin: 5px 0px; + margin: 5px 0px; } label { - display: inline-block; - width: 170px; - vertical-align: top; + display: inline-block; + width: 170px; + vertical-align: top; } #status { - color: #9D9D9D; - display: inline-block; - margin-left: 50px; + color: #9d9d9d; + display: inline-block; + margin-left: 50px; } #faq { - margin-top: 2em; + margin-top: 2em; } select { - width: 170px; + width: 170px; } .customForce { - display: none; - width: 250px; + display: none; + width: 250px; } .customKey { color: transparent; text-shadow: 0 0 0 #000000; -} \ No newline at end of file +} diff --git a/options.html b/options.html index 765809b..36c9f3c 100644 --- a/options.html +++ b/options.html @@ -1,119 +1,170 @@ - - Video Speed Controller: Options - - - - -
-

Video Speed Controller

-
+ + Video Speed Controller: Options + + + + +
+

Video Speed Controller

+

Shortcuts

-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
- +

Other

- +
- +
- +
- +
- +
-
@@ -129,21 +180,39 @@

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 it not available. You can try manually disabling Flash plugin in Chrome:

+

+ 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 it + not available. You can try manually disabling Flash plugin in Chrome: +

    -
  • In a new tab, navigate to chrome://settings/content/flash
  • +
  • + In a new tab, navigate to chrome://settings/content/flash +
  • Disable "Allow sites to run Flash"
  • Restart your browser and try playing your video again

The speed controls are not showing up for local videos?

-

To enable playback of local media (e.g. File > Open File), you need to grant additional permissions to the extension.

+

+ To enable playback of local media (e.g. File > Open File), you need + to grant additional permissions to the extension. +

  • In a new tab, navigate to chrome://extensions
  • -
  • Find "Video Speed Controller" extension in the list and enable "Allow access to file URLs"
  • -
  • Open a new tab and try opening a local file, the controls should show up
  • +
  • + Find "Video Speed Controller" extension in the list and enable "Allow + access to file URLs" +
  • +
  • + Open a new tab and try opening a local file, the controls should show + up +
diff --git a/options.js b/options.js index 8bc0719..721c83a 100644 --- a/options.js +++ b/options.js @@ -1,21 +1,21 @@ -var regStrip=/^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm; +var regStrip = /^[\r\t\f\v ]+|[\r\t\f\v ]+$/gm; var tcDefaults = { - speed: 1.0, // default: - displayKeyCode: 86, // default: V + speed: 1.0, // default: + displayKeyCode: 86, // default: V rememberSpeed: false, // default: false audioBoolean: false, // default: false - startHidden: false, // default: false - enabled: true, // default enabled + startHidden: false, // default: false + enabled: true, // default enabled controllerOpacity: 0.3, // default: 0.3 keyBindings: [ - {action: "display", key: 86, value: 0, force: false, predefined: true }, // V - {action: "slower", key: 83, value: 0.1, force: false, predefined: true}, // S - {action: "faster", key: 68, value: 0.1, force: false, predefined: true}, // D - {action: "rewind", key: 90, value: 10, force: false, predefined: true}, // Z - {action: "advance", key: 88, value: 10, force: false, predefined: true}, // X - {action: "reset", key: 82, value: 1, force: false, predefined: true}, // R - {action: "fast", key: 71, value: 1.8, force: false, predefined: true} // G + { action: "display", key: 86, value: 0, force: false, predefined: true }, // V + { action: "slower", key: 83, value: 0.1, force: false, predefined: true }, // S + { action: "faster", key: 68, value: 0.1, force: false, predefined: true }, // D + { action: "rewind", key: 90, value: 10, force: false, predefined: true }, // Z + { action: "advance", key: 88, value: 10, force: false, predefined: true }, // X + { action: "reset", key: 82, value: 1, force: false, predefined: true }, // R + { action: "fast", key: 71, value: 1.8, force: false, predefined: true } // G ], blacklist: ` www.instagram.com @@ -23,78 +23,81 @@ var tcDefaults = { vine.co imgur.com teams.microsoft.com - `.replace(regStrip, '') + `.replace(regStrip, "") }; var keyBindings = []; var keyCodeAliases = { - 0: 'null', - null: 'null', - undefined: 'null', - 32: 'Space', - 37: 'Left', - 38: 'Up', - 39: 'Right', - 40: 'Down', - 96: 'Num 0', - 97: 'Num 1', - 98: 'Num 2', - 99: 'Num 3', - 100: 'Num 4', - 101: 'Num 5', - 102: 'Num 6', - 103: 'Num 7', - 104: 'Num 8', - 105: 'Num 9', - 106: 'Num *', - 107: 'Num +', - 109: 'Num -', - 110: 'Num .', - 111: 'Num /', - 112: 'F1', - 113: 'F2', - 114: 'F3', - 115: 'F4', - 116: 'F5', - 117: 'F6', - 118: 'F7', - 119: 'F8', - 120: 'F9', - 121: 'F10', - 122: 'F11', - 123: 'F12', - 186: ';', - 188: '<', - 189: '-', - 187: '+', - 190: '>', - 191: '/', - 192: '~', - 219: '[', - 220: '\\', - 221: ']', - 222: '\'', -} + 0: "null", + null: "null", + undefined: "null", + 32: "Space", + 37: "Left", + 38: "Up", + 39: "Right", + 40: "Down", + 96: "Num 0", + 97: "Num 1", + 98: "Num 2", + 99: "Num 3", + 100: "Num 4", + 101: "Num 5", + 102: "Num 6", + 103: "Num 7", + 104: "Num 8", + 105: "Num 9", + 106: "Num *", + 107: "Num +", + 109: "Num -", + 110: "Num .", + 111: "Num /", + 112: "F1", + 113: "F2", + 114: "F3", + 115: "F4", + 116: "F5", + 117: "F6", + 118: "F7", + 119: "F8", + 120: "F9", + 121: "F10", + 122: "F11", + 123: "F12", + 186: ";", + 188: "<", + 189: "-", + 187: "+", + 190: ">", + 191: "/", + 192: "~", + 219: "[", + 220: "\\", + 221: "]", + 222: "'" +}; function recordKeyPress(e) { if ( - (e.keyCode >= 48 && e.keyCode <= 57) // Numbers 0-9 - || (e.keyCode >= 65 && e.keyCode <= 90) // Letters A-Z - || keyCodeAliases[e.keyCode] // Other character keys + (e.keyCode >= 48 && e.keyCode <= 57) || // Numbers 0-9 + (e.keyCode >= 65 && e.keyCode <= 90) || // Letters A-Z + 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.preventDefault(); e.stopPropagation(); - } else if (e.keyCode === 8) { // Clear input when backspace pressed - e.target.value = ''; - } else if (e.keyCode === 27) { // When esc clicked, clear input - e.target.value = 'null'; + } else if (e.keyCode === 8) { + // Clear input when backspace pressed + e.target.value = ""; + } else if (e.keyCode === 27) { + // When esc clicked, clear input + e.target.value = "null"; e.target.keyCode = null; } -}; +} function inputFilterNumbersOnly(e) { var char = String.fromCharCode(e.keyCode); @@ -102,18 +105,20 @@ function inputFilterNumbersOnly(e) { e.preventDefault(); e.stopPropagation(); } -}; +} function inputFocus(e) { - e.target.value = ""; -}; + e.target.value = ""; +} 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) { - document.getElementById(inputId).value = keyCodeAliases[keyCode] || String.fromCharCode(keyCode); + document.getElementById(inputId).value = + keyCodeAliases[keyCode] || String.fromCharCode(keyCode); document.getElementById(inputId).keyCode = keyCode; } @@ -123,7 +128,7 @@ function updateCustomShortcutInputText(inputItem, keyCode) { } // 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() { var html = ` `; - var div = document.createElement('div'); - div.setAttribute('class', 'row customs'); + var div = document.createElement("div"); + div.setAttribute("class", "row customs"); div.innerHTML = html; var customs_element = document.getElementById("customs"); - customs_element.insertBefore(div, customs_element.children[customs_element.childElementCount - 1]); + customs_element.insertBefore( + div, + customs_element.children[customs_element.childElementCount - 1] + ); } function createKeyBindings(item) { @@ -158,28 +166,37 @@ function createKeyBindings(item) { const key = item.querySelector(".customKey").keyCode; const value = Number(item.querySelector(".customValue").value); const force = item.querySelector(".customForce").value; - const predefined = !!item.id;//item.id ? true : false; + 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 function validate() { var valid = true; - var status = document.getElementById('status'); - document.getElementById('blacklist').value.split("\n").forEach(match => { - match = match.replace(regStrip,'') - if (match.startsWith('/')) { - try { - var regexp = new RegExp(match); - } catch(err) { - status.textContent = 'Error: Invalid Regex: ' + match - + '. Unable to save'; - valid = false; - return; + var status = document.getElementById("status"); + document + .getElementById("blacklist") + .value.split("\n") + .forEach(match => { + match = match.replace(regStrip, ""); + if (match.startsWith("/")) { + try { + var regexp = new RegExp(match); + } catch (err) { + status.textContent = + "Error: Invalid Regex: " + match + ". Unable to save"; + valid = false; + return; + } } - } - }) + }); return valid; } @@ -189,47 +206,70 @@ function save_options() { return; } 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 audioBoolean = document.getElementById('audioBoolean').checked; - var enabled = document.getElementById('enabled').checked; - var startHidden = document.getElementById('startHidden').checked; - var controllerOpacity = document.getElementById('controllerOpacity').value; - var blacklist = document.getElementById('blacklist').value; + var rememberSpeed = document.getElementById("rememberSpeed").checked; + var audioBoolean = document.getElementById("audioBoolean").checked; + var enabled = document.getElementById("enabled").checked; + var startHidden = document.getElementById("startHidden").checked; + var controllerOpacity = document.getElementById("controllerOpacity").value; + var blacklist = document.getElementById("blacklist").value; - chrome.storage.sync.remove(["resetSpeed", "speedStep", "fastSpeed", "rewindTime", "advanceTime", "resetKeyCode", "slowerKeyCode", "fasterKeyCode", "rewindKeyCode", "advanceKeyCode", "fastKeyCode"]); - chrome.storage.sync.set({ - rememberSpeed: rememberSpeed, - audioBoolean: audioBoolean, - enabled: enabled, - startHidden: startHidden, - 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); - }); + chrome.storage.sync.remove([ + "resetSpeed", + "speedStep", + "fastSpeed", + "rewindTime", + "advanceTime", + "resetKeyCode", + "slowerKeyCode", + "fasterKeyCode", + "rewindKeyCode", + "advanceKeyCode", + "fastKeyCode" + ]); + chrome.storage.sync.set( + { + rememberSpeed: rememberSpeed, + audioBoolean: audioBoolean, + enabled: enabled, + startHidden: startHidden, + 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 function restore_options() { chrome.storage.sync.get(tcDefaults, function(storage) { - document.getElementById('rememberSpeed').checked = storage.rememberSpeed; - document.getElementById('audioBoolean').checked = storage.audioBoolean; - document.getElementById('enabled').checked = storage.enabled; - document.getElementById('startHidden').checked = storage.startHidden; - document.getElementById('controllerOpacity').value = storage.controllerOpacity; - document.getElementById('blacklist').value = storage.blacklist; + document.getElementById("rememberSpeed").checked = storage.rememberSpeed; + document.getElementById("audioBoolean").checked = storage.audioBoolean; + document.getElementById("enabled").checked = storage.enabled; + document.getElementById("startHidden").checked = storage.startHidden; + document.getElementById("controllerOpacity").value = + storage.controllerOpacity; + document.getElementById("blacklist").value = storage.blacklist; // ensure that there is a "display" binding for upgrades from versions that had it as a separate binding - if(storage.keyBindings.filter(x => x.action == "display").length == 0){ - storage.keyBindings.push({ action: "display", value: 0, force: false, predefined: true }); + if (storage.keyBindings.filter(x => x.action == "display").length == 0) { + storage.keyBindings.push({ + action: "display", + value: 0, + force: false, + predefined: true + }); } for (let i in storage.keyBindings) { @@ -237,27 +277,36 @@ function restore_options() { if (item.predefined) { //do predefined ones because their value needed for overlay // document.querySelector("#" + item["action"] + " .customDo").value = item["action"]; - if (item["action"] == "display" && typeof (item["key"]) === "undefined"){ + if (item["action"] == "display" && typeof item["key"] === "undefined") { item["key"] = storage.displayKeyCode || tcDefaults.displayKeyCode; // V } if (customActionsNoValues.includes(item["action"])) - document.querySelector("#" + item["action"] + " .customValue").disabled = true; + document.querySelector( + "#" + item["action"] + " .customValue" + ).disabled = true; - updateCustomShortcutInputText(document.querySelector("#" + item["action"] + " .customKey"), item["key"]); - document.querySelector("#" + item["action"] + " .customValue").value = item["value"]; - document.querySelector("#" + item["action"] + " .customForce").value = item["force"]; - } - else { + updateCustomShortcutInputText( + document.querySelector("#" + item["action"] + " .customKey"), + item["key"] + ); + document.querySelector("#" + item["action"] + " .customValue").value = + item["value"]; + document.querySelector("#" + item["action"] + " .customForce").value = + item["force"]; + } else { // new ones add_shortcut(); - const dom = document.querySelector(".customs:last-of-type") + const dom = document.querySelector(".customs:last-of-type"); dom.querySelector(".customDo").value = item["action"]; if (customActionsNoValues.includes(item["action"])) dom.querySelector(".customValue").disabled = true; - updateCustomShortcutInputText(dom.querySelector(".customKey"), item["key"]); + updateCustomShortcutInputText( + dom.querySelector(".customKey"), + item["key"] + ); dom.querySelector(".customValue").value = item["value"]; dom.querySelector(".customForce").value = item["force"]; } @@ -268,60 +317,68 @@ function restore_options() { function restore_defaults() { chrome.storage.sync.set(tcDefaults, function() { 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. - var status = document.getElementById('status'); - status.textContent = 'Default options restored'; + var status = document.getElementById("status"); + status.textContent = "Default options restored"; setTimeout(function() { - status.textContent = ''; + status.textContent = ""; }, 1000); }); } 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(); - document.getElementById('save').addEventListener('click', save_options); - document.getElementById('add').addEventListener('click', add_shortcut); - document.getElementById('restore').addEventListener('click', restore_defaults); - document.getElementById('experimental').addEventListener('click', show_experimental); + document.getElementById("save").addEventListener("click", save_options); + document.getElementById("add").addEventListener("click", add_shortcut); + document + .getElementById("restore") + .addEventListener("click", restore_defaults); + document + .getElementById("experimental") + .addEventListener("click", show_experimental); function eventCaller(event, className, funcName) { if (!event.target.classList.contains(className)) { - return + return; } funcName(event); } - document.addEventListener('keypress', (event) => { - eventCaller(event, "customValue", inputFilterNumbersOnly) + document.addEventListener("keypress", event => { + eventCaller(event, "customValue", inputFilterNumbersOnly); }); - document.addEventListener('focus', (event) => { - eventCaller(event, "customKey", inputFocus) + document.addEventListener("focus", event => { + eventCaller(event, "customKey", inputFocus); }); - document.addEventListener('blur', (event) => { - eventCaller(event, "customKey", inputBlur) + document.addEventListener("blur", event => { + eventCaller(event, "customKey", inputBlur); }); - document.addEventListener('keydown', (event) => { - eventCaller(event, "customKey", recordKeyPress) + document.addEventListener("keydown", event => { + eventCaller(event, "customKey", recordKeyPress); }); - document.addEventListener('click', (event) => { - eventCaller(event, "removeParent", function () { - event.target.parentNode.remove() - }) + document.addEventListener("click", event => { + eventCaller(event, "removeParent", function() { + event.target.parentNode.remove(); + }); }); - document.addEventListener('change', (event) => { - eventCaller(event, "customDo", function () { + document.addEventListener("change", event => { + eventCaller(event, "customDo", function() { if (customActionsNoValues.includes(event.target.value)) { event.target.nextElementSibling.nextElementSibling.disabled = true; event.target.nextElementSibling.nextElementSibling.value = 0; } else { event.target.nextElementSibling.nextElementSibling.disabled = false; } - }) + }); }); -}) +}); diff --git a/popup.css b/popup.css index f8b9a92..1787075 100644 --- a/popup.css +++ b/popup.css @@ -1,5 +1,5 @@ body { - min-width: 8em + min-width: 8em; } hr { @@ -12,13 +12,14 @@ hr { button { width: 100%; - background-image: linear-gradient(#EDEDED, #EDEDED 38%, #DEDEDE); - border: 1px solid rgba(0,0,0,0.25); + background-image: linear-gradient(#ededed, #ededed 38%, #dedede); + border: 1px solid rgba(0, 0, 0, 0.25); border-radius: 2px; 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; - text-shadow: 0 1px 0 rgb(240,240,240); + text-shadow: 0 1px 0 rgb(240, 240, 240); font: inherit; user-select: none; } diff --git a/popup.html b/popup.html index 51536d5..28354b9 100644 --- a/popup.html +++ b/popup.html @@ -1,4 +1,4 @@ - + Video Speed Controller: Popup diff --git a/popup.js b/popup.js index 1a0dfb4..a783635 100644 --- a/popup.js +++ b/popup.js @@ -1,44 +1,47 @@ -document.addEventListener('DOMContentLoaded', function () { - document.querySelector('#config').addEventListener('click', function() { +document.addEventListener("DOMContentLoaded", function() { + document.querySelector("#config").addEventListener("click", function() { window.open(chrome.runtime.getURL("options.html")); }); - - document.querySelector('#about').addEventListener('click', function() { + + document.querySelector("#about").addEventListener("click", function() { window.open("https://github.com/igrigorik/videospeed"); }); - document.querySelector('#feedback').addEventListener('click', function() { + document.querySelector("#feedback").addEventListener("click", function() { window.open("https://github.com/igrigorik/videospeed/issues"); }); - document.querySelector('#enable').addEventListener('click', function() { - toggleEnabled(true, settingsSavedReloadMessage); + document.querySelector("#enable").addEventListener("click", function() { + toggleEnabled(true, settingsSavedReloadMessage); }); - document.querySelector('#disable').addEventListener('click', function() { - toggleEnabled(false, settingsSavedReloadMessage); + document.querySelector("#disable").addEventListener("click", function() { + toggleEnabled(false, settingsSavedReloadMessage); }); - chrome.storage.sync.get({enabled: true}, function(storage) { + chrome.storage.sync.get({ enabled: true }, function(storage) { toggleEnabledUI(storage.enabled); }); - function toggleEnabled(enabled, callback){ - chrome.storage.sync.set({ - enabled: enabled, - }, function() { - toggleEnabledUI(enabled); - if(callback) callback(enabled); - }); + function toggleEnabled(enabled, callback) { + chrome.storage.sync.set( + { + enabled: enabled + }, + function() { + toggleEnabledUI(enabled); + if (callback) callback(enabled); + } + ); } - function toggleEnabledUI(enabled){ - document.querySelector('#enable').classList.toggle("hide", enabled); - document.querySelector('#disable').classList.toggle("hide", !enabled); + function toggleEnabledUI(enabled) { + document.querySelector("#enable").classList.toggle("hide", enabled); + document.querySelector("#disable").classList.toggle("hide", !enabled); - const suffix = `${(enabled ? "" : "_disabled")}.png` + const suffix = `${enabled ? "" : "_disabled"}.png`; chrome.browserAction.setIcon({ - "path": { + path: { "19": "icons/icon19" + suffix, "38": "icons/icon38" + suffix, "48": "icons/icon48" + suffix @@ -46,12 +49,14 @@ document.addEventListener('DOMContentLoaded', function () { }); } - function settingsSavedReloadMessage(enabled){ - setStatusMessage(`${enabled ? "Enabled" : "Disabled"}. Reload page to see changes`); + function settingsSavedReloadMessage(enabled) { + setStatusMessage( + `${enabled ? "Enabled" : "Disabled"}. Reload page to see changes` + ); } - function setStatusMessage(str){ - const status_element = document.querySelector('#status') + function setStatusMessage(str) { + const status_element = document.querySelector("#status"); status_element.classList.toggle("hide", false); status_element.innerText = str; } diff --git a/shadow.css b/shadow.css index 3b9a7e1..0766347 100644 --- a/shadow.css +++ b/shadow.css @@ -72,7 +72,7 @@ button:focus { } button:hover { - opacity: 1.0; + opacity: 1; } button:active {