// ==UserScript== // @name Better YouTube Theater Mode // @name:zh-TW 更佳的 YouTube 劇場模式 // @name:zh-CN 更佳的 YouTube 剧场模式 // @name:ja 改良されたYouTubeシアターモード // @icon https://www.youtube.com/img/favicon_48.png // @author ElectroKnight22 // @namespace electroknight22_youtube_better_theater_mode_namespace // @version 1.3.2 // @match *://www.youtube.com/* // @match *://www.youtube-nocookie.com/* // @grant GM_addStyle // @grant GM.addStyle // @license MIT // @description This script adjusts YouTube's player to extend to the bottom of the screen, creating a Twitch.tv-like viewing experience with fewer distractions. // @description:zh-TW 此腳本會將 YouTube 播放器調整為延伸至螢幕底部,提供類似 Twitch.tv 的沉浸式觀看體驗,減少干擾。 // @description:zh-CN 此脚本将 YouTube 播放器调整为延伸至屏幕底部,提供类似 Twitch.tv 的沉浸式观看体验,减少干扰。 // @description:ja このスクリプトは、YouTubeのプレーヤーを画面の下部まで拡張し、Twitch.tvのようなより没入感のある視聴体験を提供します。 // @downloadURL none // ==/UserScript== /*jshint esversion: 11 */ (function () { 'use strict'; const GMCustomAddStyle = typeof GM_info !== 'undefined' ? GM_addStyle : GM.addStyle; let mastHeadContainer = null; let pageManager = null; let chatFrame = null; let isTheaterMode = false; let chatCollapsed = true; let resizeTimeout = null; const RETRY_COUNT = 5; const RETRY_DELAY = 100; const retryOperation = (operation, retries, delay) => { return new Promise((resolve, reject) => { const attempt = (remainingRetries) => { const result = operation(); if (result) { resolve(result); } else if (remainingRetries > 0) { setTimeout(() => attempt(remainingRetries - 1), delay); } else { reject(new Error('Operation failed after retries')); } }; attempt(retries); }); }; function updateStyle() { if (!mastHeadContainer) { mastHeadContainer = document.querySelector('#masthead-container'); } if (mastHeadContainer) { const chatWidth = chatFrame?.offsetWidth || 0; mastHeadContainer.style.maxWidth = (chatFrame && isTheaterMode && !chatCollapsed && chatFrame.getBoundingClientRect().top === 0) ? `calc(100% - ${chatWidth}px)` : '100%'; } } function findElement(selector, retries = RETRY_COUNT, delay = RETRY_DELAY) { return retryOperation(() => document.querySelector(selector), retries, delay) .catch(() => console.warn(`Element not found: ${selector}`)); } function forceUpdateAll() { isTheaterMode = !!pageManager.getState().watch.isTheaterMode; chatCollapsed = !!pageManager.getState().watch.chatCollapsed; updateStyle(); } async function prepNewPage(event) { if (!event || event?.detail.pageType === 'watch') { pageManager = await findElement('.ytd-page-manager'); chatFrame = await findElement('ytd-live-chat-frame'); } forceUpdateAll(); } function theaterToggled(event) { isTheaterMode = !!event?.detail?.enabled; } function chatToggled(event) { chatCollapsed = !!event?.detail; } function attachEventListeners() { window.addEventListener('yt-set-theater-mode-enabled', (event) => { theaterToggled(event); }, true); window.addEventListener('yt-chat-collapsed-changed', (event) => { chatToggled(event); }, true); window.addEventListener('player-api-ready', updateStyle, true); window.addEventListener('yt-navigate-finish', (event) => { prepNewPage(event); }, true); } function applyStaticStyles() { GMCustomAddStyle(` ytd-watch-flexy[full-bleed-player] #full-bleed-container.ytd-watch-flexy { max-height: calc(100vh - var(--ytd-watch-flexy-masthead-height)) !important; } ytd-live-chat-frame[theater-watch-while][rounded-container], #panel-pages.yt-live-chat-renderer { border-radius: 0 !important; border-top: 0px !important; border-bottom: 0px !important; } ytd-watch-flexy[fixed-panels] #chat.ytd-watch-flexy { top: 0 !important; } `); } function init() { applyStaticStyles(); attachEventListeners(); prepNewPage(); } init(); })();