Auto-enable Theater mode and resize the YouTube player to fill the viewport height. While in Fullscreen, temporarily "cover" the header to prevent stray bars; revert on exit. Excludes embeds and YouTube Music. Global Arrow keys control volume with a clean OSD.
// ==UserScript==
// @name YouTube Theater Fill and volume control
// @namespace https://greasyfork.org/users/1533208
// @version 1.4.1
// @description Auto-enable Theater mode and resize the YouTube player to fill the viewport height. While in Fullscreen, temporarily "cover" the header to prevent stray bars; revert on exit. Excludes embeds and YouTube Music. Global Arrow keys control volume with a clean OSD.
// @author Martin (Left234) & Lina
// @license MIT
// @match https://*.youtube.com/*
// @exclude https://*.youtube.com/embed/*
// @exclude https://music.youtube.com/*
// @homepageURL https://greasyfork.org/en/scripts/554496-youtube-theater-fill
// @supportURL https://greasyfork.org/en/scripts/554496-youtube-theater-fill/feedback
// @icon https://www.youtube.com/s/desktop/6c8d3e3a/img/favicon_144x144.png
// @run-at document-start
// @grant none
// ==/UserScript==
/*
MIT License
Copyright (c) 2025 Martin (Left234) & Lina
*/
(() => {
"use strict";
// ---------------- Config ----------------
// OSD composition placement: 'golden' (0.382 from top) or 'thirds' (1/3 from top)
const OSD_COMPOSITION = 'golden';
const OSD_FRAC = OSD_COMPOSITION === 'golden' ? 0.382 : (1 / 3);
// Volume steps
const VOL_STEP = 0.05; // 5% normal
const SHIFT_MULT = 2; // Shift = 10% total (2 * 5%)
// -------------- Theater Fill --------------
const VERSION = "1.4.1";
const CLASS = "ytf-fill";
const COVER = "ytf-cover-header";
const STYLE_ID = "ytf-fill-style";
const LS_THEATER_PREF_KEYS = [
"yt-player-theater-mode-preference",
"ytd-player-theater-mode-preference",
];
let flexyAttrObserver = null;
let mastheadRO = null;
// --- Utilities ---
function onReady(fn) {
if (document.readyState === "complete" || document.readyState === "interactive") fn();
else document.addEventListener("DOMContentLoaded", fn, { once: true });
}
function initViewportUnit() {
let unit = "100vh";
try {
if (CSS.supports("height: 100dvh")) unit = "100dvh";
else if (CSS.supports("height: 100svh")) unit = "100svh";
} catch {}
document.documentElement.style.setProperty("--ytf-viewport", unit);
}
function injectStyle() {
if (document.getElementById(STYLE_ID)) return;
const css = `
/* Base sizing */
body.${CLASS} { overflow-x: hidden !important; }
body.${CLASS} ytd-app { position: static !important; }
body.${CLASS} ytd-watch-flexy[theater] #player-theater-container.ytd-watch-flexy,
body.${CLASS} ytd-watch-flexy[theater] #player-full-bleed-container.ytd-watch-flexy,
body.${CLASS} ytd-watch-flexy[theater] #full-bleed-container.ytd-watch-flexy,
body.${CLASS} ytd-watch-grid[theater] #player-theater-container.ytd-watch-grid,
body.${CLASS} ytd-watch-grid[theater] #player-full-bleed-container.ytd-watch-grid,
body.${CLASS} ytd-watch-grid[theater] #full-bleed-container.ytd-watch-grid {
height: calc(var(--ytf-viewport, 100vh) - var(--ytf-offset, 56px)) !important;
max-height: calc(var(--ytf-viewport, 100vh) - var(--ytf-offset, 56px)) !important;
width: 100vw !important;
max-width: 100vw !important;
background: #000 !important;
position: relative !important;
inset: auto !important;
left: 0 !important; right: 0 !important;
transform: none !important;
margin: 0 auto !important;
padding: 0 !important;
}
body.${CLASS} ytd-watch-flexy[theater] #player-container-outer.ytd-watch-flexy,
body.${CLASS} ytd-watch-flexy[theater] #player-container-inner.ytd-watch-flexy,
body.${CLASS} ytd-watch-flexy[theater] #player-container.ytd-watch-flexy,
body.${CLASS} ytd-watch-grid[theater] #player-container-outer.ytd-watch-grid,
body.${CLASS} ytd-watch-grid[theater] #player-container-inner.ytd-watch-grid,
body.${CLASS} ytd-watch-grid[theater] #player-container.ytd-watch-grid {
height: 100% !important;
width: 100% !important;
display: flex !important;
justify-content: center !important;
align-items: stretch !important;
left: 0 !important; right: 0 !important;
transform: none !important;
margin: 0 !important; padding: 0 !important;
}
body.${CLASS} ytd-watch-flexy[theater] ytd-player#ytd-player,
body.${CLASS} ytd-watch-grid[theater] ytd-player#ytd-player {
height: 100% !important; width: 100% !important;
}
body.${CLASS} ytd-watch-flexy[theater] .html5-video-player,
body.${CLASS} ytd-watch-flexy[theater] .html5-video-container,
body.${CLASS} ytd-watch-flexy[theater] video.html5-main-video,
body.${CLASS} ytd-watch-grid[theater] .html5-video-player,
body.${CLASS} ytd-watch-grid[theater] .html5-video-container,
body.${CLASS} ytd-watch-grid[theater] video.html5-main-video {
width: 100% !important; height: 100% !important;
left: 0 !important; right: 0 !important;
transform: none !important;
object-fit: contain !important;
object-position: center center !important;
}
/* Remove ambient cinematics that fight our sizing */
body.${CLASS} ytd-watch-flexy #cinematics,
body.${CLASS} ytd-watch-grid #cinematics,
body.${CLASS} #cinematics-container { display: none !important; }
/* Chips and below content placement */
body.${CLASS} ytd-watch-flexy #chips,
body.${CLASS} ytd-watch-grid #chips,
body.${CLASS} #chips-wrapper { position: static !important; }
body.${CLASS} ytd-watch-flexy #below,
body.${CLASS} ytd-watch-grid #below { margin-top: 8px !important; }
/* Header cover mode */
body.${CLASS}.${COVER} { --ytf-offset: 0px !important; }
body.${CLASS}.${COVER} ytd-app #masthead-container.ytd-app,
body.${CLASS}.${COVER} ytd-masthead {
position: absolute !important;
top: 0 !important; left: 0 !important; right: 0 !important;
z-index: 2020 !important;
width: 100% !important;
}
`;
const s = document.createElement("style");
s.id = STYLE_ID;
s.textContent = css;
(document.head || document.documentElement).appendChild(s);
}
function masthead() {
return document.getElementById("masthead-container") || document.querySelector("ytd-masthead");
}
function updateOffset() {
if (!document.body) return;
if (document.body.classList.contains(COVER)) {
document.documentElement.style.setProperty("--ytf-offset", "0px");
} else {
const h = masthead()?.offsetHeight || 56;
document.documentElement.style.setProperty("--ytf-offset", `${h}px`);
}
}
function isWatchUrl() {
return location.pathname === "/watch" || /[?&]v=/.test(location.search);
}
const onWatch = () => isWatchUrl() || !!document.querySelector("ytd-watch-flexy, ytd-watch-grid");
function setTheaterPref() {
try {
LS_THEATER_PREF_KEYS.forEach(k => localStorage.setItem(k, "DEFAULT_ON"));
} catch {}
}
function ensureTheater(flexy) {
if (!flexy) return;
setTheaterPref();
if (!flexy.hasAttribute("theater")) {
try { flexy.setAttribute("theater", ""); } catch {}
try { flexy.theater = true; } catch {}
queueMicrotask(() => {
const stillNot = !document.querySelector("ytd-watch-flexy[theater], ytd-watch-grid[theater]");
if (stillNot) {
const btn = document.querySelector(".ytp-size-button");
if (btn) btn.click();
}
});
}
}
function whenFlexy(cb) {
const tryNow = () => {
const el = document.querySelector("ytd-watch-flexy, ytd-watch-grid");
if (!el) return false;
cb(el);
return true;
};
if (tryNow()) return;
const mo = new MutationObserver(() => { if (tryNow()) mo.disconnect(); });
mo.observe(document.documentElement, { childList: true, subtree: true });
}
function watchFlexyAttrs(flexy) {
if (!flexy) return;
if (flexyAttrObserver) flexyAttrObserver.disconnect();
flexyAttrObserver = new MutationObserver(updateOffset);
flexyAttrObserver.observe(flexy, { attributes: true });
}
function watchMasthead() {
const mh = masthead();
if (!mh || typeof ResizeObserver === "undefined") return;
if (mastheadRO) mastheadRO.disconnect();
mastheadRO = new ResizeObserver(updateOffset);
mastheadRO.observe(mh);
}
function setCover(on) {
if (!document.body) return;
document.body.classList.toggle(COVER, !!on);
updateOffset();
}
function isFullscreen() {
return !!(document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement);
}
function onFullscreenChange() {
setCover(isFullscreen());
}
function apply() {
if (!document.body) return;
if (!onWatch()) {
document.body.classList.remove(CLASS, COVER);
return;
}
document.body.classList.add(CLASS);
whenFlexy(flexy => {
ensureTheater(flexy);
watchFlexyAttrs(flexy);
updateOffset();
// Scroll to top so player is fully visible
window.scrollTo(0, 0);
console.info(`[YouTube Theater Fill v${VERSION}] Theater + fill applied.`);
});
watchMasthead();
}
// -------------- Global Volume --------------
let ytfOsd, ytfOsdTimer;
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
const isEditable = (el) => {
if (!el) return false;
if (el.isContentEditable) return true;
const tag = el.tagName;
if (!tag) return false;
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" ||
!!el.closest('input, textarea, select, [contenteditable="true"]');
};
const getVideo = () =>
document.querySelector("video.html5-main-video") ||
document.querySelector("#movie_player video") ||
document.querySelector("video");
function positionOSDToVideo() {
if (!ytfOsd) return;
const vid = getVideo();
if (!vid) return;
const r = vid.getBoundingClientRect();
const x = r.left + r.width / 2;
const y = r.top + r.height * OSD_FRAC; // golden/thirds placement
ytfOsd.style.left = `${x}px`;
ytfOsd.style.top = `${y}px`;
}
// Transparent, thinner OSD (white text), positioned by composition rule
function showVolumeOSD(value) {
if (!ytfOsd) {
ytfOsd = document.createElement("div");
ytfOsd.style.cssText = [
"position:fixed",
"left:50%", // initial (will be repositioned to video on first show)
"top:30%",
"transform:translate(-50%,-50%)",
"padding:0",
"background:transparent",
"color:#fff",
// Slightly thinner, clean stack
"font:600 40px/1.08 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, Apple Color Emoji, Segoe UI Emoji",
"letter-spacing:.2px",
"-webkit-font-smoothing:antialiased",
"text-rendering:optimizeLegibility",
// Very light edge for legibility on bright frames
"-webkit-text-stroke:.35px rgba(0,0,0,.30)",
"text-shadow:0 0 8px rgba(0,0,0,.28)",
"z-index:2147483647",
"pointer-events:none",
"opacity:0",
"transition:opacity .15s ease"
].join(";");
document.documentElement.appendChild(ytfOsd);
// Keep OSD aligned on resizes and fullscreen toggles
window.addEventListener("resize", positionOSDToVideo);
document.addEventListener("fullscreenchange", positionOSDToVideo);
document.addEventListener("webkitfullscreenchange", positionOSDToVideo);
document.addEventListener("mozfullscreenchange", positionOSDToVideo);
document.addEventListener("MSFullscreenChange", positionOSDToVideo);
}
// Set text & place using composition geometry
ytfOsd.textContent = typeof value === "number" ? `${value}%` : String(value);
positionOSDToVideo();
// Fade in/out
ytfOsd.style.opacity = "1";
clearTimeout(ytfOsdTimer);
ytfOsdTimer = setTimeout(() => (ytfOsd.style.opacity = "0"), 900);
}
function onKeydown(e) {
// Only on watch pages; ignore when typing; ignore with Ctrl/Cmd/Alt
if (!onWatch()) return;
if (isEditable(e.target)) return;
if (e.ctrlKey || e.metaKey || e.altKey) return;
// Volume Up/Down
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
const vid = getVideo();
if (!vid) return;
const mult = e.shiftKey ? SHIFT_MULT : 1;
const delta = (e.key === "ArrowUp" ? 1 : -1) * VOL_STEP * mult;
const newVol = clamp(Math.round((vid.volume + delta) * 100) / 100, 0, 1);
if (newVol !== vid.volume) {
if (vid.muted && newVol > 0) vid.muted = false;
vid.volume = newVol;
showVolumeOSD(Math.round(newVol * 100));
}
e.preventDefault();
e.stopImmediatePropagation();
return;
}
// Mute toggle
if (e.key === "m" || e.key === "M") {
const vid = getVideo();
if (!vid) return;
vid.muted = !vid.muted;
showVolumeOSD(vid.muted ? "Muted" : Math.round(vid.volume * 100));
e.preventDefault();
e.stopImmediatePropagation();
return;
}
}
// --- Init + SPA awareness ---
initViewportUnit();
injectStyle();
onReady(() => {
apply();
updateOffset();
// Fallback retry in case of race conditions
setTimeout(apply, 1500);
// YouTube SPA events
document.addEventListener("yt-navigate-start", apply);
document.addEventListener("yt-navigate-finish", apply);
document.addEventListener("yt-page-data-fetched", apply);
// Resize updates header offset
window.addEventListener("resize", updateOffset);
// Fullscreen listeners (cross-browser)
document.addEventListener("fullscreenchange", onFullscreenChange);
document.addEventListener("webkitfullscreenchange", onFullscreenChange);
document.addEventListener("mozfullscreenchange", onFullscreenChange);
document.addEventListener("MSFullscreenChange", onFullscreenChange);
// Global volume keys (capture phase to beat page scroll)
window.addEventListener("keydown", onKeydown, true);
});
})();