Minimal, stability-first build for Brave: auto-enable Theater and fill viewport height with clean letterboxing. No volume OSD, no hotkeys, no autoplay/watchdogs—just sizing.
// ==UserScript==
// @name YouTube Theater Fill (Brave) — v3.1 clean + OSD
// @namespace https://gf.qytechs.cn/users/1533208
// @version 3.1-clean+osd
// @description Minimal, stability-first build for Brave: auto-enable Theater and fill viewport height with clean letterboxing. No volume OSD, no hotkeys, no autoplay/watchdogs—just sizing.
// @author Martin (Left234) & Lina
// @license MIT
// @match https://*.youtube.com/*
// @exclude https://*.youtube.com/embed/*
// @exclude https://music.youtube.com/*
// @run-at document-start
// @grant none
// ==/UserScript==
(() => {
"use strict";
const VERSION = "3.0-stripped-beta";
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",
];
// ---------- utils ----------
function onReady(fn) {
if (document.readyState === "complete" || document.readyState === "interactive") fn();
else document.addEventListener("DOMContentLoaded", fn, { once: true });
}
const $ = (sel, root=document) => root.querySelector(sel);
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 */
body.${CLASS} { overflow-x: hidden !important; }
body.${CLASS} ytd-app { position: static !important; }
/* Size the main host containers to the usable viewport */
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;
}
/* These inner containers must stretch to 100% */
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;
position: relative !important; /* anchor for absolute fill below */
}
/* Ensure player + video absolutely fill the anchored box */
#movie_player,
#movie_player .html5-video-player,
#movie_player .html5-video-container,
#movie_player video.html5-main-video {
position: absolute !important;
inset: 0 !important;
width: 100% !important;
height: 100% !important;
}
#movie_player video.html5-main-video {
display: block !important;
object-fit: contain !important;
object-position: center center !important;
}
/* Avoid ambient cinematics fighting our sizing */
body.${CLASS} #cinematics,
body.${CLASS} #cinematics-container { display: none !important; }
/* Header cover when fullscreen (prevents stray bars) */
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;
}
/* If YouTube tries "cover", force contain to preserve full frame visibility */
.ytp-fit-cover-video .html5-main-video { object-fit: contain !important; }
/* Ensure clean bars on extra-wide videos (avoid page peeking) */
ytd-watch-flexy[theater] #player-theater-container.ytd-watch-flexy,
ytd-watch-grid[theater] #player-theater-container.ytd-watch-grid {
background: #000 !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") || $("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() || !!$("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 = !$("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 = $("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 isFullscreen() {
return !!(document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement);
}
function onFullscreenChange(){ if (document.body) { document.body.classList.toggle(COVER, isFullscreen()); updateOffset(); } }
// Strip inline styles that can persist across SPA transitions and cause corner/tiny/offset issues.
function normalizePlayerSizing(){
const player = document.getElementById('movie_player');
const container = player?.querySelector('.html5-video-container');
const video = player?.querySelector('video.html5-main-video');
if (!player || !container || !video) return;
[player, container, video].forEach(el => {
['width','height','left','top','right','bottom','transform'].forEach(p => { try { el.style.removeProperty(p); } catch {} });
});
try {
container.style.position = 'absolute';
container.style.inset = '0';
video.style.width = '100%';
video.style.height = '100%';
video.style.objectFit = 'contain';
video.style.objectPosition = 'center center';
} catch {}
}
function apply(){
if (!document.body) return;
if (!onWatch()) { document.body.classList.remove(CLASS, COVER); return; }
document.body.classList.add(CLASS);
whenFlexy(flexy => { ensureTheater(flexy); updateOffset(); normalizePlayerSizing(); });
}
// ---------- init ----------
initViewportUnit();
injectStyle();
onReady(() => {
apply();
updateOffset();
// Minimal, stability-first hooks only
document.addEventListener("yt-navigate-finish", apply);
window.addEventListener("load", apply);
window.addEventListener("resize", updateOffset);
document.addEventListener("fullscreenchange", onFullscreenChange);
document.addEventListener("webkitfullscreenchange", onFullscreenChange);
document.addEventListener("mozfullscreenchange", onFullscreenChange);
document.addEventListener("MSFullscreenChange", onFullscreenChange);
});
// ---- Minimal Volume OSD + Arrow Up/Down hotkeys (no layout changes) ----
(function ytfMinimalVolumeOSD(){
const VOL_STEP = 0.05;
const SHIFT_MULT = 2;
let osd, timer;
function isEditable(el){
if (!el) return false;
if (el.isContentEditable) return true;
const t = el.tagName;
if (!t) return false;
const edit = /^(INPUT|TEXTAREA|SELECT)$/i.test(t);
return edit || !!el.closest('input, textarea, select, [contenteditable="true"]');
}
function getVideo(){
return document.querySelector("video.html5-main-video") ||
document.querySelector("#movie_player video") ||
document.querySelector("ytd-player video") ||
document.querySelector("video");
}
function ensureOSD(){
if (osd) return osd;
osd = document.createElement("div");
osd.style.cssText = [
"position:fixed","left:50%","top:35%","transform:translate(-50%,-50%)",
"padding:0","background:transparent","color:#fff",
"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",
"-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 .12s ease"
].join(";");
document.documentElement.appendChild(osd);
return osd;
}
function positionToVideo(){
const v = getVideo(); if (!v || !osd) return;
const r = v.getBoundingClientRect();
if (!r.width || !r.height) return;
const x = r.left + r.width / 2;
const y = r.top + r.height * 0.382; // golden-ish
osd.style.left = x + "px";
osd.style.top = y + "px";
}
function show(val){
ensureOSD();
osd.textContent = typeof val === "number" ? `${val}%` : String(val);
positionToVideo();
osd.style.opacity = "1";
clearTimeout(timer);
timer = setTimeout(() => { osd.style.opacity = "0"; }, 900);
}
function clamp(v, lo, hi){ return Math.min(hi, Math.max(lo, v)); }
function onKey(e){
if (isEditable(e.target)) return;
if (e.ctrlKey || e.metaKey || e.altKey) return;
if (e.key === "ArrowUp" || e.key === "ArrowDown"){
const v = getVideo(); if (!v) return;
const mult = e.shiftKey ? SHIFT_MULT : 1;
const delta = (e.key === "ArrowUp" ? 1 : -1) * VOL_STEP * mult;
const newVol = clamp(Math.round((v.volume + delta) * 100) / 100, 0, 1);
if (newVol !== v.volume){
if (v.muted && newVol > 0) v.muted = false;
v.volume = newVol;
// let YouTube react naturally; do not touch layout/containers
v.dispatchEvent(new Event('volumechange'));
show(Math.round(newVol * 100));
}
e.preventDefault(); e.stopImmediatePropagation();
} else if (e.key === "m" || e.key === "M"){
const v = getVideo(); if (!v) return;
v.muted = !v.muted;
v.dispatchEvent(new Event('volumechange'));
show(v.muted ? "Muted" : Math.round(v.volume * 100));
e.preventDefault(); e.stopImmediatePropagation();
}
}
// Bind once DOM is interactive; capture phase to win over page handlers
const bind = () => { window.addEventListener("keydown", onKey, true); };
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", bind, { once: true });
else bind();
window.addEventListener("resize", positionToVideo);
document.addEventListener("fullscreenchange", positionToVideo);
document.addEventListener("webkitfullscreenchange", positionToVideo);
document.addEventListener("mozfullscreenchange", positionToVideo);
document.addEventListener("MSFullscreenChange", positionToVideo);
})();
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址