Resizes the YouTube player to different sizes
// ==UserScript==
// @name YouTube Sizer
// @author John Burt
// @namespace namespace_runio
// @version 2.04
// @description Resizes the YouTube player to different 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";
if (window.frameElement) {
throw new Error("Stopped JavaScript.");
}
//==================================================================
// Storage helpers
//==================================================================
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 || value === undefined) {
setPref(preference, new_value);
value = new_value;
} else if (typeof value === "number" && isNaN(value)) {
console.warn(`[YT Sizer] Stored preference "${preference}" was NaN — resetting to default.`);
setPref(preference, new_value);
value = new_value;
}
return value;
}
//==================================================================
// Preferences
//==================================================================
initPref("yt-width", 1280);
initPref("yt-resize", false);
function getMaxWidth() {
const stored = getPref("yt-width") ?? 854;
return Math.max(854, Math.min(stored, window.innerWidth));
}
function setMaxWidth(value) {
const clamped = Math.max(854, Math.min(value, window.innerWidth));
setPref("yt-width", clamped);
}
//==================================================================
// Global constants / state
//==================================================================
var shortcutKey = "R";
var ytresizeCss = `ytd-watch-flexy[fullscreen] #ytp-resize-button { display:none !important; }`;
let currentResizeObserver = null;
let keyListenersAdded = false;
let sizeObserverInstance = null;
let resizeDebounceTimer = null;
let checkURLDebounceTimer = null;
let lastCheckedPath = "";
//==================================================================
// Boot
//==================================================================
window.addEventListener("load", () => {
const observer = new MutationObserver(checkURL);
const titleEl = document.querySelector("title");
if (titleEl) {
observer.observe(titleEl, {
attributes: true,
characterData: true,
childList: true
});
checkURL();
}
}, {
once: true
});
//==================================================================
// checkURL
//==================================================================
function checkURL() {
clearTimeout(checkURLDebounceTimer);
checkURLDebounceTimer = setTimeout(() => {
const path = window.location.pathname;
if (!path.includes("watch")) return;
if (path === lastCheckedPath) return;
lastCheckedPath = path;
waitElement("#player-container-outer").then((elm) => {
if (!window.location.pathname.includes("watch")) return;
if (!document.getElementById("yt-css")) {
startMethods();
} else {
controlResize();
createResize();
viewObserver();
}
}).catch(() => {});
}, 150);
}
//==================================================================
// waitElement
//==================================================================
function waitElement(selector, timeoutMs = 5000) {
return new Promise((resolve, reject) => {
let element = document.querySelector(selector);
if (element) return resolve(element);
const observer = new MutationObserver(() => {
element = document.querySelector(selector);
if (element) {
clearTimeout(timer);
observer.disconnect();
resolve(element);
}
});
const timer = setTimeout(() => {
observer.disconnect();
reject(new Error(`waitElement: "${selector}" not found within ${timeoutMs} ms`));
}, timeoutMs);
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
//==================================================================
// startMethods
//==================================================================
function startMethods() {
sizeObserver();
if (getPref("yt-resize") === true) {
addCss(`#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) { max-width: ${getMaxWidth()}px !important; }`, "small-player");
addCss(`ytd-watch-flexy[flexy]:not([full-bleed-player][full-bleed-no-max-width-columns]) #columns.ytd-watch-flexy {max-width: 100% !important;}`, "max-player");
}
addCss(ytresizeCss, "yt-css");
controlResize();
viewObserver();
}
//==================================================================
// Helpers
//==================================================================
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
};
return Math.abs(center1.x - center2.x) <= 1 &&
Math.abs(center1.y - center2.y) <= 1;
}
function addCss(cssString, id) {
const css = document.createElement("style");
css.type = "text/css";
css.id = id;
css.textContent = cssString;
document.head.appendChild(css);
}
//==================================================================
// createResize
//==================================================================
function createResize() {
const element = document.querySelector("ytd-app");
if (!element) return;
element.dispatchEvent(new CustomEvent("yt-action", {
bubbles: true,
cancelable: true,
composed: true,
detail: {
actionName: "yt-window-resized",
disableBroadcast: false,
optionalAction: true,
returnValue: []
}
}));
}
//==================================================================
// viewObserver
//==================================================================
function viewObserver() {
let movie_player = document.querySelector(".html5-video-player");
let video = document.querySelector("video");
if (!movie_player || !video) return;
if (currentResizeObserver) currentResizeObserver.disconnect();
const resizeObserver = new ResizeObserver((entries) => {
window.requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) return;
const currentlyCentered = isCentered(video, movie_player);
if (!currentlyCentered) {
clearTimeout(resizeDebounceTimer);
resizeDebounceTimer = setTimeout(createResize, 100);
}
});
});
currentResizeObserver = resizeObserver;
resizeObserver.observe(video);
}
//==================================================================
// sizeObserver
//==================================================================
function sizeObserver() {
if (sizeObserverInstance) return;
const config = {
attributes: true,
childList: true,
subtree: true,
characterData: true
};
const callback = function(mutationsList) {
for (let mutation of mutationsList) {
const removedHasSmall = Array.from(mutation.removedNodes).some(
node => node.nodeType === Node.ELEMENT_NODE && node.id === "small-player"
);
const addedHasSmall = Array.from(mutation.addedNodes).some(
node => node.nodeType === Node.ELEMENT_NODE && node.id === "small-player"
);
if (removedHasSmall) {
setPref("yt-resize", false);
controlResize();
createResize();
} else if (addedHasSmall) {
setPref("yt-resize", true);
controlResize();
createResize();
}
if (mutation.target && mutation.target.id === "small-player") {
setPref("yt-width", getMaxWidth());
createResize();
} else if (mutation.target && mutation.target.parentNode &&
mutation.target.parentNode.id === "small-player") {
setPref("yt-width", getMaxWidth());
createResize();
}
}
};
sizeObserverInstance = new MutationObserver(callback);
sizeObserverInstance.observe(document.head, config);
}
//==================================================================
// Tooltip — R is now inside a real bordered box
//==================================================================
let resizeTooltipHideTimeout = null;
function showResizeButtonTooltip(btn, show = true) {
if (resizeTooltipHideTimeout) {
clearTimeout(resizeTooltipHideTimeout);
resizeTooltipHideTimeout = null;
}
const buttonRect = btn.getBoundingClientRect();
const tooltipHorizontalCenter = buttonRect.left + buttonRect.width / 2;
const tooltip = document.getElementById("ytd-resize-tt") || createTooltip();
const tooltipText = tooltip.querySelector("#ytd-resize-tt-text");
const tooltipKey = tooltip.querySelector("#ytd-resize-tt-key");
if (show) {
const label = btn.getAttribute("aria-label") || btn.getAttribute("title") || "Resize";
tooltipText.textContent = label.replace(/\s*\[[^\]]+\]\s*$/, "");
tooltipKey.textContent = shortcutKey;
tooltip.style.removeProperty("display");
tooltip.style.visibility = "hidden";
tooltip.style.transition = "none";
void tooltip.offsetHeight;
const tooltipRect = tooltip.getBoundingClientRect();
// --- FIX: anchor to stable YouTube UI baseline ---
const controlsBar =
document.querySelector(".ytp-chrome-bottom") ||
document.querySelector(".ytp-progress-bar-container");
let baseTop;
if (controlsBar) {
const controlsRect = controlsBar.getBoundingClientRect();
baseTop = controlsRect.top;
} else {
baseTop = buttonRect.top;
}
const gap = 11.5;
const tooltipTop = baseTop - tooltipRect.height - gap;
tooltip.style.top = `${tooltipTop}px`;
tooltip.style.left = `${tooltipHorizontalCenter - tooltipRect.width / 2}px`;
tooltip.style.visibility = "visible";
btn.removeAttribute("title");
} else {
resizeTooltipHideTimeout = setTimeout(() => {
tooltip.style.display = "none";
tooltip.style.visibility = "";
tooltipText.textContent = "";
tooltipKey.textContent = "";
const currentLabel = btn.getAttribute("aria-label");
if (currentLabel) {
btn.setAttribute("title", currentLabel);
}
resizeTooltipHideTimeout = null;
}, 120);
}
function createTooltip() {
const htmlPlayer = document.querySelector(".html5-video-player");
if (!htmlPlayer) {
const fallback = document.createElement("div");
fallback.style.display = "none";
return fallback;
}
const tooltip = document.createElement("div");
tooltip.id = "ytd-resize-tt";
tooltip.className = "ytp-tooltip ytp-bottom";
tooltip.style.position = "fixed";
tooltip.style.zIndex = "10000";
const wrapper = document.createElement("div");
wrapper.className = "ytp-tooltip-text-wrapper ytp-tooltip-bottom-text";
const tooltipText = document.createElement("span");
tooltipText.className = "ytp-tooltip-text";
tooltipText.id = "ytd-resize-tt-text";
const tooltipKey = document.createElement("span");
tooltipKey.id = "ytd-resize-tt-key";
wrapper.appendChild(tooltipText);
wrapper.appendChild(tooltipKey);
tooltip.appendChild(wrapper);
if (!document.getElementById("yt-sizer-tooltip-style")) {
const style = document.createElement("style");
style.id = "yt-sizer-tooltip-style";
style.textContent = `
#ytd-resize-tt .ytp-tooltip-text-wrapper {
display: flex;
align-items: center;
}
#ytd-resize-tt-key {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 15px;
height: 15px;
margin-left: 4px;
padding: 0 4px;
border: 1px solid rgba(255,255,255,0.30);
border-radius: 4px;
font-size: 12px;
font-weight: 500;
line-height: 15px;
box-sizing: border-box;
vertical-align: middle;
}
`;
document.head.appendChild(style);
}
htmlPlayer.appendChild(tooltip);
return tooltip;
}
}
//==================================================================
// Button setup
//==================================================================
function setButton(btn, path) {
let pathData = {};
let ariaLabel, titleText;
if (getPref("yt-resize") !== true) {
pathData.d = `M 13 17 L 5 9 L 5 17 Z
M 23 19
L 23 4.98 C 23 3.88 22.1 3 21 3
L 3 3 C 1.9 3 1 3.88 1 4.98
L 1 19 C 1 20.1 1.9 21 3 21
L 21 21 C 22.1 21 23 20.1 23 19
L 23 19 Z
M 21 19.02 L 3 19.02 L 3 4.97
L 21 4.97 L 21 19.02 L 21 19.02 Z`;
ariaLabel = `Resize mode [${shortcutKey}]`;
titleText = `Resize mode [${shortcutKey}]`;
} else {
pathData.d = `M 19 15 L 19 7 L 11 7 Z M 23 19
L 23 4.98 C 23 3.88 22.1 3 21 3
L 3 3 C 1.9 3 1 3.88 1 4.98
L 1 19 C 1 20.1 1.9 21 3 21
L 21 21 C 22.1 21 23 20.1 23 19
L 23 19 Z M 21 19.02
L 3 19.02 L 3 4.97
L 21 4.97 L 21 19.02 L 21 19.02 Z`;
ariaLabel = `Default view [${shortcutKey}]`;
titleText = `Default view [${shortcutKey}]`;
}
path.setAttribute("d", pathData.d);
btn.setAttribute("aria-label", ariaLabel);
btn.setAttribute("title", titleText);
}
function createButton(container) {
const btn = document.createElement("button");
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
svg.setAttribute("height", "24");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("width", "24");
setButton(btn, path);
path.setAttribute("fill", "white");
svg.appendChild(path);
btn.appendChild(svg);
btn.classList.add("ytp-resize-button", "ytp-button");
btn.setAttribute("id", "ytp-resize-button");
btn.setAttribute("data-tooltip-target-id", "ytp-resize-button");
container.insertBefore(btn, container.lastChild.previousSibling || container.lastChild);
const showTooltip = (event) => {
showResizeButtonTooltip(btn, ["mouseover", "focus"].includes(event.type));
};
btn.addEventListener("click", (e) => {
e.stopPropagation();
e.preventDefault();
buttonScript();
}, false);
btn.addEventListener("mouseover", showTooltip);
btn.addEventListener("mouseout", showTooltip);
btn.addEventListener("focus", showTooltip);
btn.addEventListener("blur", showTooltip);
}
function toggleStyle(id, cssTemplate) {
const styleElement = document.getElementById(id);
if (styleElement && document.head.contains(styleElement)) {
document.head.removeChild(styleElement);
} else {
addCss(cssTemplate, id);
}
}
function buttonScript() {
toggleStyle(
"max-player",
`ytd-watch-flexy[flexy]:not([full-bleed-player][full-bleed-no-max-width-columns])
#columns.ytd-watch-flexy { max-width: 100% !important; }`
);
toggleStyle(
"small-player",
`#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) {
max-width: ${getMaxWidth()}px !important; }`
);
}
function shortScript() {
const css = `#primary.ytd-watch-flexy:not([theater]):not([fullscreen]) { max-width: ${getMaxWidth()}px !important; }`;
let splayer = document.getElementById("small-player");
if (splayer && document.head.contains(splayer)) {
splayer.textContent = css;
} else {
addCss(css, "small-player");
}
}
//==================================================================
// Keyboard / wheel handlers
//==================================================================
function handleKeydown(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;
const splayer = document.getElementById("small-player");
if (e.key === shortcutKey.toLowerCase() || e.key === shortcutKey.toUpperCase()) {
e.stopPropagation();
e.preventDefault();
buttonScript();
return;
}
if (document.head.contains(splayer)) {
if (e.key === "z") {
e.stopPropagation();
e.preventDefault();
setMaxWidth(getMaxWidth() - 20);
shortScript();
} else if (e.key === "x") {
e.stopPropagation();
e.preventDefault();
setMaxWidth(getMaxWidth() + 20);
shortScript();
}
}
}
function handleWheel(e) {
const splayer = document.getElementById("small-player");
if (!document.head.contains(splayer)) return;
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) return;
e.stopPropagation();
e.preventDefault();
if (e.deltaY < 0) {
setMaxWidth(getMaxWidth() + 20);
} else if (e.deltaY > 0) {
setMaxWidth(getMaxWidth() - 20);
}
shortScript();
}
//==================================================================
// controlResize
//==================================================================
function addListenersOnce() {
if (keyListenersAdded) return;
document.addEventListener("keydown", handleKeydown, false);
document.addEventListener("wheel", handleWheel, {
passive: false
});
keyListenersAdded = true;
}
function controlResize() {
const buttonExists = document.getElementById("ytp-resize-button");
if (!buttonExists) {
const container = document.querySelector(".ytp-right-controls-right") ||
document.querySelector(".ytp-right-controls");
if (container) {
createButton(container);
addListenersOnce();
} else {
waitElement(".ytp-right-controls-right, .ytp-right-controls").then((container) => {
if (!document.getElementById("ytp-resize-button")) {
createButton(container);
}
addListenersOnce();
}).catch(() => {});
}
} else {
setButton(buttonExists, buttonExists.querySelector("path"));
}
}
//==================================================================
})();