/* 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.60 // @description Make YouTube Player 480px 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; } //================================================================== // 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; } .ytp-chrome-bottom { max-width: calc(100% - 24px);width: calc(100% - 24px) !important; }`; //================================================================== initPref("yt-resize", false); // Run When Navigating Page window.addEventListener("yt-navigate", () => { eventFired = true; if (window.location.href.includes("/watch?v=")) { startMethods(); } }, { once: true }); // Run When Page Loaded Direct let eventFired = false; setTimeout(function() { if (!eventFired) { if (window.location.href.includes("/watch?v=")) { startMethods(); } } }, 1000); //================================================================== function startMethods() { const outer = document.getElementById("player-container-outer"); if (outer !== "null") { sizeObserver(); // Size Observer if (getPref("yt-resize")) { addCss(smallerCss, "small-player"); } addCss(ytresizeCss, "yt-css"); controlResize(); // Create Resize Button viewInterval(); // Resize Interval } else { alert("player-container-outer not found"); } } //================================================================== function isInViewport(element2, element1) { let rect = element1.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom >= element2.clientHeight && rect.right >= element2.clientWidth ); } function isCentered(element1, element2) { let rect = element1.getBoundingClientRect(); let rect2 = element2.getBoundingClientRect(); let 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 css = document.createElement("style"); css.type = "text/css"; css.id = id; css.innerHTML = cssString; document.head.appendChild(css); } //================================================================== /*Resize Video Container*/ function createResize() { const ytd_video = document.getElementById("ytd-player"); const movie_player = document.getElementById("movie_player"); const { clientHeight: height, clientWidth: width } = movie_player; if (typeof ytd_video !== null && typeof movie_player !== null) { ytd_video.player_.setInternalSize(width, height); } return; } /* Check Video Container */ function viewInterval() { const html_video = document.querySelector(".video-stream.html5-main-video"); const movie_player = document.getElementById("movie_player"); const video = document.getElementsByTagName("video")[0]; const ytd_flexy = document.getElementsByTagName("ytd-watch-flexy")[0]; const chrome_bottom = document.querySelector(".ytp-chrome-bottom"); setInterval(() => { const isInViewportYTD = isInViewport(video, ytd_flexy); const isCenteredMoviePlayer = isCentered(video, movie_player); const isInViewportHTMLPlayer = isInViewport(chrome_bottom, html_video); const isCenteredHTMLPlayer = isCentered(chrome_bottom, html_video); if (isInViewportYTD && isInViewportHTMLPlayer && !isCenteredMoviePlayer && !isCenteredHTMLPlayer) { const height = video.clientHeight; // Height Of The Video Element if (height !== maxWidth) { createResize(); console.log(`Player Size: ${video.clientHeight} x ${video.clientWidth}`); } } }, 500); } //================================================================== /*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.getElementsByClassName("ytp-right-controls")[0]; 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")); } } //================================================================== })();