/* Resize the YouTube player to 480px size. Copyright (C) 2023 Runio This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see */ // ==UserScript== // @name YouTube Sizer // @author Runio // @namespace namespace_runio // @version 1.36 // @description Make YouTube Player 480px Size // @match https://www.youtube.com/* // @match https://youtu.be/* // @exclude https://www.youtube.com/ // @exclude https://www.youtube.com/tv* // @exclude https://www.youtube.com/embed/* // @exclude https://www.youtube.com/live_chat* // @exclude https://www.youtube.com/shorts* // @run-at document-end // @grant GM_setValue // @grant GM_getValue // @icon https://i.imgur.com/KJeLd60.png // @license GPL-3.0+ // @noframes // @downloadURL none // ==/UserScript== "use strict"; //================================================================== //Local Storage Functions if (window.frameElement) throw new Error("Stopped JavaScript."); function setPref(preference, new_value) { GM_setValue(preference, new_value); } function getPref(preference) { return GM_getValue(preference); } function initPref(preference, new_value) { var value = getPref(preference); if (value == null) { setPref(preference, new_value); value = new_value; } return value; } //================================================================== initPref("yt-resize", false); //================================================================== // Global Booleans var scrub_bool = false; var buttonDone = false; var pref_done = false; var resize_done = false; //================================================================== // Global Variables var max_width = 480; // Max Width of Video var aspect_ratio = 16 / 9; // Aspect Ratio var shortcut_key = "r"; // Shortcut Key var smaller_string = "#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) {max-width: calc(" + max_width + "px * " + aspect_ratio + ") !important;"; var css_ytresize = ".ytp-big-mode .ytp-chrome-controls .ytp-resize-button {display: none !important;} .ytp-chrome-bottom {max-width: calc(100% - 24px);width: calc(100% - 24px);}"; //================================================================== window.addEventListener("yt-navigate-start", () => { startScript(); }); window.addEventListener("yt-page-data-updated", () => { startScript(); }); //================================================================== // Start Script function startScript() { 'use strict'; const isDocumentReady = ["complete", "loaded", "interactive"].includes(document.readyState); if (isDocumentReady) { startMethods(); } else { document.addEventListener("DOMContentLoaded", startMethods); } } //================================================================== function startMethods() { // Global Video Player Variables /* global ytd_video, movie_player, outer, video, ytd_flexy */ window.ytd_video = document.getElementById("ytd-player"); window.movie_player = document.getElementById("movie_player"); window.outer = document.getElementById("player-container-outer"); window.video = document.getElementsByTagName("video")[0]; window.ytd_flexy = document.getElementsByTagName("ytd-watch-flexy")[0]; if (!pref_done) { if (getPref("yt-resize")) { addCss(smaller_string, "small-player"); } autoResizeVideo(); addCss(css_ytresize, "yt-css"); pref_done = true; } } //================================================================== //Scrubber Event Listener function getComputedTranslateXY(obj) { const transArr = []; if (!window.getComputedStyle) return; const style = getComputedStyle(obj), transform = style.transform || style.webkitTransform || style.mozTransform; let mat = transform.match(/^matrix3d\((.+)\)$/); if (mat) return parseFloat(mat[1].split(', ')[13]); mat = transform.match(/^matrix\((.+)\)$/); mat ? transArr.push(parseFloat(mat[1].split(', ')[4])) : 0; mat ? transArr.push(parseFloat(mat[1].split(', ')[5])) : 0; return transArr; } function scrubListener() { var elem = document.querySelector(".ytp-progress-bar-container > .ytp-progress-bar"); elem.addEventListener('timeupdate', function(elem) { var event = new CustomEvent('scrubber'); elem.dispatchEvent(event); }); elem.addEventListener('scrubber', function() { var scrub = document.querySelector(".ytp-progress-bar > .ytp-scrubber-container"); var progress = document.querySelector(".ytp-progress-list > .ytp-hover-progress"); var numericValue = window.getComputedStyle(progress, null).getPropertyValue('left').match(/\d+/); if (scrub) { if (Math.floor(getComputedTranslateXY(scrub)[0]) == parseInt(numericValue[0])) { scrub_bool = true; } return (scrub_bool); } else { return false; } }); } //================================================================== function isInViewport(video, element) { var rect = element.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom >= video.clientHeight && rect.right >= video.clientWidth ); } function isCentered(element1, element2) { var rect = element1.getBoundingClientRect(); var rect2 = element2.getBoundingClientRect(); var centered = { outer: rect.left + rect.width / 2, inner: rect2.left + rect2.width / 2, }; return ( Math.floor(centered.outer) == Math.floor(centered.inner) ); } function addCss(cssString, id) { const existingStyle = document.getElementById(id); if (existingStyle) { existingStyle.innerHTML = cssString; } else { const newCss = document.createElement('style'); newCss.type = 'text/css'; newCss.setAttribute('id', id); newCss.innerHTML = cssString; document.head.appendChild(newCss); } } function injectJs(link) { var scr = document.createElement('script'); scr.type = "text/javascript"; scr.innerHTML = link; document.getElementsByTagName('head')[0].appendChild(scr); } function showResizeButtonTooltip(btn, show = true) { const buttonRect = btn.getBoundingClientRect(); const tooltipHorizontalCenter = buttonRect.left + buttonRect.width / 2; let tooltipTopOffset = 64; if (document.fullscreenElement) { tooltipTopOffset = 75; } const tooltipTop = buttonRect.top + buttonRect.height / 2 - tooltipTopOffset; const tooltip = document.getElementById('ytd-resize-tt') || createTooltip(); const tooltipText = tooltip.querySelector('#ytd-resize-tt-text'); if (show) { tooltip.style.top = `${tooltipTop}px`; tooltipText.textContent = btn.getAttribute('aria-label'); tooltip.style.removeProperty('display'); const tooltipWidth = tooltip.getBoundingClientRect().width; tooltip.style.left = `${tooltipHorizontalCenter - tooltipWidth / 2}px`; btn.removeAttribute('title'); } else { tooltip.style.setProperty('display', 'none'); tooltipText.textContent = ''; btn.setAttribute('title', btn.getAttribute('aria-label')); } function createTooltip() { const htmlPlayer = document.querySelector('.html5-video-player'); const tooltip = document.createElement('div'); const tooltipTextWrapper = document.createElement('div'); const tooltipText = document.createElement('span'); tooltip.setAttribute('class', 'ytp-tooltip ytp-bottom'); tooltip.setAttribute('id', 'ytd-resize-tt'); tooltip.style.setProperty('position', 'fixed'); tooltipTextWrapper.setAttribute('class', 'ytp-tooltip-text-wrapper'); tooltipText.setAttribute('class', 'ytp-tooltip-text'); tooltipText.setAttribute('id', 'ytd-resize-tt-text'); tooltip.appendChild(tooltipTextWrapper); tooltipTextWrapper.appendChild(tooltipText); htmlPlayer.appendChild(tooltip); return tooltip; } } function createResize() { var ytd = { height: movie_player.clientHeight, width: movie_player.clientWidth, }; ytd_video.player_.setInternalSize(ytd.width, ytd.height); return; } function buttonScript() { var key_script = ` function keyPress() { document.addEventListener("keydown", function(e) { if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return; if (/^(?:input|textarea|select|button)$/i.test(e.target.tagName)) return; if (/(?:contenteditable-root)/i.test(e.target.id)) return; if (e.key == "` + shortcut_key.toLowerCase() + `" || e.key == "` + shortcut_key.toUpperCase() + `") { e.preventDefault(); e.stopPropagation(); document.getElementById("ytd-player").focus(); resizeScript(); } return; }); document.querySelector(".ytp-right-controls > .ytp-resize-button.ytp-button").onclick = function foo() { resizeScript(); }; } function resizeScript() { let splayer = document.getElementById("small-player"); let ytvideo = document.getElementById("ytd-player"); let ytplayer = document.getElementById("movie_player"); if (document.head.contains(splayer)) { splayer.parentNode.removeChild(splayer); ytvideo.player_.setInternalSize(ytplayer.clientWidth, ytplayer.clientHeight); } else { var head = document.getElementsByTagName("head")[0]; var newCss = document.createElement("style"); newCss.type = "text/css"; newCss.setAttribute("id", "small-player"); newCss.innerHTML = "#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) {max-width: calc(` + max_width + `px * ` + aspect_ratio + `) !important;}"; head.appendChild(newCss); ytvideo.player_.setInternalSize(ytplayer.clientWidth, ytplayer.clientHeight); } } if (!key_done) { keyPress(); key_done = true; }`; injectJs(key_script); } /*Create Resize Button*/ function controlResize() { if (!buttonDone) { const abtn = document.querySelector("#movie_player > div.ytp-chrome-bottom > div.ytp-chrome-controls > div.ytp-right-controls"); const btn = document.createElement("button"); const btnContent = ` `; btn.innerHTML = btnContent; btn.classList.add("ytp-resize-button", "ytp-button"); btn.setAttribute("id", "ytp-resize-button"); btn.setAttribute("data-tooltip-target-id", "ytp-resize-button"); btn.setAttribute("aria-label", `Resize (${shortcut_key})`); btn.setAttribute("title", `Resize (${shortcut_key})`); abtn.insertBefore(btn, abtn.lastChild.previousSibling); buttonScript(); // Inject button script /*Tooltip Event Handlers*/ const showTooltip = (event) => { const isMouseOver = ["mouseover", "focus"].includes(event.type); showResizeButtonTooltip(btn, isMouseOver); }; btn.addEventListener("mouseover", showTooltip); btn.addEventListener("mouseout", showTooltip); btn.addEventListener("focus", showTooltip); btn.addEventListener("blur", showTooltip); buttonDone = true; } } /*Viewport Observer*/ function viewObserver() { let resizeObserver = new ResizeObserver((entries) => { window.requestAnimationFrame(() => { if (!Array.isArray(entries) || !entries.length) { // Check Animation Frame return; } entries.forEach(entry => { const isInViewportYTD = isInViewport(video, ytd_flexy); const isCenteredMoviePlayer = isCentered(video, movie_player); const isInViewportMoviePlayer = isInViewport(video, movie_player); if (isInViewportYTD && !isCenteredMoviePlayer && (!isInViewportMoviePlayer || !scrub_bool)) { const { height } = entry.contentRect; if (height !== max_width) { createResize(); } } }); }); entries.forEach(entry => console.log(`Player Size: ${entry.contentRect.height} x ${entry.contentRect.width}`)); }); // observe the given element for changes resizeObserver.observe(video); } /*Saves Size Setting*/ function sizeObserver() { const targetNode = document.head; const config = { attributes: true, childList: true, subtree: true }; const handleMutation = (mutation) => { const smallPlayer = document.querySelector('#small-player'); if (mutation.removedNodes.length >= 1 && mutation.removedNodes[0] === smallPlayer) { setPref('yt-resize', false); } else if (mutation.addedNodes.length >= 1 && mutation.addedNodes[0] === smallPlayer) { setPref('yt-resize', true); } }; const observer = new MutationObserver((mutationsList, observer) => { mutationsList.forEach(handleMutation); }); observer.observe(targetNode, config); } function autoResizeVideo() { if (outer !== "null") { injectJs('let key_done = false;'); // Global Variable scrubListener(); // Add Scrubber Listener sizeObserver(); // Size Observer viewObserver(); // Resize Observer window.addEventListener("yt-action", () => { // Adds Resize Button if (window.location.href.indexOf('youtube.com/watch') !== -1) { if (video !== "null" && !resize_done) { controlResize(); resize_done = true; } } }); video.addEventListener("canplay", () => { // Resize on video load setTimeout(function() { createResize(); }, 500); }); window.addEventListener("fullscreenchange", () => { if (!document.fullscreenElement) { // Check if leaving fullscreen createResize(); } }); } else { alert("player-container-outer not found"); } }