YouTube Fullscreen Title Display

Show video title in top left corner during fullscreen when controls appear

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         YouTube Fullscreen Title Display
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Show video title in top left corner during fullscreen when controls appear
// @author       You
// @match        https://www.youtube.com/*
// @match        https://*.youtube.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    
    let titleOverlay = null;
    let isTitleVisible = false;
    let isFullscreen = false;
    let currentVideoId = null;
    
    function createTitleOverlay() {
        if (titleOverlay) return;
        
        titleOverlay = document.createElement('div');
        titleOverlay.style.cssText = `
            position: fixed;
            top: 10px;
            left: 10px;
            background-color: transparent;
            color: rgba(255, 255, 255, 0.9);
            padding: 0px;
            border-radius: 0px;
            font-family: 'YouTube Sans', 'Roboto', Arial, sans-serif;
            font-size: 16px;
            font-weight: 500;
            max-width: 90%;
            z-index: 9999;
            opacity: 0;
            transition: opacity 0.2s ease;
            pointer-events: none;
            word-wrap: normal;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            backdrop-filter: none;
            border: none;
            box-shadow: none;
            line-height: 1.2;
            letter-spacing: 0.2px;
            text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
        `;
        document.body.appendChild(titleOverlay);
    }
    
    function getVideoTitle() {
        // Updated selectors for YouTube's new UI
        const selectors = [
            'h1.ytd-watch-metadata yt-formatted-string',
            'h1 yt-formatted-string',
            '.title.style-scope.ytd-video-primary-info-renderer',
            'h1.title.style-scope.ytd-video-primary-info-renderer',
            'ytd-watch-metadata h1',
            '#title h1 yt-formatted-string',
            '#above-the-fold h1',
            'ytd-watch-metadata #title h1',
            // New UI fallbacks
            'ytd-watch-metadata yt-formatted-string[class*="title"]',
            '#container h1 yt-formatted-string',
            '#info-container h1'
        ];
        
        for (const selector of selectors) {
            const titleElement = document.querySelector(selector);
            if (titleElement && titleElement.textContent.trim()) {
                return titleElement.textContent.trim();
            }
        }
        
        return 'YouTube Video';
    }
    
    function getVideoId() {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('v');
    }
    
    function showTitle() {
        if (!titleOverlay || !isFullscreen) return;
        
        const title = getVideoTitle();
        titleOverlay.textContent = title;
        titleOverlay.style.opacity = '1';
        isTitleVisible = true;
    }
    
    function hideTitle() {
        if (!titleOverlay) return;
        
        titleOverlay.style.opacity = '0';
        isTitleVisible = false;
    }
    
    function isFullscreenMode() {
        return document.fullscreenElement || 
               document.webkitFullscreenElement ||
               document.mozFullScreenElement ||
               document.msFullscreenElement;
    }
    
    function areControlsVisible() {
        if (!isFullscreenMode()) return false;
        
        // Check for YouTube's fullscreen controls visibility
        const selectors = [
            '.ytp-chrome-controls',
            '.ytp-chrome-bottom',
            '.ytp-chrome-top',
            'ytd-watch-flexy[theater] .ytp-chrome-bottom', // Theater mode
            '#movie_player:not(.ytp-autohide)', // Controls always visible
            '#movie_player.ytp-hover' // Mouse hover state
        ];
        
        // Check if controls are explicitly hidden
        const hiddenControls = document.querySelector('.ytp-chrome-bottom[aria-hidden="true"]');
        if (hiddenControls) return false;
        
        // Check for hover state or non-autohide mode
        const moviePlayer = document.querySelector('#movie_player');
        if (moviePlayer) {
            const isHovering = moviePlayer.classList.contains('ytp-hover');
            const isAutohide = moviePlayer.classList.contains('ytp-autohide');
            
            if (isAutohide && !isHovering) return false;
        }
        
        // Check for visible control elements
        for (const selector of selectors) {
            const element = document.querySelector(selector);
            if (element && element.getBoundingClientRect().height > 0) {
                return true;
            }
        }
        
        return false;
    }
    
    function checkFullscreenState() {
        const wasFullscreen = isFullscreen;
        isFullscreen = isFullscreenMode();
        
        if (isFullscreen) {
            if (!wasFullscreen) {
                // Just entered fullscreen
                createTitleOverlay();
                checkControlsState();
            }
        } else {
            // Exited fullscreen - always hide title
            if (isTitleVisible) {
                hideTitle();
            }
        }
    }
    
    function checkControlsState() {
        if (!isFullscreen) {
            hideTitle();
            return;
        }
        
        if (areControlsVisible()) {
            if (!isTitleVisible) {
                showTitle();
            }
            // Reset hide timeout when controls are visible
            clearHideTimeout();
        } else {
            // Hide title when controls disappear
            if (isTitleVisible) {
                hideTitle();
            }
        }
    }
    
    let hideTimeout;
    function clearHideTimeout() {
        if (hideTimeout) {
            clearTimeout(hideTimeout);
        }
    }
    
    function setupEventListeners() {
        // Fullscreen change events
        document.addEventListener('fullscreenchange', checkFullscreenState);
        document.addEventListener('webkitfullscreenchange', checkFullscreenState);
        document.addEventListener('mozfullscreenchange', checkFullscreenState);
        document.addEventListener('MSFullscreenChange', checkFullscreenState);
        
        // Mouse movement in fullscreen
        document.addEventListener('mousemove', function() {
            if (!isFullscreen) return;
            
            clearHideTimeout();
            checkControlsState();
            
            // Set timeout to hide title when controls auto-hide
            hideTimeout = setTimeout(() => {
                if (isFullscreen && isTitleVisible) {
                    hideTitle();
                }
            }, 2000);
        });
        
        // Video events
        const video = document.querySelector('video');
        if (video) {
            video.addEventListener('play', checkControlsState);
            video.addEventListener('pause', checkControlsState);
            video.addEventListener('click', checkControlsState);
        }
        
        // YouTube player events
        const moviePlayer = document.querySelector('#movie_player');
        if (moviePlayer) {
            const observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutation) {
                    if (mutation.type === 'attributes' && 
                        mutation.attributeName === 'class') {
                        setTimeout(checkControlsState, 100);
                    }
                });
            });
            
            observer.observe(moviePlayer, {
                attributes: true,
                attributeFilter: ['class']
            });
        }
    }
    
    function checkForVideoChange() {
        const newVideoId = getVideoId();
        if (newVideoId && newVideoId !== currentVideoId) {
            currentVideoId = newVideoId;
            // Video changed, update title if visible
            if (isTitleVisible) {
                showTitle();
            }
        }
    }
    
    function initialize() {
        // Wait for YouTube to load
        if (!document.querySelector('#movie_player') && !document.querySelector('video')) {
            setTimeout(initialize, 1000);
            return;
        }
        
        createTitleOverlay();
        setupEventListeners();
        checkFullscreenState();
        
        // Check for video changes (navigation)
        setInterval(() => {
            checkForVideoChange();
            checkFullscreenState();
        }, 1000);
    }
    
    // Start when page loads
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }
    
    // Re-initialize on YouTube navigation (SPA)
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            setTimeout(initialize, 1000);
        }
    }).observe(document, { subtree: true, childList: true });
    
})();