B站自动宽屏居中

自动宽屏播放并将播放器垂直居中视口,退出宽屏/网页全屏/全屏模式自动滚动页面到顶部。默认关闭自动宽屏。

// ==UserScript==
// @name         B站自动宽屏居中
// @namespace    @ChatGPT
// @version      1.65
// @description  自动宽屏播放并将播放器垂直居中视口,退出宽屏/网页全屏/全屏模式自动滚动页面到顶部。默认关闭自动宽屏。
// @author       Gemini, wha4up (AI Optimized)
// @icon         data:image/webp;base64,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
// @license      MIT
// @match        https://*.bilibili.com/video/*
// @match        https://*.bilibili.com/list/*
// @match        https://*.bilibili.com/bangumi/play/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @run-at       document-idle
// @supportURL   https://greasyfork.org/zh-CN/scripts/492413-b%E7%AB%99%E8%87%AA%E5%8A%A8%E5%AE%BD%E5%B1%8F%E5%B1%85%E4%B8%AD/feedback
// @homepageURL  https://greasyfork.org/zh-CN/scripts/492413-b%E7%AB%99%E8%87%AA%E5%8A%A8%E5%AE%BD%E5%B1%8F%E5%B1%85%E4%B8%AD
// ==/UserScript==

(function () {
    'use strict';

    // --- 配置项 ---
    const PLAYER_CENTER_OFFSET = 75;      // 播放器垂直居中时的偏移量 (像素)
    const CHECK_INTERVAL = 500;           // (waitForElement) 查找元素的间隔时间 (ms)
    const MAX_ATTEMPTS = 20;              // (waitForElement) 查找元素的最大尝试次数
    const DEBOUNCE_DELAY = 200;           // 事件防抖延迟 (ms)
    const URL_CHECK_DELAY = 500;          // URL 变化后执行逻辑的延迟 (ms)
    const FINAL_CHECK_DELAY = 300;        // 初始化或导航后最终检查状态的延迟 (ms)
    const SCROLL_ANIMATION_DURATION = 500;// 预估的平滑滚动动画时长 (ms)
    const OBSERVER_MAX_WAIT_TIME = 15000; // MutationObserver 最长等待时间 (15秒)
    const SCRIPT_VERSION = '1.68-final-review'; // 脚本版本,用于日志记录

    // --- 状态变量 ---
    let elements = {
        wideBtn: null,
        webFullBtn: null,
        fullBtn: null,
        player: null,
        playerContainer: null,
    };
    let isEnabled = GM_getValue('enableWideScreen', false); // 自动宽屏功能是否启用
    let currentUrl = window.location.href;                  // 当前页面的完整URL
    let initTimeout = null;                                 // 初始化延迟计时器
    let reInitScheduled = false;                            // 是否已计划重新初始化
    let lastScrollTime = 0;                                 // 上次滚动时间,用于滚动节流
    let isScrolling = false;                                // 是否正在执行平滑滚动
    let currentMenuCommandText = '';                        // 当前注册的油猴菜单命令文本

    let coreElementsObserver = null;                        // 用于核心元素加载的 MutationObserver
    let observerTimeoutId = null;                           // MutationObserver 的安全超时计时器

    // --- 工具函数 ---

    /**
     * 防抖函数:在事件触发后等待指定延迟,若期间无新触发则执行函数。
     * @param {Function} func 需要防抖的函数
     * @param {number} delay 延迟时间 (ms)
     * @returns {Function} 防抖处理后的函数
     */
    function debounce(func, delay) {
        let timeoutId;
        return function(...args) {
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => {
                func.apply(this, args);
            }, delay);
        };
    }

    /**
     * (辅助函数) 通过轮询等待指定元素出现在 DOM 中。
     * 主要用于 MutationObserver 机制之外的特定元素等待场景。
     * @param {string} selector CSS选择器
     * @param {Function} callback 元素找到后的回调函数,参数为找到的元素或null
     * @param {number} interval 检查间隔 (ms)
     * @param {number} maxAttempts 最大尝试次数
     */
    function waitForElement(selector, callback, interval = CHECK_INTERVAL, maxAttempts = MAX_ATTEMPTS) {
        let attempts = 0;
        // console.log(`[B站自动宽屏居中] (waitForElement) 开始等待元素 "${selector}"`); // 调试时可取消注释
        let intervalId = setInterval(() => {
            const element = document.querySelector(selector);
            if (element) {
                // console.log(`[B站自动宽屏居中] (waitForElement) 元素 "${selector}" 已找到`); // 调试时可取消注释
                clearInterval(intervalId);
                callback(element);
            } else {
                attempts++;
                if (attempts >= maxAttempts) {
                    clearInterval(intervalId);
                    console.warn(`[B站自动宽屏居中] (waitForElement) 元素 "${selector}" 未在 ${maxAttempts * interval}ms 内找到。`);
                    if (typeof callback === 'function') callback(null);
                }
            }
        }, interval);
    }

    /**
     * 平滑滚动到指定的垂直位置 (带节流)。
     * @param {number} topPosition 目标垂直滚动位置
     */
    function scrollToPosition(topPosition) {
        if (isScrolling) return; // 如果已在滚动中,则忽略新的滚动请求
        const now = Date.now();
        // 简单的节流,避免过于频繁的滚动请求
        if (now - lastScrollTime < 100 && Math.abs(window.scrollY - topPosition) < 50) {
            return;
        }
        lastScrollTime = now;
        isScrolling = true;
        window.scrollTo({
            top: topPosition,
            behavior: 'smooth'
        });
        // 预估滚动动画完成时间,在此期间阻止新的滚动
        setTimeout(() => {
            isScrolling = false;
        }, SCROLL_ANIMATION_DURATION);
    }

    /** 滚动页面使播放器大致垂直居中于视口。 */
    function scrollToPlayer() {
        // 确保播放器元素已缓存,如果未缓存则尝试缓存
        if (!elements.player && !cacheElements()) {
             console.warn("[B站自动宽屏居中] scrollToPlayer: 播放器元素未缓存且重新缓存失败。");
             return;
        }
        // 再次检查,确保 elements.player 有效
        if (!elements.player) {
            console.error("[B站自动宽屏居中] scrollToPlayer: 播放器元素 (elements.player) 仍然无效。");
            return;
        }
        // 使用 requestAnimationFrame 优化滚动性能
        requestAnimationFrame(() => {
            const playerRect = elements.player.getBoundingClientRect();
            if (playerRect.height > 0) { // 仅当播放器有高度时执行
                const playerTop = playerRect.top + window.scrollY;
                const desiredScrollTop = playerTop - PLAYER_CENTER_OFFSET;
                // 仅当当前滚动位置与目标位置有显著差异时才滚动
                if (Math.abs(window.scrollY - desiredScrollTop) > 5) {
                    scrollToPosition(desiredScrollTop);
                }
            }
        });
    }

    /** 滚动到页面顶部。 */
    function scrollToTop() {
        if (window.scrollY > 0) {
            scrollToPosition(0);
        }
    }

    /**
     * 缓存播放器及相关的控制按钮等核心DOM元素。
     * @returns {boolean} 如果核心元素(播放器和宽屏按钮)成功缓存则返回true,否则返回false。
     */
    function cacheElements() {
        // console.log("[B站自动宽屏居中] 开始缓存元素 (cacheElements)..."); // 调试时可取消注释
        elements.player = document.querySelector('#bilibili-player');
        if (!elements.player) {
            console.warn("[B站自动宽屏居中] cacheElements: 核心播放器元素 '#bilibili-player' 未找到。");
            return false; // 播放器是必需的
        }

        // 查找播放器容器,有多种可能的选择器
        elements.playerContainer = document.querySelector('.bpx-player-container') ||
                                   document.querySelector('#bilibiliPlayer') || // 兼容旧版选择器
                                   elements.player; // 最差情况回退到播放器主元素

        // 在播放器容器内查找控制按钮
        if (elements.playerContainer) {
            elements.wideBtn = elements.playerContainer.querySelector('.bpx-player-ctrl-wide');
            elements.webFullBtn = elements.playerContainer.querySelector('.bpx-player-ctrl-web');
            elements.fullBtn = elements.playerContainer.querySelector('.bpx-player-ctrl-full');
        } else {
            // 如果播放器容器未明确找到(理论上不太可能,因为有 fallback),尝试全局查找按钮
            console.warn("[B站自动宽屏居中] cacheElements: 'elements.playerContainer' 未明确找到,尝试全局查找按钮。");
            elements.wideBtn = document.querySelector('.bpx-player-ctrl-wide');
            elements.webFullBtn = document.querySelector('.bpx-player-ctrl-web');
            elements.fullBtn = document.querySelector('.bpx-player-ctrl-full');
        }

        if (!elements.wideBtn) {
            console.warn("[B站自动宽屏居中] cacheElements: 未找到宽屏按钮 '.bpx-player-ctrl-wide'。");
            return false; // 宽屏按钮对于核心功能也是必需的
        }

        // 网页全屏和全屏按钮不是核心功能所必需的,仅记录警告
        if (!elements.webFullBtn) {
            // console.warn("[B站自动宽屏居中] cacheElements: 未找到网页全屏按钮 '.bpx-player-ctrl-web'.");
        }
        if (!elements.fullBtn) {
            // console.warn("[B站自动宽屏居中] cacheElements: 未找到全屏按钮 '.bpx-player-ctrl-full'.");
        }
        return true; // 播放器和宽屏按钮都找到,视为成功
    }

    /** 检查播放器当前的宽屏/全屏状态,并据此执行相应的滚动操作。 */
    function checkAndScroll() {
        // 确保核心元素已缓存
        if (!elements.player || !elements.wideBtn) {
            if (!cacheElements()) { // 如果未缓存,尝试再次缓存
                console.error("[B站自动宽屏居中] checkAndScroll: 核心元素缓存失败,无法执行滚动逻辑。");
                return;
            }
        }

        const isWide = elements.wideBtn.classList.contains('bpx-state-entered');
        const isWebFull = elements.webFullBtn && elements.webFullBtn.classList.contains('bpx-state-entered');
        const isFull = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);

        // 根据状态执行滚动
        if (isWide && !isWebFull && !isFull) { // 仅在宽屏模式(非网页全屏/全屏)下居中播放器
            scrollToPlayer();
        } else if (!isWide && !isWebFull && !isFull) { // 非任何特殊模式时,滚动到顶部
            scrollToTop();
        }
        // 其他情况(网页全屏/全屏)不执行滚动
    }

    /** 防抖处理的 checkAndScroll,用于 resize 事件,避免频繁触发。 */
    const debouncedCheckAndScroll = debounce(checkAndScroll, DEBOUNCE_DELAY);

    /** 如果启用了自动宽屏,确保播放器处于宽屏模式。 */
    function ensureWideMode() {
        if (!isEnabled) return; // 如果未启用自动宽屏,则不执行任何操作

        // 确保宽屏按钮已缓存
        if (!elements.wideBtn && !cacheElements()) {
             console.error("[B站自动宽屏居中] ensureWideMode: 宽屏按钮无法缓存。");
             return;
        }
        if (!elements.wideBtn) { // 再次检查
            console.error("[B站自动宽屏居中] ensureWideMode: 宽屏按钮 (elements.wideBtn) 仍然无效。");
            return;
        }

        const isCurrentlyWide = elements.wideBtn.classList.contains('bpx-state-entered');
        const isWebFull = elements.webFullBtn && elements.webFullBtn.classList.contains('bpx-state-entered');
        const isFull = !!(document.fullscreenElement || document.webkitFullscreenElement);

        // 如果当前不是宽屏,也不是网页全屏或全屏,则点击宽屏按钮
        if (!isCurrentlyWide && !isWebFull && !isFull) {
            elements.wideBtn.click();
            setTimeout(checkAndScroll, 200); // 点击后稍作延迟再检查滚动状态
        } else if (isCurrentlyWide && !isWebFull && !isFull) {
            // 如果已经是宽屏模式(且非其他全屏),则执行一次检查滚动
            checkAndScroll();
        }
    }

    /** 设置事件监听器。 */
    function setupListeners() {
        removeListenersAndObserver(); // 先移除旧的监听器和Observer,确保清洁状态
        console.log("[B站自动宽屏居中] setupListeners: 开始设置事件监听器。");

        if (!cacheElements()) { // 确保元素已缓存
            console.error("[B站自动宽屏居中] setupListeners: 核心元素查找失败,无法设置监听器。");
            return;
        }

        // 为播放器控制按钮添加点击事件监听
        elements.wideBtn.addEventListener('click', handleWideBtnClick);
        if (elements.webFullBtn) elements.webFullBtn.addEventListener('click', checkAndScroll);
        if (elements.fullBtn) elements.fullBtn.addEventListener('click', checkAndScroll);

        // 为视频区域添加双击事件监听(双击通常也可能改变全屏状态)
        const videoArea = elements.playerContainer?.querySelector('.bpx-player-video-area');
        if (videoArea) videoArea.addEventListener('dblclick', checkAndScroll);

        // 监听全屏状态变化事件
        document.addEventListener('fullscreenchange', checkAndScroll);
        document.addEventListener('webkitfullscreenchange', checkAndScroll); // 兼容 WebKit 内核
        document.addEventListener('mozfullscreenchange', checkAndScroll);    // 兼容 Firefox
        document.addEventListener('MSFullscreenChange', checkAndScroll);     // 兼容 IE/Edge (旧版)

        // 监听键盘事件 (主要用于处理 ESC 键退出全屏)
        document.addEventListener('keydown', handleKeyPress);
        // 监听窗口大小变化事件
        window.addEventListener('resize', debouncedCheckAndScroll);
        console.log("[B站自动宽屏居中] setupListeners: 事件监听器设置完成。");
    }

    /** 移除所有已添加的事件监听器和 MutationObserver。 */
    function removeListenersAndObserver() {
        console.log("[B站自动宽屏居中] removeListenersAndObserver: 开始清理监听器和Observer。");

        // 移除按钮点击事件
        if (elements.wideBtn) elements.wideBtn.removeEventListener('click', handleWideBtnClick);
        if (elements.webFullBtn) elements.webFullBtn.removeEventListener('click', checkAndScroll);
        if (elements.fullBtn) elements.fullBtn.removeEventListener('click', checkAndScroll);

        // 移除视频区域双击事件 (需要重新获取容器以防 elements 对象被清空)
        const currentContainer = elements.playerContainer || document.querySelector('.bpx-player-container') || document.querySelector('#bilibiliPlayer');
        const videoArea = currentContainer?.querySelector('.bpx-player-video-area');
        if (videoArea) videoArea.removeEventListener('dblclick', checkAndScroll);

        // 移除全屏状态变化事件
        document.removeEventListener('fullscreenchange', checkAndScroll);
        document.removeEventListener('webkitfullscreenchange', checkAndScroll);
        document.removeEventListener('mozfullscreenchange', checkAndScroll);
        document.removeEventListener('MSFullscreenChange', checkAndScroll);

        // 移除键盘和窗口大小变化事件
        document.removeEventListener('keydown', handleKeyPress);
        window.removeEventListener('resize', debouncedCheckAndScroll);

        // 断开并清理 MutationObserver
        if (coreElementsObserver) {
            coreElementsObserver.disconnect();
            coreElementsObserver = null;
            console.log("[B站自动宽屏居中] CoreElements MutationObserver 已断开。");
        }
        if (observerTimeoutId) {
            clearTimeout(observerTimeoutId);
            observerTimeoutId = null;
        }

        // 重置缓存的元素对象
        elements = { wideBtn: null, webFullBtn: null, fullBtn: null, player: null, playerContainer: null };
        console.log("[B站自动宽屏居中] removeListenersAndObserver: 清理完成。");
    }

    /** 处理键盘按下事件,主要用于检测 ESC 键。 */
    function handleKeyPress(event) {
        if (event.key === 'Escape') {
            // ESC 键通常用于退出全屏或网页全屏,延迟检查状态
            setTimeout(checkAndScroll, 150);
        }
    }

    /** 注册或更新油猴菜单命令,用于切换自动宽屏功能的启用状态。 */
    function registerMenuCommand() {
        // 确保 GM API 可用
        if (typeof GM_registerMenuCommand !== 'function' || typeof GM_unregisterMenuCommand !== 'function') return;

        const newCommandText = `自动宽屏模式 (当前: ${isEnabled ? '✅ 开启' : '❌ 关闭'})`;
        // 如果菜单文本发生变化,先尝试注销旧命令
        if (currentMenuCommandText && currentMenuCommandText !== newCommandText) {
            try { GM_unregisterMenuCommand(currentMenuCommandText); } catch (e) {
                console.warn("[B站自动宽屏居中] 注销旧菜单命令失败:", e);
            }
        }
        // 注册新命令
        try {
            GM_registerMenuCommand(newCommandText, toggleWideScreen);
            currentMenuCommandText = newCommandText; // 更新当前命令文本记录
        } catch (e) {
            console.error("[B站自动宽屏居中] 注册新菜单命令失败:", e);
        }
    }

    /** 切换自动宽屏功能的启用/禁用状态。 */
    function toggleWideScreen() {
        const intendedState = !GM_getValue('enableWideScreen', false); // 获取预期的下一个状态
        // 弹出确认框,让用户确认操作
        if (window.confirm(`是否要${intendedState ? "开启" : "关闭"}自动宽屏模式?`)) {
            isEnabled = intendedState;
            GM_setValue('enableWideScreen', isEnabled); // 保存设置
            registerMenuCommand(); // 更新菜单显示

            if (isEnabled) { // 如果开启自动宽屏
                ensureWideMode(); // 确保进入宽屏模式
            } else { // 如果关闭自动宽屏
                // 如果当前处于宽屏模式,则模拟点击宽屏按钮退出宽屏
                if (elements.wideBtn && elements.wideBtn.classList.contains('bpx-state-entered')) {
                    elements.wideBtn.click();
                }
                setTimeout(checkAndScroll, 100); // 稍作延迟后检查滚动状态
            }
        }
    }

    /** 处理宽屏按钮被点击的事件。 */
    function handleWideBtnClick() {
        checkAndScroll(); // 立即检查一次
        setTimeout(checkAndScroll, 200); // 延迟后再次检查,确保状态更新
    }

    /**
     * 核心初始化逻辑:尝试缓存元素,如果失败则使用 MutationObserver 等待元素加载。
     */
    function initializeScriptLogic() {
        reInitScheduled = false; // 重置重新初始化计划标志
        clearTimeout(initTimeout); // 清除可能存在的初始化延迟计时器

        // 清理任何可能存在的旧 Observer 和其超时
        if (coreElementsObserver) { coreElementsObserver.disconnect(); coreElementsObserver = null; }
        if (observerTimeoutId) { clearTimeout(observerTimeoutId); observerTimeoutId = null; }

        console.log("[B站自动宽屏居中] initializeScriptLogic: 开始初始化脚本逻辑...");

        // 1. 尝试立即缓存核心元素
        if (cacheElements()) {
            console.log("[B站自动宽屏居中] initializeScriptLogic: 核心元素已通过初次尝试成功缓存。");
            setupListeners();      // 设置事件监听
            if (isEnabled) ensureWideMode(); // 如果启用,则确保宽屏
            setTimeout(checkAndScroll, FINAL_CHECK_DELAY); // 最终状态检查
            return; // 初始化成功,结束
        }

        // 2. 如果初次缓存失败,则设置 MutationObserver 等待元素出现
        console.log("[B站自动宽屏居中] initializeScriptLogic: 初次缓存失败,设置 MutationObserver。");

        const observerCallback = function(mutationsList, observerInstance) {
            // 检查核心元素(播放器和宽屏按钮)是否已出现在DOM中
            if (document.querySelector('#bilibili-player') && document.querySelector('.bpx-player-ctrl-wide')) {
                console.log("[B站自动宽屏居中] MutationObserver 检测到变化,尝试重新缓存元素。");
                if (cacheElements()) { // 再次尝试缓存
                    console.log("[B站自动宽屏居中] MutationObserver 触发: 核心元素已成功缓存。");
                    observerInstance.disconnect();      // 成功找到,停止观察
                    clearTimeout(observerTimeoutId);    // 清除安全超时
                    coreElementsObserver = null;        // 清理 observer 实例变量
                    observerTimeoutId = null;

                    setupListeners();                   // 设置事件监听
                    if (isEnabled) ensureWideMode();    // 如果启用,则确保宽屏
                    setTimeout(checkAndScroll, FINAL_CHECK_DELAY); // 最终状态检查
                } else {
                    // 此情况理论上较少发生:querySelector 找到了基本元素,但 cacheElements 内部的完整检查仍失败
                    console.warn("[B站自动宽屏居中] MutationObserver 触发: querySelector 找到基本元素,但 cacheElements 完整检查失败。");
                }
            }
        };

        coreElementsObserver = new MutationObserver(observerCallback);

        // 选择一个合适的父节点进行观察,目标是尽早发现播放器相关元素的注入
        // 尝试的顺序:#playerWrap -> #mirror-vdcon -> #app -> document.body
        let targetNodeToObserve = document.getElementById('playerWrap') ||
                                  document.getElementById('mirror-vdcon') ||
                                  document.getElementById('app') ||
                                  document.body; // 最差情况观察整个 body

        console.log("[B站自动宽屏居中] MutationObserver 将开始观察节点:", targetNodeToObserve.id || targetNodeToObserve.tagName);
        coreElementsObserver.observe(targetNodeToObserve, { childList: true, subtree: true });

        // 设置一个最长等待超时,防止 MutationObserver 无限期运行
        observerTimeoutId = setTimeout(() => {
            if (coreElementsObserver) { // 检查 Observer 是否仍然存在 (可能已被成功回调清除)
                console.error(`[B站自动宽屏居中] MutationObserver 超时 (${OBSERVER_MAX_WAIT_TIME}ms): 未能找到或缓存核心元素。`);
                coreElementsObserver.disconnect();
                coreElementsObserver = null;
            }
        }, OBSERVER_MAX_WAIT_TIME);
    }

    /**
     * 安排脚本的重新初始化,通常在检测到页面导航(URL路径变化)时调用。
     * @param {number} delay 重新初始化前的延迟时间 (ms)
     */
    function scheduleReInitialization(delay = URL_CHECK_DELAY) {
        if (reInitScheduled) return; // 如果已安排,则不再重复安排
        reInitScheduled = true;
        clearTimeout(initTimeout); // 清除之前的延迟计时器
        console.log(`[B站自动宽屏居中] scheduleReInitialization: 安排在 ${delay}ms 后重新初始化。`);
        initTimeout = setTimeout(() => {
            removeListenersAndObserver(); // 清理旧的监听器和状态
            setTimeout(initializeScriptLogic, 100); // 短暂延迟后开始新的初始化
        }, delay);
    }

    /**
     * 检查给定的URL是否匹配脚本的目标页面规则。
     * @param {string} url 要检查的URL
     * @returns {boolean} 如果是目标页面则返回true,否则返回false
     */
    function isTargetPage(url) {
        return /\/(video|list|bangumi\/play)\//.test(url); // 匹配视频页、列表页、番剧播放页
    }

    /**
     * 处理URL发生变化(包括SPA导航和历史记录变化)。
     * 主要通过比较URL的pathname部分来判断是否需要重新初始化脚本。
     */
    function handleUrlChange() {
        // 使用 requestAnimationFrame 确保在浏览器下一次重绘前执行,优化性能
        requestAnimationFrame(() => {
            const newHref = window.location.href;
            const newPathname = window.location.pathname;

            let oldPathnameFromCurrentUrl = '/'; // 默认值,以防 currentUrl 解析失败
            if (currentUrl) { // 确保 currentUrl 有值
                try {
                    // currentUrl 存储的是上一次检查时的完整 href
                    oldPathnameFromCurrentUrl = new URL(currentUrl).pathname;
                } catch (e) {
                    console.warn('[B站自动宽屏居中] 解析旧URL的pathname失败:', currentUrl, e);
                    // 备用方案:尝试从旧的完整URL中提取路径部分
                    const doubleSlashIndex = currentUrl.indexOf('//');
                    if (doubleSlashIndex !== -1) {
                        const pathStartIndex = currentUrl.indexOf('/', doubleSlashIndex + 2);
                        if (pathStartIndex !== -1) {
                            const queryIndex = currentUrl.indexOf('?', pathStartIndex);
                            const hashIndex = currentUrl.indexOf('#', pathStartIndex);
                            let endIndex = currentUrl.length;
                            if (queryIndex !== -1) endIndex = queryIndex;
                            if (hashIndex !== -1 && hashIndex < endIndex) endIndex = hashIndex;
                            oldPathnameFromCurrentUrl = currentUrl.substring(pathStartIndex, endIndex);
                        }
                    }
                }
            }

            // 仅当 URL 的路径部分 (pathname) 发生变化时,才认为需要重新初始化
            if (newPathname !== oldPathnameFromCurrentUrl) {
                console.log(`[B站自动宽屏居中] Pathname 变化: 从 "${oldPathnameFromCurrentUrl}" 到 "${newPathname}". 触发重新初始化.`);
                const previousFullUrl = currentUrl; // 保存变化前的完整URL,用于isTargetPage判断
                currentUrl = newHref; // 更新当前URL记录为新的完整URL

                const wasTarget = isTargetPage(previousFullUrl);
                const isNowTarget = isTargetPage(newHref);

                if (isNowTarget) { // 如果新URL是目标页面
                    scheduleReInitialization();
                } else if (wasTarget && !isNowTarget) { // 如果从目标页面导航到非目标页面
                    console.log("[B站自动宽屏居中] 从目标页面导航到非目标页面 (Pathname change),移除监听器。");
                    removeListenersAndObserver();
                    clearTimeout(initTimeout); // 取消任何待处理的重新初始化
                    reInitScheduled = false;
                }
            } else if (newHref !== currentUrl) {
                // Pathname 未变,但完整 URL (可能查询参数或哈希值) 变了。
                // 此时仅更新 currentUrl 记录,不触发重新初始化。
                // console.log(`[B站自动宽屏居中] URL (query/hash) 变化,Pathname 未变. 不重新初始化.`); // 调试时可取消注释
                currentUrl = newHref;
            }
        });
    }

    /** 脚本主入口函数。 */
    function main() {
        console.log(`[B站自动宽屏居中] 脚本开始执行 (main)。版本: ${SCRIPT_VERSION}`);
        isEnabled = GM_getValue('enableWideScreen', false); // 读取用户保存的设置
        registerMenuCommand(); // 注册油猴菜单

        // 监听浏览器历史记录变化 (前进/后退按钮)
        window.addEventListener('popstate', handleUrlChange);

        // 劫持 history.pushState 和 history.replaceState 以监听SPA导航
        const originalPushState = history.pushState;
        history.pushState = function(...args) {
            const result = originalPushState.apply(this, args);
            window.dispatchEvent(new CustomEvent('historystatechanged')); // 触发自定义事件
            return result;
        };
        const originalReplaceState = history.replaceState;
        history.replaceState = function(...args) {
            const result = originalReplaceState.apply(this, args);
            window.dispatchEvent(new CustomEvent('historystatechanged')); // 触发自定义事件
            return result;
        };
        // 监听自定义的 history state 变化事件
        window.addEventListener('historystatechanged', handleUrlChange);

        // 初始加载时,如果当前页面是目标页面,则初始化脚本
        if (isTargetPage(currentUrl)) { // currentUrl 已在顶部初始化
            initializeScriptLogic();
        }

        // 页面卸载时执行清理操作
        window.addEventListener('unload', () => {
            removeListenersAndObserver();
            history.pushState = originalPushState;
            history.replaceState = originalReplaceState;
            window.removeEventListener('historystatechanged', handleUrlChange);
            window.removeEventListener('popstate', handleUrlChange);
            clearTimeout(initTimeout);
        });
    }

    // --- 启动脚本 ---
    // 等待 DOM 内容加载完成后执行 main 函数
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', main);
    } else {
        main(); // 如果 DOM 已加载,则直接执行
    }
})();