Resizes the YouTube player to smaller sizes
/* Resizes the YouTube player to smaller sizes. Copyright (C) 2023 John Burt 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 <http://www.gnu.org/licenses/> */ // ==UserScript== // @name YouTube Sizer // @author John Burt // @namespace namespace_runio // @version 1.98 // @description Resizes the YouTube player to smaller sizes // @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 // @supportURL https://greasyfork.org/scripts/421396-youtube-sizer // @icon https://i.imgur.com/9haPE5X.png // @license GPL-3.0+ // @noframes // ==/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 || isNaN(value)) { setPref(preference, new_value); value = new_value; } return value; } //================================================================== let policy; if (window.trustedTypes) { policy = trustedTypes.createPolicy('default', { createHTML: (string) => string, }); } else { // Fallback if trustedTypes is not supported policy = { createHTML: (string) => string, }; } //================================================================== initPref("yt-width", 854); initPref("yt-resize", false); //================================================================== // Global Variables var maxWidth = getPref("yt-width"); // Max Width of Video var shortcutKey = "r"; // Shortcut Key var ytresizeCss = `.ytp-big-mode .ytp-chrome-controls .ytp-resize-button { display:none !important; }`; //================================================================== window.addEventListener("load", () => { const observer = new MutationObserver(checkURL); const e = document.querySelector("title"); if (e) { observer.observe(e, { attributes: true, characterData: true, childList: true }); } }, { once: true }); //================================================================== function checkURL() { waitElement("#player-container-outer").then((elm) => { if (window.location.pathname.includes("watch")) { if (!document.getElementById("yt-css")) { startMethods(); createResize(); } } }); } //================================================================== 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 startMethods() { new Promise((resolve) => { sizeObserver(); // Size Observer resolve(); }).then(() => { if (getPref("yt-resize")) { addCss(`#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) { max-width: ${maxWidth}px !important; }`, "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; // Use the Trusted Type policy to create trusted HTML css.innerHTML = policy.createHTML(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.width !== maxWidth) { createResize(); console.log("Player Size: " + entry.contentRect.width + " x " + entry.contentRect.height); } } } }); }); // 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, characterData: 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 } if (mutation.target && mutation.target.id === "small-player") { setPref("yt-width", maxWidth); createResize(); // Resize Video Container } else if (mutation.target && mutation.target.parentNode && mutation.target.parentNode.id === "small-player") { setPref("yt-width", maxWidth); 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 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() { let abtn = document.querySelector(".ytp-right-controls"); let btn = document.createElement("button"); let path = document.createElement("path"); let clickEvent = new Event("click", { bubbles: false }); /*Start Create SVG*/ let svg = document.createElement("svg"); svg.setAttribute("height", "100%"); svg.setAttribute("version", "1.1"); svg.setAttribute("viewBox", "0 0 36 36"); svg.setAttribute("width", "100%"); let 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); } function buttonScript() { let splayer = document.getElementById("small-player"); if (document.head.contains(splayer)) { document.head.removeChild(splayer); } else { addCss(`#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) { max-width: ${maxWidth}px !important; }`, "small-player"); } return; } function shortScript() { let splayer = document.getElementById("small-player"); if (document.head.contains(splayer)) { splayer.innerHTML = `#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) { max-width: ${maxWidth}px !important; }`; } else { addCss(smallerCss, "small-player"); } return; } /*Create Resize Button*/ function controlResize() { let buttonExists = document.getElementById("ytp-resize-button"); if (!buttonExists) { createButton(); document.addEventListener("keydown", function(e) { let splayer = document.getElementById("small-player"); // Common checks for all keys 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; // Check for shortcutKey if (e.key == shortcutKey.toLowerCase() || e.key == shortcutKey.toUpperCase()) { e.stopPropagation(); e.preventDefault(); buttonScript(); return; } // Check for "x" and "z" if "small-player" is in the document head if (document.head.contains(splayer)) { if (e.key == "z") { e.stopPropagation(); e.preventDefault(); maxWidth -= 20; if (maxWidth <= 854) maxWidth = 854; // min-width shortScript(); } else if (e.key == "x") { e.stopPropagation(); e.preventDefault(); maxWidth += 20; if (maxWidth >= window.innerWidth) maxWidth = window.innerWidth; // max-width shortScript(); } } }); document.addEventListener("wheel", function(e) { let splayer = document.getElementById("small-player"); let player = document.getElementById("primary-inner"); if (document.head.contains(splayer)) { if (e.altKey || e.ctrlKey || e.metaKey) return; if (/^(?:input|textarea|select|button)$/i.test(e.target.tagName)) return; if (/(?:contenteditable-root)/i.test(e.target.id)) return; if (e.shiftKey) { e.stopPropagation(); if (e.deltaY < 0) { // Scroll Up maxWidth += 20; if (maxWidth >= window.innerWidth) maxWidth = window.innerWidth; // max-width } else if (e.deltaY > 0) { // Scroll Down maxWidth -= 20; if (maxWidth <= 854) maxWidth = 854; // min-width } shortScript(); } } }); } else { setButton(buttonExists, buttonExists.querySelector("path")); } } //================================================================== })();