From 0fde1a3cc964ffc22efff6dcb8cf520eb1abd989 Mon Sep 17 00:00:00 2001 From: George Ogata Date: Mon, 18 Jan 2016 03:01:10 -0500 Subject: [PATCH 1/2] Extract videoController definition, scope to video's ownerDocument. --- inject.js | 319 +++++++++++++++++++++++++++--------------------------- 1 file changed, 162 insertions(+), 157 deletions(-) diff --git a/inject.js b/inject.js index f23fe37..9453b9b 100644 --- a/inject.js +++ b/inject.js @@ -33,162 +33,119 @@ chrome.extension.sendMessage({}, function(response) { } ); + function defineVideoController() { + tc.videoController = function(target) { + this.video = target; + this.document = target.ownerDocument; + if (!tc.settings.rememberSpeed) { + tc.settings.speed = 1.0; + } + this.initializeControls(); + + target.addEventListener('play', function(event) { + target.playbackRate = tc.settings.speed; + }); + + target.addEventListener('ratechange', function(event) { + if (target.readyState === 0) { + return; + } + var speed = this.getSpeed(); + this.speedIndicator.textContent = speed; + tc.settings.speed = speed; + chrome.storage.sync.set({'speed': speed}); + }.bind(this)); + + target.playbackRate = tc.settings.speed; + }; + + tc.videoController.prototype.getSpeed = function() { + return parseFloat(this.video.playbackRate).toFixed(2); + } + + tc.videoController.prototype.remove = function() { + this.parentElement.removeChild(this); + } + + tc.videoController.prototype.initializeControls = function() { + var fragment = this.document.createDocumentFragment(); + var container = this.document.createElement('div'); + var speedIndicator = this.document.createElement('span'); + + var controls = this.document.createElement('span'); + var fasterButton = this.document.createElement('button'); + var slowerButton = this.document.createElement('button'); + var rewindButton = this.document.createElement('button'); + var advanceButton = this.document.createElement('button'); + var hideButton = this.document.createElement('button'); + + rewindButton.innerHTML = '«'; + fasterButton.textContent = '+'; + slowerButton.textContent = '-'; + advanceButton.innerHTML = '»'; + hideButton.textContent = 'x'; + hideButton.className = 'tc-hideButton'; + + controls.appendChild(rewindButton); + controls.appendChild(slowerButton); + controls.appendChild(fasterButton); + controls.appendChild(advanceButton); + controls.appendChild(hideButton); + + container.appendChild(speedIndicator); + container.appendChild(controls); + + container.classList.add('tc-videoController'); + controls.classList.add('tc-controls'); + + fragment.appendChild(container); + this.video.parentElement.insertBefore(fragment, this.video); + + var speed = parseFloat(tc.settings.speed).toFixed(2); + speedIndicator.textContent = speed; + this.speedIndicator = speedIndicator; + + container.addEventListener('click', function(e) { + if (e.target === slowerButton) { + runAction('slower', this.document) + } else if (e.target === fasterButton) { + runAction('faster', this.document) + } else if (e.target === rewindButton) { + runAction('rewind', this.document) + } else if (e.target === advanceButton) { + runAction('advance', this.document) + } else if (e.target === hideButton) { + container.nextSibling.classList.add('vc-cancelled') + container.remove(); + } + + e.preventDefault(); + e.stopPropagation(); + }, true); + + // Prevent full screen mode on YouTube + container.addEventListener('dblclick', function(e) { + e.preventDefault(); + e.stopPropagation(); + }, true); + + // Prevent full screen mode on Vimeo + container.addEventListener('mousedown', function(e) { + e.preventDefault(); + e.stopPropagation(); + }, true); + } + + function setSpeed(v, speed) { + v.playbackRate = speed; + } + } + function initializeVideoSpeed() { if (document.readyState === 'complete') { clearInterval(readyStateCheckInterval); - tc.videoController = function(target) { - this.video = target; - if (!tc.settings.rememberSpeed) { - tc.settings.speed = 1.0; - } - this.initializeControls(); - - target.addEventListener('play', function(event) { - target.playbackRate = tc.settings.speed; - }); - - target.addEventListener('ratechange', function(event) { - if (target.readyState === 0) { - return; - } - var speed = this.getSpeed(); - this.speedIndicator.textContent = speed; - tc.settings.speed = speed; - chrome.storage.sync.set({'speed': speed}); - }.bind(this)); - - target.playbackRate = tc.settings.speed; - }; - - tc.videoController.prototype.getSpeed = function() { - return parseFloat(this.video.playbackRate).toFixed(2); - } - - tc.videoController.prototype.remove = function() { - this.parentElement.removeChild(this); - } - - tc.videoController.prototype.initializeControls = function() { - var fragment = document.createDocumentFragment(); - var container = document.createElement('div'); - var speedIndicator = document.createElement('span'); - - var controls = document.createElement('span'); - var fasterButton = document.createElement('button'); - var slowerButton = document.createElement('button'); - var rewindButton = document.createElement('button'); - var advanceButton = document.createElement('button'); - var hideButton = document.createElement('button'); - - rewindButton.innerHTML = '«'; - fasterButton.textContent = '+'; - slowerButton.textContent = '-'; - advanceButton.innerHTML = '»'; - hideButton.textContent = 'x'; - hideButton.className = 'tc-hideButton'; - - controls.appendChild(rewindButton); - controls.appendChild(slowerButton); - controls.appendChild(fasterButton); - controls.appendChild(advanceButton); - controls.appendChild(hideButton); - - container.appendChild(speedIndicator); - container.appendChild(controls); - - container.classList.add('tc-videoController'); - controls.classList.add('tc-controls'); - - fragment.appendChild(container); - this.video.parentElement.insertBefore(fragment, this.video); - - var speed = parseFloat(tc.settings.speed).toFixed(2); - speedIndicator.textContent = speed; - this.speedIndicator = speedIndicator; - - container.addEventListener('click', function(e) { - if (e.target === slowerButton) { - runAction('slower') - } else if (e.target === fasterButton) { - runAction('faster') - } else if (e.target === rewindButton) { - runAction('rewind') - } else if (e.target === advanceButton) { - runAction('advance') - } else if (e.target === hideButton) { - container.nextSibling.classList.add('vc-cancelled') - container.remove(); - } - - e.preventDefault(); - e.stopPropagation(); - }, true); - - // Prevent full screen mode on YouTube - container.addEventListener('dblclick', function(e) { - e.preventDefault(); - e.stopPropagation(); - }, true); - - // Prevent full screen mode on Vimeo - container.addEventListener('mousedown', function(e) { - e.preventDefault(); - e.stopPropagation(); - }, true); - } - - function setSpeed(v, speed) { - v.playbackRate = speed; - } - - function runAction(action) { - var videoTags = document.getElementsByTagName('video'); - videoTags.forEach = Array.prototype.forEach; - - videoTags.forEach(function(v) { - if (!v.classList.contains('vc-cancelled')) { - if (action === 'rewind') { - v.currentTime -= tc.settings.rewindTime; - } else if (action === 'advance') { - v.currentTime += tc.settings.advanceTime; - } else if (action === 'faster') { - // Maxium playback speed in Chrome is set to 16: - // https://code.google.com/p/chromium/codesearch#chromium/src/media/blink/webmediaplayer_impl.cc&l=64 - var s = Math.min(v.playbackRate + tc.settings.speedStep, 16); - setSpeed(v, Number(s.toFixed(2))); - } else if (action === 'slower') { - // Audio playback is cut at 0.05: - // https://code.google.com/p/chromium/codesearch#chromium/src/media/filters/audio_renderer_algorithm.cc&l=49 - var s = Math.max(v.playbackRate - tc.settings.speedStep, 0); - setSpeed(v, Number(s.toFixed(2))); - } else if (action === 'reset') { - setSpeed(v, 1.0); - } - - // show controller on keyboard input - var controller = v.parentElement - .getElementsByClassName('tc-videoController')[0]; - controller.style.visibility = 'visible'; - if (controllerAnimation != null - && controllerAnimation.playState != 'finished') { - controllerAnimation.cancel(); - } - controllerAnimation = controller.animate([ - {opacity: 0.3}, - {opacity: 0.3}, - {opacity: 0.0}, - ], { - duration: 3000, - iterations: 1, - delay: 0 - }); - controllerAnimation.onfinish = function(e) { - controller.style.visibility = 'hidden'; - } - } - }); - } + defineVideoController(); document.addEventListener('keypress', function(event) { // if lowercase letter pressed, check for uppercase key code @@ -202,15 +159,15 @@ chrome.extension.sendMessage({}, function(response) { } if (keyCode == tc.settings.rewindKeyCode) { - runAction('rewind') + runAction('rewind', document) } else if (keyCode == tc.settings.advanceKeyCode) { - runAction('advance') + runAction('advance', document) } else if (keyCode == tc.settings.fasterKeyCode) { - runAction('faster') + runAction('faster', document) } else if (keyCode == tc.settings.slowerKeyCode) { - runAction('slower') + runAction('slower', document) } else if (keyCode == tc.settings.resetKeyCode) { - runAction('reset') + runAction('reset', document) } return false; @@ -230,4 +187,52 @@ chrome.extension.sendMessage({}, function(response) { }); } } + + function runAction(action, document) { + var videoTags = document.getElementsByTagName('video'); + videoTags.forEach = Array.prototype.forEach; + + videoTags.forEach(function(v) { + if (!v.classList.contains('vc-cancelled')) { + if (action === 'rewind') { + v.currentTime -= tc.settings.rewindTime; + } else if (action === 'advance') { + v.currentTime += tc.settings.advanceTime; + } else if (action === 'faster') { + // Maximum playback speed in Chrome is set to 16: + // https://code.google.com/p/chromium/codesearch#chromium/src/media/blink/webmediaplayer_impl.cc&l=64 + var s = Math.min(v.playbackRate + tc.settings.speedStep, 16); + setSpeed(v, Number(s.toFixed(2))); + } else if (action === 'slower') { + // Audio playback is cut at 0.05: + // https://code.google.com/p/chromium/codesearch#chromium/src/media/filters/audio_renderer_algorithm.cc&l=49 + var s = Math.max(v.playbackRate - tc.settings.speedStep, 0); + setSpeed(v, Number(s.toFixed(2))); + } else if (action === 'reset') { + setSpeed(v, 1.0); + } + + // show controller on keyboard input + var controller = v.parentElement + .getElementsByClassName('tc-videoController')[0]; + controller.style.visibility = 'visible'; + if (controllerAnimation != null + && controllerAnimation.playState != 'finished') { + controllerAnimation.cancel(); + } + controllerAnimation = controller.animate([ + {opacity: 0.3}, + {opacity: 0.3}, + {opacity: 0.0}, + ], { + duration: 3000, + iterations: 1, + delay: 0 + }); + controllerAnimation.onfinish = function(e) { + controller.style.visibility = 'hidden'; + } + } + }); + } }); From ed79c8e860d752e0af9c1db6c57a6745ca28c93d Mon Sep 17 00:00:00 2001 From: George Ogata Date: Mon, 18 Jan 2016 03:56:42 -0500 Subject: [PATCH 2/2] Apply controller to videos in all accessible iframes. --- inject.js | 102 +++++++++++++++++++++++++++++--------------------- manifest.json | 3 ++ 2 files changed, 62 insertions(+), 43 deletions(-) diff --git a/inject.js b/inject.js index 9453b9b..565c95f 100644 --- a/inject.js +++ b/inject.js @@ -16,22 +16,18 @@ chrome.extension.sendMessage({}, function(response) { }; var controllerAnimation; - var readyStateCheckInterval; chrome.storage.sync.get(tc.settings, function(storage) { - tc.settings.speed = Number(storage.speed); - tc.settings.speedStep = Number(storage.speedStep); - tc.settings.rewindTime = Number(storage.rewindTime); - tc.settings.advanceTime = Number(storage.advanceTime); - tc.settings.resetKeyCode = Number(storage.resetKeyCode); - tc.settings.rewindKeyCode = Number(storage.rewindKeyCode); - tc.settings.slowerKeyCode = Number(storage.slowerKeyCode); - tc.settings.fasterKeyCode = Number(storage.fasterKeyCode); - tc.settings.advanceKeyCode = Number(storage.advanceKeyCode); - tc.settings.rememberSpeed = Boolean(storage.rememberSpeed); - - readyStateCheckInterval = setInterval(initializeVideoSpeed, 10); - } - ); + tc.settings.speed = Number(storage.speed); + tc.settings.speedStep = Number(storage.speedStep); + tc.settings.rewindTime = Number(storage.rewindTime); + tc.settings.advanceTime = Number(storage.advanceTime); + tc.settings.resetKeyCode = Number(storage.resetKeyCode); + tc.settings.rewindKeyCode = Number(storage.rewindKeyCode); + tc.settings.slowerKeyCode = Number(storage.slowerKeyCode); + tc.settings.fasterKeyCode = Number(storage.fasterKeyCode); + tc.settings.advanceKeyCode = Number(storage.advanceKeyCode); + tc.settings.rememberSpeed = Boolean(storage.rememberSpeed); + }); function defineVideoController() { tc.videoController = function(target) { @@ -68,16 +64,18 @@ chrome.extension.sendMessage({}, function(response) { } tc.videoController.prototype.initializeControls = function() { - var fragment = this.document.createDocumentFragment(); - var container = this.document.createElement('div'); - var speedIndicator = this.document.createElement('span'); + var document = this.document; - var controls = this.document.createElement('span'); - var fasterButton = this.document.createElement('button'); - var slowerButton = this.document.createElement('button'); - var rewindButton = this.document.createElement('button'); - var advanceButton = this.document.createElement('button'); - var hideButton = this.document.createElement('button'); + var fragment = document.createDocumentFragment(); + var container = document.createElement('div'); + var speedIndicator = document.createElement('span'); + + var controls = document.createElement('span'); + var fasterButton = document.createElement('button'); + var slowerButton = document.createElement('button'); + var rewindButton = document.createElement('button'); + var advanceButton = document.createElement('button'); + var hideButton = document.createElement('button'); rewindButton.innerHTML = '«'; fasterButton.textContent = '+'; @@ -107,13 +105,13 @@ chrome.extension.sendMessage({}, function(response) { container.addEventListener('click', function(e) { if (e.target === slowerButton) { - runAction('slower', this.document) + runAction('slower', document) } else if (e.target === fasterButton) { - runAction('faster', this.document) + runAction('faster', document) } else if (e.target === rewindButton) { - runAction('rewind', this.document) + runAction('rewind', document) } else if (e.target === advanceButton) { - runAction('advance', this.document) + runAction('advance', document) } else if (e.target === hideButton) { container.nextSibling.classList.add('vc-cancelled') container.remove(); @@ -135,17 +133,27 @@ chrome.extension.sendMessage({}, function(response) { e.stopPropagation(); }, true); } - - function setSpeed(v, speed) { - v.playbackRate = speed; - } } - function initializeVideoSpeed() { - if (document.readyState === 'complete') { - clearInterval(readyStateCheckInterval); + function initializeWhenReady(document) { + var readyStateCheckInterval = setInterval(function() { + if (document.readyState === 'complete') { + clearInterval(readyStateCheckInterval); + initializeNow(document); + } + }, 10); + } - defineVideoController(); + function initializeNow(document) { + if (document === window.document) { + defineVideoController(); + } else { + var link = document.createElement('link'); + link.href = chrome.extension.getURL('inject.css'); + link.type = 'text/css'; + link.rel = 'stylesheet'; + document.head.appendChild(link); + } document.addEventListener('keypress', function(event) { // if lowercase letter pressed, check for uppercase key code @@ -180,12 +188,18 @@ chrome.extension.sendMessage({}, function(response) { } }); + var forEach = Array.prototype.forEach; var videoTags = document.getElementsByTagName('video'); - videoTags.forEach = Array.prototype.forEach; - videoTags.forEach(function(video) { - var control = new tc.videoController(video); + forEach.call(videoTags, function(video) { + 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); }); - } } function runAction(action, document) { @@ -202,14 +216,14 @@ chrome.extension.sendMessage({}, function(response) { // Maximum playback speed in Chrome is set to 16: // https://code.google.com/p/chromium/codesearch#chromium/src/media/blink/webmediaplayer_impl.cc&l=64 var s = Math.min(v.playbackRate + tc.settings.speedStep, 16); - setSpeed(v, Number(s.toFixed(2))); + v.playbackRate = Number(s.toFixed(2)); } else if (action === 'slower') { // Audio playback is cut at 0.05: // https://code.google.com/p/chromium/codesearch#chromium/src/media/filters/audio_renderer_algorithm.cc&l=49 var s = Math.max(v.playbackRate - tc.settings.speedStep, 0); - setSpeed(v, Number(s.toFixed(2))); + v.playbackRate = Number(s.toFixed(2)); } else if (action === 'reset') { - setSpeed(v, 1.0); + v.playbackRate = 1.0; } // show controller on keyboard input @@ -235,4 +249,6 @@ chrome.extension.sendMessage({}, function(response) { } }); } + + initializeWhenReady(document); }); diff --git a/manifest.json b/manifest.json index 2f05aea..2cbbcaf 100755 --- a/manifest.json +++ b/manifest.json @@ -31,5 +31,8 @@ "css": [ "inject.css" ], "js": [ "inject.js" ] } + ], + "web_accessible_resources": [ + "inject.css" ] }