From c73851554b4179186b2db1fe97dbc5879a657e57 Mon Sep 17 00:00:00 2001 From: fire332 <96039230+fire332@users.noreply.github.com> Date: Wed, 18 Dec 2024 03:34:10 -0800 Subject: [PATCH] fix stretched/offset shorts video --- src/screensaver-fix.ts | 87 ++++++++++++++++++++++++++++++++++++++++++ src/userScript.js | 38 +----------------- 2 files changed, 89 insertions(+), 36 deletions(-) create mode 100644 src/screensaver-fix.ts diff --git a/src/screensaver-fix.ts b/src/screensaver-fix.ts new file mode 100644 index 00000000..b4874717 --- /dev/null +++ b/src/screensaver-fix.ts @@ -0,0 +1,87 @@ +/** + * On webOS, when a video element doesn't perfectly fill + * the entire screen, the screensaver can be kick in. + */ + +import { waitForChildAdd } from './utils'; + +/** + * document.querySelector but waits for the Element to be added if it doesn't already exist. + */ +async function requireElement( + cssSelectors: string, + expected: E +): Promise> { + const alreadyPresent = document.querySelector(cssSelectors); + if (alreadyPresent) { + if (!(alreadyPresent instanceof expected)) throw new Error(); + + // Cast required due to narrowing limitations. + // https://github.com/microsoft/TypeScript/issues/55241 + return alreadyPresent as InstanceType; + } + + const result = await waitForChildAdd( + document.body, + (node): node is Element => + node instanceof Element && node.matches(cssSelectors), + true + ); + + if (!(result instanceof expected)) throw new Error(); + return result as InstanceType; +} + +function isPlayerHidden(video: HTMLVideoElement) { + // Youtube uses display none sometimes along with a negative top to hide the HTMLVideoElement. + return video.style.display == 'none' || video.style.top.startsWith('-'); +} + +function isWatchPage() { + return document.body.classList.contains('WEB_PAGE_TYPE_WATCH'); +} + +const playerCtrlObs = new MutationObserver((mutations, obs) => { + // Only watch page has a full-screen player. + if (!isWatchPage()) { + obs.disconnect(); + return; + } + + const video = mutations[0]?.target; + if (!(video instanceof HTMLVideoElement)) throw new Error(); + const style = video.style; + + // Not sure if there will be a race condition so just in case. + if (isPlayerHidden(video)) return; + + const targetWidth = `${window.innerWidth}px`; + const targetHeight = `${window.innerHeight}px`; + const targetLeft = '0px'; + const targetTop = style.top; + + /** + * Check to see if identical before assignment as some webOS versions will trigger a mutation + * mutation event even if the assignment effectively does nothing, leading to an infinite loop. + */ + style.width !== targetWidth && (style.width = targetWidth); + style.height !== targetHeight && (style.height = targetHeight); + style.left !== targetLeft && (style.left = targetLeft); + style.top !== targetTop && (style.top = targetTop); +}); + +const bodyAttrObs = new MutationObserver(async () => { + if (!isWatchPage()) return; + + // Youtube TV re-uses the same video element for everything. + const video = await requireElement('video', HTMLVideoElement); + playerCtrlObs.observe(video, { + attributes: true, + attributeFilter: ['style'] + }); +}); + +bodyAttrObs.observe(document.body, { + attributes: true, + attributeFilter: ['class'] +}); diff --git a/src/userScript.js b/src/userScript.js index df0f41c4..6baae086 100644 --- a/src/userScript.js +++ b/src/userScript.js @@ -1,7 +1,7 @@ import 'whatwg-fetch'; import './domrect-polyfill'; -import { handleLaunch, waitForChildAdd } from './utils'; +import { handleLaunch } from './utils'; document.addEventListener( 'webOSRelaunch', @@ -16,38 +16,4 @@ import './adblock.js'; import './shorts.js'; import './sponsorblock.js'; import './ui.js'; - -// This IIFE is to keep the video element fill the entire window so that screensaver doesn't kick in. -(async () => { - /** @type {HTMLVideoElement} */ - const video = await waitForChildAdd( - document.body, - (node) => node instanceof HTMLVideoElement, - false - ); - - const playerCtrlObs = new MutationObserver(() => { - const style = video.style; - - const targetWidth = `${window.innerWidth}px`; - const targetHeight = `${window.innerHeight}px`; - const targetLeft = '0px'; - // YT uses a negative top to hide player when not in use. Don't know why but let's not affect it. - const targetTop = - style.top === `-${window.innerHeight}px` ? style.top : '0px'; - - /** - * Check to see if identical before assignment as some webOS versions will trigger a mutation - * mutation event even if the assignment effectively does nothing, leading to an infinite loop. - */ - style.width !== targetWidth && (style.width = targetWidth); - style.height !== targetHeight && (style.height = targetHeight); - style.left !== targetLeft && (style.left = targetLeft); - style.top !== targetTop && (style.top = targetTop); - }); - - playerCtrlObs.observe(video, { - attributes: true, - attributeFilter: ['style'] - }); -})(); +import './screensaver-fix';