/* Resize the YouTube player to 480p 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.76 // @description Make YouTube Player 480p Size // @match https://www.youtube.com/* // @match https://www.youtu.be/* // @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== (function() { "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) { let value = getPref(preference); if (value === null) { setPref(preference, new_value); value = new_value; } return value; } //================================================================== initPref("yt-resize", false); //================================================================== // Global Variables var maxWidth = 480; // Max Width of Video var aspectRatio = 16 / 9; // Aspect Ratio var shortcutKey = "r"; // Shortcut Key var smallerCss = `#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) { max-width:calc(${maxWidth}px*${aspectRatio}) !important; }`; var ytresizeCss = `.ytp-big-mode .ytp-chrome-controls .ytp-resize-button { display:none !important; }`; //================================================================== let hasStarted = false; const isDOMReady = () => ["complete", "loaded", "interactive"].includes(document.readyState); const startOnce = () => { if (!hasStarted) { PageObserver(); checkURL(); hasStarted = true; } }; if (isDOMReady()) { startOnce(); } else { document.addEventListener("DOMContentLoaded", startOnce, { once: true }); } //================================================================== function PageObserver() { const observer = new MutationObserver(checkURL); const urlChangeObserver = () => { const e = document.querySelector("title"); if (e) { observer.observe(e, { attributes: true, characterData: true, childList: true }); return; } setTimeout(urlChangeObserver, 250); }; urlChangeObserver(); } //================================================================== function waitElement(selector) { const element = document.querySelector(selector); const targetNode = document.body; const config = { childList: true, subtree: true }; return new Promise((resolve) => { const observer = new MutationObserver((mutationsList) => { for (let mutation of mutationsList) { if (mutation.type === "childList" && mutation.addedNodes.length > 0) { observer.disconnect(); return resolve(element); } } }); observer.observe(targetNode, config); }); } //================================================================== function checkURL() { let checkStart = false; function checkCss() { if (!document.getElementById("yt-css")) { location.reload(); } } waitElement("#player-container-outer").then((elm) => { if (window.location.pathname.includes("watch")) { new Promise((resolve) => { if (!checkStart) { startMethods(); checkStart = true; } }).then(() => { checkCss(); }); } }); } //================================================================== function startMethods() { new Promise((resolve) => { sizeObserver(); // Size Observer window.addEventListener("transitionend", () => { createResize(); }); resolve(); }).then(() => { if (getPref("yt-resize")) { addCss(smallerCss, "small-player"); } addCss(ytresizeCss, "yt-css"); controlResize(); // Create Resize Button viewObserver(); // Video Container Observer }); } //================================================================== function isCentered(element1, element2) { const box1 = element1.getBoundingClientRect(); const box2 = element2.getBoundingClientRect(); const center1 = { x: box1.left + box1.width / 2, y: box1.top + box1.height / 2 }; const center2 = { x: box2.left + box2.width / 2, y: box2.top + box2.height / 2 }; const horizontalDistance = Math.abs(center1.x - center2.x); const verticalDistance = Math.abs(center1.y - center2.y); return horizontalDistance <= 1 && verticalDistance <= 1; } function addCss(cssString, id) { const css = document.createElement("style"); css.type = "text/css"; css.id = id; css.innerHTML = cssString; document.head.appendChild(css); } //================================================================== /*Resize Video Container*/ function createResize() { const element = document.querySelector("ytd-app"); element.dispatchEvent( new CustomEvent("yt-action", { bubbles: !0, cancelable: !0, composed: !0, detail: { actionName: "yt-window-resized", disableBroadcast: false, optionalAction: true, returnValue: [] } }) ); return; } /*Viewport Observer*/ function viewObserver() { let movie_player = document.querySelector(".html5-video-player"); let video = document.querySelector("video"); let isCenteredMoviePlayer = isCentered(video, movie_player); let resizeObserver = new ResizeObserver((entries) => { window.requestAnimationFrame(() => { if (!Array.isArray(entries) || !entries.length) { // Check Animation Frame return; } for (let entry of entries) { if (!isCenteredMoviePlayer) { if (entry.contentRect.height !== maxWidth) { createResize(); } } } }); for (let entry of entries) { 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 callback = function(mutationsList, observer) { for (let mutation of mutationsList) { if (mutation.removedNodes.length >= 1 && mutation.removedNodes[0].id == "small-player") { setPref("yt-resize", false); // Set Resize To False controlResize(); // Change Button Icon createResize(); // Resize Video Container } else if (mutation.addedNodes.length >= 1 && mutation.addedNodes[0].id == "small-player") { setPref("yt-resize", true); // Set Resize To True controlResize(); // Change Button Icon createResize(); // Resize Video Container } } }; const observer = new MutationObserver(callback); observer.observe(targetNode, config); } //================================================================== function showResizeButtonTooltip(btn, show = true) { let tooltipTopOffset = 62; // Height Above The Button For The Tooltip const buttonRect = btn.getBoundingClientRect(); // Get Button Position const tooltipHorizontalCenter = buttonRect.left + buttonRect.width / 2; // Tooltip Horizontal Center const tooltipTop = buttonRect.top + buttonRect.height / 2 - tooltipTopOffset; // Tooltip Top const tooltip = document.getElementById("ytd-resize-tt") || createTooltip(); const tooltipText = tooltip.querySelector("#ytd-resize-tt-text"); if (show) { // 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 { // Hide 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; } } //================================================================== /*Resize Button Script*/ function buttonScript() { let splayer = document.getElementById("small-player"); if (document.head.contains(splayer)) { document.head.removeChild(splayer); } else { addCss(smallerCss, "small-player"); } return; } function setButton(btn, path) { var pathData = {}; var ariaLabel = ""; var title = ""; if (!getPref("yt-resize")) { pathData.d = `M 19 23 L 11 15 L 11 23 Z M 29 25 L 29 10.98 C 29 9.88 28.1 9 27 9 L 9 9 C 7.9 9 7 9.88 7 10.98 L 7 25 C 7 26.1 7.9 27 9 27 L 27 27 C 28.1 27 29 26.1 29 25 L 29 25 Z M 27 25.02 L 9 25.02 L 9 10.97 L 27 10.97 L 27 25.02 L 27 25.02 Z`; ariaLabel = `Resize mode (${shortcutKey})`; title = `Resize mode (${shortcutKey})`; } else { pathData.d = `M 25 21 L 25 13 L 17 13 Z M 29 25 L 29 10.98 C 29 9.88 28.1 9 27 9 L 9 9 C 7.9 9 7 9.88 7 10.98 L 7 25 C 7 26.1 7.9 27 9 27 L 27 27 C 28.1 27 29 26.1 29 25 L 29 25 Z M 27 25.02 L 9 25.02 L 9 10.97 L 27 10.97 L 27 25.02 L 27 25.02 Z`; ariaLabel = `Default view (${shortcutKey})`; title = `Default view (${shortcutKey})`; } path.setAttribute("d", pathData.d); btn.setAttribute("aria-label", ariaLabel); btn.setAttribute("title", title); } function createButton() { var abtn = document.querySelector(".ytp-right-controls"); var btn = document.createElement("button"); var path = document.createElement("path"); var clickEvent = new Event("click", { bubbles: false }); /*Start Create SVG*/ var svg = document.createElement("svg"); svg.setAttribute("height", "100%"); svg.setAttribute("version", "1.1"); svg.setAttribute("viewBox", "0 0 36 36"); svg.setAttribute("width", "100%"); var use = document.createElement("use"); use.setAttribute("class", "ytp-svg-shadow"); setButton(btn, path); // Decide Which Button path.setAttribute("fill", "#fff"); path.setAttribute("fill-rule", "evenodd"); svg.appendChild(use); svg.appendChild(path); const btnContent = svg.outerHTML; /*Finished Create SVG*/ 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"); abtn.insertBefore(btn, abtn.lastChild.previousSibling); /*Tooltip Event Handlers*/ const showTooltip = (event) => { const isMouseOver = ["mouseover", "focus"].includes(event.type); showResizeButtonTooltip(btn, isMouseOver); }; btn.addEventListener("click", function(e) { e.stopPropagation(); e.preventDefault(); buttonScript(); }, clickEvent); btn.addEventListener("mouseover", showTooltip); btn.addEventListener("mouseout", showTooltip); btn.addEventListener("focus", showTooltip); btn.addEventListener("blur", showTooltip); } /*Create Resize Button*/ function controlResize() { var buttonExists = document.getElementById("ytp-resize-button"); if (!buttonExists) { window.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 == shortcutKey.toLowerCase() || e.key == shortcutKey.toUpperCase()) { e.stopPropagation(); e.preventDefault(); buttonScript(); } return; }); createButton(); } else { setButton(buttonExists, buttonExists.querySelector("path")); } } //================================================================== })();