diff --git a/README.md b/README.md index 7552190..0402c9f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # The science of accelerated playback -**TL;DR: faster playback translates to quick progress, better engagement, and retention.** +**TL;DR: faster playback translates to better engagement and retention.** -Average adult reads prose text at [250 to 300 words per minute](http://www.paperbecause.com/PIOP/files/f7/f7bb6bc5-2c4a-466f-9ae7-b483a2c0dca4.pdf) (wpm). By contrast, the average rate of speech for English speakers is ~150 wpm, with slide presentations often closer to 100 wpm. As a result, when given the choice, many viewers [speed up video playback to ~1.3~1.5 its recorded rate](http://research.microsoft.com/en-us/um/redmond/groups/coet/compression/chi99/paper.pdf) to compensate for the difference. +Average adult reads prose text at [250 to 300 words per minute](http://www.paperbecause.com/PIOP/files/f7/f7bb6bc5-2c4a-466f-9ae7-b483a2c0dca4.pdf) (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://xenia.media.mit.edu/~barons/html/avios92.html#beasleyalteredspeech) if they are forced to return to normal rate of presentation. +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 @@ -24,11 +24,7 @@ Once the extension is installed simply navigate to any page that offers HTML5 vi * **X** - advance video by 10 seconds. * **V** - show/hide the controller. -Note that you can customize these shortcuts in the extension settings page. Also, a few tips for enabling and forcing HTML5 video: - - * YouTube: make sure you [enable the HTML5 opt-in experiment](http://www.youtube.com/html5). - * If you're adventurous, try disabling the Flash plugin in Chrome in chrome://plugins/ - * If viewing a video on Wistia, right click to switch to HTML5 video, refresh the page, and the controls will appear. +_Note: you can customize these shortcut keys in the extension settings page._ ### FAQ diff --git a/inject.css b/inject.css index d11b58d..935e7fe 100644 --- a/inject.css +++ b/inject.css @@ -29,6 +29,18 @@ top: 40px; } +/* Google Photos player */ +/* Inline preview doesn't have any additional hooks, relying on Aria label */ +a[aria-label^="Video"] .vsc-controller { + position: relative; + top: 35px; +} +/* Google Photos full-screen view */ +#player:not(.ytd-watch) .html5-video-container .vsc-controller { + position: relative; + top: 50px; +} + /* Netflix player */ #netflix-player:not(.player-cinema-mode) .vsc-controller { position: relative; diff --git a/inject.js b/inject.js index 064aac7..f06d2cd 100644 --- a/inject.js +++ b/inject.js @@ -50,13 +50,17 @@ chrome.runtime.sendMessage({}, function(response) { function defineVideoController() { tc.videoController = function(target, parent) { + if (target.dataset['vscid']) { + return; + } + this.video = target; this.parent = target.parentElement || parent; this.document = target.ownerDocument; this.id = Math.random().toString(36).substr(2, 9); if (!tc.settings.rememberSpeed) { tc.settings.speed = 1.0; - tc.settings.resetSpeed = 1.0; + tc.settings.resetSpeed = tc.settings.fastSpeed; } this.initializeControls(); @@ -65,9 +69,6 @@ chrome.runtime.sendMessage({}, function(response) { }); target.addEventListener('ratechange', function(event) { - if (target.readyState === 0) { - return; - } var speed = this.getSpeed(); this.speedIndicator.textContent = speed; tc.settings.speed = speed; @@ -103,6 +104,10 @@ chrome.runtime.sendMessage({}, function(response) { wrapper.addEventListener('mousedown', prevent, false); wrapper.addEventListener('click', prevent, false); + if (tc.settings.startHidden) { + wrapper.classList.add('vsc-hidden'); + } + var styleElem = document.createElement('style') var shadowCSS = chrome.runtime.getURL('shadow.css') var textElem = document.createTextNode(`@import "${shadowCSS}";`) @@ -169,12 +174,26 @@ chrome.runtime.sendMessage({}, function(response) { var fragment = document.createDocumentFragment(); fragment.appendChild(wrapper); - // 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); this.video.classList.add('vsc-initialized'); this.video.dataset['vscid'] = this.id; + + switch (location.hostname) { + case 'www.amazon.com': + // insert before parent to bypass overlay + this.parent.parentElement.insertBefore(fragment, this.parent); + break; + + case 'www.facebook.com': + // set stacking context to same as parent's parent. + // + default fallthrough + this.parent.style.zIndex = 'auto'; + + 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); + } } } @@ -210,6 +229,13 @@ chrome.runtime.sendMessage({}, function(response) { } function initializeNow(document) { + // in theory, this should only run once, in practice.. + // that's not guaranteed, hence we enforce own init-once. + if (document.body.classList.contains('vsc-initialized')) { + return; + } + document.body.classList.add('vsc-initialized'); + if (document === window.document) { defineVideoController(); } else { @@ -263,9 +289,7 @@ chrome.runtime.sendMessage({}, function(response) { function checkForVideo(node, parent, added) { if (node.nodeName === 'VIDEO') { if (added) { - if (!node.dataset['vscid']) { - new tc.videoController(node, parent); - } + new tc.videoController(node, parent); } else { if (node.classList.contains('vsc-initialized')) { let id = node.dataset['vscid']; @@ -342,13 +366,7 @@ chrome.runtime.sendMessage({}, function(response) { var s = Math.max(v.playbackRate - tc.settings.speedStep, 0.0625); v.playbackRate = Number(s.toFixed(2)); } else if (action === 'reset') { - if(v.playbackRate === 1.0) { - v.playbackRate = tc.settings.resetSpeed; - } else { - tc.settings.resetSpeed = v.playbackRate; - chrome.storage.local.set({'resetSpeed': v.playbackRate}); - v.playbackRate = 1.0; - } + resetSpeed(v, 1.0); } else if (action === 'close') { v.classList.add('vsc-cancelled'); controller.remove(); @@ -358,23 +376,25 @@ chrome.runtime.sendMessage({}, function(response) { } else if (action === 'drag') { handleDrag(v, controller); } else if (action === 'fast') { - playVideoAtFastSpeed(v); + resetSpeed(v, tc.settings.fastSpeed); } } }); } - function playVideoAtFastSpeed(video) { - video.playbackRate = tc.settings.fastSpeed; + function resetSpeed(v, target) { + if (v.playbackRate === target) { + v.playbackRate = tc.settings.resetSpeed; + } else { + tc.settings.resetSpeed = v.playbackRate; + chrome.storage.local.set({'resetSpeed': v.playbackRate}); + v.playbackRate = target; + } } function handleDrag(video, controller) { const parentElement = controller.parentElement, - boundRect = parentElement.getBoundingClientRect(), - shadowController = controller.querySelector('#controller'), - drag = shadowController.querySelector('.draggable'), - offsetLeft = boundRect.left + drag.offsetLeft + drag.offsetWidth, - offsetTop = boundRect.top + drag.offsetTop + drag.offsetHeight; + shadowController = controller.querySelector('#controller'); video.classList.add('vcs-dragging'); shadowController.classList.add('dragging'); diff --git a/manifest.json b/manifest.json index 0fff214..f945fca 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "Video Speed Controller", "short_name": "videospeed", - "version": "0.4.6.1", + "version": "0.4.8", "manifest_version": 2, "description": "Speed up, slow down, advance and rewind any HTML5 video with quick shortcuts.", "homepage_url": "https://github.com/codebicycle/videospeed", @@ -26,6 +26,7 @@ "exclude_matches": [ "https://plus.google.com/hangouts/*", "https://hangouts.google.com/hangouts/*", + "https://meet.google.com/*", "https://teamtreehouse.com/*", "http://www.hitbox.tv/*" ],