From 4ac2e458f548681976346615210cb6b1cdbe6576 Mon Sep 17 00:00:00 2001 From: jedawson Date: Thu, 6 Feb 2020 16:07:33 -0600 Subject: [PATCH] Apple tv plus (#541) * add support for traversing shadowRoot's; enables apple tv support. --- inject.js | 95 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 22 deletions(-) diff --git a/inject.js b/inject.js index b15455e..0424f38 100644 --- a/inject.js +++ b/inject.js @@ -180,7 +180,7 @@ var observer=new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && (mutation.attributeName === 'src' || mutation.attributeName === 'currentSrc')){ - var controller = document.querySelector(`div[data-vscid="${this.id}"]`); + var controller = getController(this.id) if(!controller){ return; } @@ -268,6 +268,9 @@ // 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 @@ -333,6 +336,32 @@ 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 @@ -380,7 +409,7 @@ } // Ignore keydown event if typing in a page without vsc - if (!document.querySelector(".vsc-controller")) { + if (!getShadow(document.body).filter(x => x.tagName == 'vsc-controller')) { return false; } @@ -424,27 +453,48 @@ // Process the DOM nodes lazily requestIdleCallback(_ => { mutations.forEach(function(mutation) { - forEach.call(mutation.addedNodes, function(node) { - if (typeof node === "function") - return; - checkForVideo(node, node.parentNode || mutation.target, true); - }); - forEach.call(mutation.removedNodes, function(node) { - if (typeof node === "function") - return; - checkForVideo(node, node.parentNode || mutation.target, false); - }); + 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, { childList: true, subtree: true }); + 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); }); @@ -459,9 +509,11 @@ function runAction(action, document, value, e) { if (tc.settings.audioBoolean) { - var mediaTags = document.querySelectorAll('video,audio'); + var mediaTags = getShadow(document.body).filter(x => { + return x.tagName == 'AUDIO' || x.tagName == 'VIDEO' + }); } else { - var mediaTags = document.querySelectorAll('video'); + var mediaTags = getShadow(document.body).filter(x => x.tagName == 'VIDEO'); } mediaTags.forEach = Array.prototype.forEach; @@ -469,13 +521,12 @@ // 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 = document.querySelector(`div[data-vscid="${id}"]`); - - // Don't change video speed if the video has a different controller + var controller = getController(id) + // Don't change video speed if the video has a different controller if (e && !(targetController == controller)) { return; } @@ -564,13 +615,13 @@ 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');