// ==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.3 // @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 chatFrame = null; let isTheaterMode = false; let chatCollapsed = true; const RETRY_COUNT = 5; const RETRY_DELAY = 100; const retryOperation = (operation, retries, delay) => { return new Promise((resolve, reject) => { const attempt = (remainingRetries) => { try { const result = operation(); if (result) { console.log(`Operation succeeded: ${operation}`); resolve(result); } else if (remainingRetries > 0) { console.log(`Retrying operation: ${operation}, retries left: ${remainingRetries}`); setTimeout(() => attempt(remainingRetries - 1), delay); } else { reject(new Error('Operation failed after retries')); } } catch (error) { console.error(`Error in retryOperation: ${error}`); reject(error); } }; attempt(retries); }); }; function updateStyle() { console.log('Updating style...'); 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%'; console.log('Updated mastHeadContainer maxWidth:', mastHeadContainer.style.maxWidth); } else { console.warn('mastHeadContainer not found.'); } } function findElement(selector, retries = RETRY_COUNT, delay = RETRY_DELAY) { return retryOperation(() => document.querySelector(selector), retries, delay) .catch(() => console.warn(`Element not found: ${selector}`)); } function updateTheaterStatus(event) { isTheaterMode = !!event?.detail?.enabled; updateStyle(); } async function updateChatStatus(event) { chatFrame = event.target; chatCollapsed = event.detail !== false; window.addEventListener('player-api-ready', updateStyle, { once: true} ); } function attachEventListeners() { window.addEventListener('yt-set-theater-mode-enabled', (event) => { updateTheaterStatus(event); }, true); window.addEventListener('yt-chat-collapsed-changed', (event) => { updateChatStatus(event); }, true); window.addEventListener('yt-navigate-finish', () => { window.addEventListener('yt-page-data-updated', updateStyle, { once: true } ); }, true); } function applyStaticStyles() { console.log('Applying static styles...'); 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; } `); console.log('Static styles applied.'); } function init() { applyStaticStyles(); attachEventListeners(); } init(); })();