Greasy Fork

Greasy Fork is available in English.

哔哩哔哩自动画质

自动解锁并更改哔哩哔哩视频的画质和音质及直播画质,实现自动选择最高画质、无损音频、杜比全景声。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         哔哩哔哩自动画质
// @namespace    https://github.com/AHCorn/Bilibili-Auto-Quality/
// @version      5.3
// @license      GPL-3.0
// @description  自动解锁并更改哔哩哔哩视频的画质和音质及直播画质,实现自动选择最高画质、无损音频、杜比全景声。
// @author       安和(AHCorn)
// @icon         https://www.bilibili.com/favicon.ico
// @match        *://www.bilibili.com/video/*
// @match        *://www.bilibili.com/list/*
// @match        *://www.bilibili.com/blackboard/*
// @match        *://www.bilibili.com/watchlater/*
// @match        *://www.bilibili.com/bangumi/*
// @match        *://www.bilibili.com/watchroom/*
// @match        *://www.bilibili.com/medialist/*
// @match        *://bangumi.bilibili.com/*
// @match        *://live.bilibili.com/*
// @exclude      *://live.bilibili.com/
// @exclude      *://live.bilibili.com/p/*
// @noframes
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// ==/UserScript==

(async function () {
    "use strict";
    if (typeof unsafeWindow === "undefined") { unsafeWindow = window; }
    const state = {
        hiResAudioEnabled: GM_getValue("hiResAudio", false),
        dolbyAtmosEnabled: GM_getValue("dolbyAtmos", false),
        userQualitySetting: GM_getValue("qualitySetting", "最高画质"),
        userBackupQualitySetting: GM_getValue("backupQualitySetting", "最高画质"),
        useHighestQualityFallback: GM_getValue("useHighestQualityFallback", true),
        activeQualityTab: GM_getValue("activeQualityTab", "primary"),
        takeOverQualityControl: GM_getValue("takeOverQualityControl", false),
        // 解锁相关设置
        unlockUA: GM_getValue("unlockUA", false),
        unlockHDR: GM_getValue("unlockHDR", false),
        unlockMarker: GM_getValue("unlockMarker", true),
        disableHDROption: GM_getValue("disableHDR", false),
        isVipUser: false,
        vipStatusChecked: false,
        isLoading: true,
        isLivePage: false,
        userLiveQualitySetting: GM_getValue("liveQualitySetting", "最高画质"),
        userLiveDecodeSetting: GM_getValue("liveDecodeSetting", "默认"),
        userVideoDecodeSetting: GM_getValue("videoDecodeSetting", "默认"),
        decodeSettingEnabled: GM_getValue("decodeSettingEnabled", false),
        devModeEnabled: GM_getValue("devModeEnabled", false),
        devModeVipStatus: GM_getValue("devModeVipStatus", "默认"),
        devModeNoLoginStatus: GM_getValue("devModeNoLoginStatus", false),
        preserveTouchPoints: GM_getValue("preserveTouchPoints", false),
        devModeAudioRetries: GM_getValue("devModeAudioRetries", 2),
        devModeAudioDelay: GM_getValue("devModeAudioDelay", 5000),
        devDoubleCheckDelay: GM_getValue("devDoubleCheckDelay", 10000),
        devAllowFreeVipQualities: GM_getValue("devAllowFreeVipQualities", false),
        injectQualityButton: GM_getValue("injectQualityButton", true),
        qualityDoubleCheck: GM_getValue("qualityDoubleCheck", true),
        liveQualityDoubleCheck: GM_getValue("liveQualityDoubleCheck", true),
        sessionCache: {
            vipStatus: null,
            vipChecked: false
        }
    };
    // 应用解锁相关本地标记
    try {
        if (state.unlockHDR) {
            window.localStorage.bilibili_player_force_hdr = 1;
        }
        if (state.unlockMarker) {
            const baseKey = 'bilibili_player_force_DolbyAtmos&8K';
            const hdrKey = baseKey + '&HDR';
            const finalKey = state.unlockHDR ? hdrKey : baseKey;
            window.localStorage[finalKey] = 1;
        }
    } catch (e) {
        console.warn('[解锁设置] 写入本地标记失败:', e);
    }
    function detectPointerType() {
        try {
            const hasFinePointer = window.matchMedia('(pointer: fine)').matches;
            const hasCoarsePointer = window.matchMedia('(pointer: coarse)').matches;
            const anyHover = window.matchMedia('(any-hover: hover)').matches;
            const supportsTouch = ('ontouchstart' in window) || navigator.maxTouchPoints > 0;
            console.log(`[解锁设置] 设备检测 Pointer: fine=${hasFinePointer}, coarse=${hasCoarsePointer}`);
            console.log(`[解锁设置] 设备检测 Hover: ${anyHover}`);
            console.log(`[解锁设置] 设备检测 Touch: ${supportsTouch}`);
            return {
                isMouseDevice: hasFinePointer && anyHover,
                isTouchDevice: hasCoarsePointer && supportsTouch
            };
        } catch (error) {
            console.error("[解锁设置] 设备检测失败,返回默认桌面模式");
            return { isMouseDevice: true, isTouchDevice: false };
        }
    }
    try {
        if (state.unlockUA) {
            Object.defineProperty(navigator, 'userAgent', {
                value: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Safari/605.1.15",
                configurable: true
            });
            Object.defineProperty(navigator, 'platform', {
                value: "MacIntel",
                configurable: true
            });
            console.log("[解锁设置] UA 和平台标识修改成功");
        } else {
            console.log("[解锁设置] 未开启 UA 修改");
        }
        if (state.unlockUA && state.devModeEnabled && state.preserveTouchPoints) {
            console.log("[解锁设置] 已启用保留触控点,跳过 maxTouchPoints 修改");
        } else if (state.unlockUA) {
            const pointerType = detectPointerType();
            console.log(`[解锁设置] 设备检测结果: 鼠标=${pointerType.isMouseDevice}, 触控=${pointerType.isTouchDevice}`);
            if (pointerType.isMouseDevice && !pointerType.isTouchDevice) {
                Object.defineProperty(navigator, 'maxTouchPoints', {
                    value: 0,
                    configurable: true
                });
                console.log("[解锁设置] 纯鼠标设备,已设置 maxTouchPoints 为 0");
            } else {
                console.log(`[解锁设置] 保留 maxTouchPoints 原值: ${navigator.maxTouchPoints},原因: ` +
                            `${pointerType.isTouchDevice ? "检测到触控设备" : "无精确鼠标指针"}`);
            }
        }
    } catch (error) {
        console.error("[解锁设置] 修改 UA 或 maxTouchPoints 失败:", error);
    }
    GM_addStyle(`
    #bilibili-quality-selector, #bilibili-live-quality-selector, #bilibili-dev-settings, #bilibili-unlock-settings, #bilibili-decode-settings {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: linear-gradient(135deg, #f6f8fa, #e9ecef);
        border-radius: 24px;
        box-shadow: 0 20px 50px rgba(0, 0, 0, 0.14), 0 8px 24px rgba(0, 0, 0, 0.10);
        padding: 30px;
        width: 90%;
        max-width: 400px;
        max-height: 85vh;
        overflow-y: auto;
        overflow-x: hidden;
        display: none;
        z-index: 10000;
        font-family: 'Segoe UI', 'Roboto', sans-serif;
        transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
        scrollbar-width: thin;
        scrollbar-color: rgba(0, 161, 214, 0.3) transparent;
    }
    .quality-tabs {
        display: flex;
        margin-bottom: 20px;
        border-radius: 12px;
        background: #e8eaed;
        padding: 4px;
    }
    .quality-tab {
        flex: 1;
        padding: 8px;
        text-align: center;
        cursor: pointer;
        border-radius: 8px;
        transition: all 0.3s ease;
        color: #666;
        font-weight: 600;
    }
    .quality-tab.active { background: transparent; }
    .quality-section { display: none; }
    .quality-section.active { display: block; }
    .quality-tabs { position: relative; }
    .quality-tabs .tab-indicator {
        position: absolute;
        top: 4px; left: 4px;
        width: calc(50% - 8px);
        height: calc(100% - 8px);
        border-radius: 8px;
        background: #00a1d6;
        transition: transform 0.35s ease, background-color 0.35s ease;
        z-index: 0;
    }
    .quality-tabs .quality-tab { position: relative; z-index: 1; }
    .quality-tabs[data-active="primary"] .tab-indicator { transform: translateX(0); background: #00a1d6; }
    .quality-tabs[data-active="backup"] .tab-indicator { transform: translateX(100%); background: #f25d8e; }
    .quality-tabs[data-active="primary"] .quality-tab[data-tab="primary"],
    .quality-tabs[data-active="backup"] .quality-tab[data-tab="backup"] { color: #fff; }
    .vip-status-section {
        background: #ffffff;
        border-radius: 16px;
        padding: 15px;
        margin-bottom: 16px;
        border: 1px solid #e5e7eb;
        transition: all 0.2s ease;
        box-shadow: 0 1px 2px rgba(0,0,0,0.05), 0 1px 1px rgba(0,0,0,0.02);
    }

    .vip-status-title {
        font-size: 16px;
        color: #3c4043;
        font-weight: 600;
        margin-bottom: 4px;
    }
    .vip-status-description {
        font-size: 13px;
        color: #666;
        margin-bottom: 12px;
        line-height: 1.4;
    }
    .vip-status-tabs {
        display: flex;
        border-radius: 12px;
        background: #e8eaed;
        padding: 4px;
        position: relative;
    }
    .vip-status-tabs.disabled {
        opacity: 0.6;
        cursor: not-allowed;
    }
    .vip-tab-indicator {
        position: absolute;
        top: 4px;
        left: 4px;
        width: calc((100% - 8px) / 3);
        height: calc(100% - 8px);
        border-radius: 8px;
        background: #00a1d6;
        transition: transform 0.35s ease, background-color 0.35s ease;
        z-index: 0;
    }
    .vip-status-tab {
        flex: 1;
        padding: 8px;
        text-align: center;
        cursor: pointer;
        border-radius: 8px;
        transition: all 0.3s ease;
        color: #666;
        font-weight: 600;
        position: relative;
        z-index: 1;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .vip-status-tabs.disabled .vip-status-tab {
        cursor: not-allowed;
    }
    .vip-status-tabs[data-active="默认"] .vip-tab-indicator { transform: translateX(0); background: #00a1d6; }
    .vip-status-tabs[data-active="普通"] .vip-tab-indicator { transform: translateX(100%); background: #666; }
    .vip-status-tabs[data-active="会员"] .vip-tab-indicator { transform: translateX(200%); background: #f25d8e; }
    .vip-status-tabs[data-active="默认"] .vip-status-tab[data-status="默认"],
    .vip-status-tabs[data-active="普通"] .vip-status-tab[data-status="普通"],
    .vip-status-tabs[data-active="会员"] .vip-status-tab[data-status="会员"] { color: #fff; }
    .quality-button-hidden {
        display: none !important;
    }
    .toggle-switch.hide {
        display: none;
    }
    .toggle-switch.show {
        display: flex;
    }
    #bilibili-quality-selector h2, #bilibili-live-quality-selector h2, #bilibili-decode-settings h2 {
        margin: 0 0 20px;
        color: #00a1d6;
        font-size: 28px;
        text-align: center;
        font-weight: 700;
    }
    .quality-group {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
        gap: 12px;
        margin-bottom: 25px;
    }
    .quality-button {
        background-color: #ffffff;
        border: 1px solid #e5e7eb;
        border-radius: 12px;
        padding: 10px 8px;
        font-size: 14px;
        color: #3c4043;
        cursor: pointer;
        transition: all 0.2s ease;
        font-weight: 600;
        text-align: center;
        box-shadow: 0 1px 2px rgba(0,0,0,0.05), 0 1px 1px rgba(0,0,0,0.02);
    }
    .quality-button:hover {
        background-color: #f7f9fb;
        transform: translateY(-2px);
        box-shadow: 0 3px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.035);
    }
    .quality-button.active {
        background-color: #00a1d6;
        color: white;
        border-color: #00a1d6;
        box-shadow: 0 6px 12px rgba(0, 161, 214, 0.3);
    }
    .quality-button.active.vip-quality {
        background-color: #f25d8e;
        color: white;
        border-color: #f25d8e;
        box-shadow: 0 6px 12px rgba(242, 93, 142, 0.3);
    }
    .toggle-switch {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 12px;
        padding: 10px 15px;
        border-radius: 12px;
        transition: all 0.2s ease;
        background: #ffffff;
        border: 1px solid #e5e7eb;
        box-shadow: 0 1px 2px rgba(0,0,0,0.05), 0 1px 1px rgba(0,0,0,0.02);
    }
    .toggle-switch:hover {
        background-color: #f7f9fb;
        box-shadow: 0 3px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.035);
    }
    .quality-tabs { box-shadow: 0 1px 2px rgba(0,0,0,0.05), 0 1px 1px rgba(0,0,0,0.02); }
    .toggle-switch label {
        font-size: 16px;
        color: #3c4043;
        font-weight: 600;
    }
    .switch {
        position: relative;
        display: inline-block;
        width: 52px;
        height: 28px;
    }
    .switch input {
        opacity: 0;
        width: 0;
        height: 0;
    }
    .slider {
        position: absolute;
        cursor: pointer;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: #ccc;
        transition: .4s;
        border-radius: 34px;
    }
    .slider:before {
        position: absolute;
        content: "";
        height: 20px;
        width: 20px;
        left: 4px;
        bottom: 4px;
        background-color: white;
        transition: .4s;
        border-radius: 50%;
    }
    input:checked + .slider {
        background-color: #00a1d6;
    }
    input:checked + .slider.vip-audio {
        background-color: #f25d8e;
    }
    input:checked + .slider:before {
        transform: translateX(24px);
    }
    @keyframes fadeIn {
        from { opacity: 0; }
        to { opacity: 1; }
    }
    @keyframes slideIn {
        from { transform: translate(-50%, -60%); }
        to { transform: translate(-50%, -50%); }
    }
    #bilibili-quality-selector.show, #bilibili-live-quality-selector.show, #bilibili-decode-settings.show {
        display: block;
        animation: fadeIn 0.3s ease-out, slideIn 0.3s ease-out;
    }
    @media (max-width: 480px) {
        #bilibili-quality-selector, #bilibili-live-quality-selector, #bilibili-dev-settings, #bilibili-unlock-settings, #bilibili-decode-settings {
            width: 95%;
            padding: 20px;
            max-height: 80vh;
        }
        .quality-group {
            grid-template-columns: repeat(2, 1fr);
            gap: 8px;
        }
        .quality-button {
            padding: 8px 6px;
            font-size: 13px;
        }
        .live-quality-button {
            padding: 10px 6px;
            font-size: 14px;
        }
        .toggle-switch {
            padding: 8px 12px;
            margin-bottom: 8px;
        }
        .toggle-switch label {
            font-size: 14px;
        }
        .toggle-switch .description {
            font-size: 12px;
        }
        .input-group {
            padding: 12px;
            margin-bottom: 12px;
        }
        .input-group label {
            font-size: 14px;
        }
        .input-group .description {
            font-size: 12px;
        }
        .input-group input[type="number"] {
            width: 70px;
            padding: 6px;
            font-size: 13px;
        }
        .github-link {
            top: 20px;
            right: 20px;
            width: 20px;
            height: 20px;
        }
        #bilibili-decode-settings .quality-group {
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
        }
        #bilibili-decode-settings .quality-button {
            padding: 10px 8px;
            font-size: 14px;
        }
        h2 {
            font-size: 24px !important;
            margin-bottom: 15px !important;
        }
        .dev-warning {
            font-size: 13px;
            padding: 12px;
            margin-bottom: 15px;
        }
        .warning {
            font-size: 13px;
            padding: 8px;
            margin: 8px 0;
        }
        .status-bar {
            font-size: 13px;
            padding: 8px;
            margin-bottom: 12px;
        }
        .quality-section-title {
            font-size: 15px;
            margin: 15px 0 12px;
        }
        .quality-section-description {
            font-size: 12px;
            margin: -3px 0 12px;
        }
    }
    @media (max-height: 600px) {
        #bilibili-quality-selector, #bilibili-live-quality-selector, #bilibili-dev-settings, #bilibili-unlock-settings {
            max-height: 90vh;
            padding: 15px;
        }
        .quality-group {
            margin-bottom: 15px;
        }
        .toggle-switch {
            margin-bottom: 6px;
        }
        .input-group {
            margin-bottom: 10px;
        }
    }
    .status-bar {
        padding: 10px;
        border-radius: 8px;
        margin-bottom: 15px;
        text-align: center;
        font-weight: bold;
        transition: all 0.5s ease;
    }
    .status-bar.non-vip {
        background-color: #f0f0f0;
        color: #666666;
    }
    .status-bar.vip {
        background-color: #fff1f5;
        color: #f25d8e;
    }
    .warning {
        background-color: #fce8e6;
        color: #d93025;
        padding: 10px;
        border-radius: 8px;
        margin-top: 12px;
        margin-bottom: 12px;
        text-align: center;
        font-weight: bold;
        transition: all 0.3s ease;
    }
    .warning::before {
        content: "";
        margin-right: 10px;
    }
    #bilibili-dev-settings {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: linear-gradient(135deg, #ffffff, #f8f9fa);
        border-radius: 24px;
        box-shadow: 0 24px 60px rgba(0, 0, 0, 0.16), 0 10px 28px rgba(0, 0, 0, 0.10);
        padding: 32px;
        width: 90%;
        max-width: 420px;
        max-height: 85vh;
        overflow-y: auto;
        overflow-x: hidden;
        display: none;
        z-index: 10000;
        font-family: 'Segoe UI', 'Roboto', sans-serif;
        scrollbar-width: thin;
        scrollbar-color: rgba(0, 161, 214, 0.3) transparent;
    }
    #bilibili-dev-settings.show, #bilibili-unlock-settings.show {
        display: block;
        animation: fadeIn 0.3s ease-out, slideIn 0.3s ease-out;
    }
    #bilibili-dev-settings h2 {
        margin: 0 0 24px;
        color: #f25d8e;
        font-size: 28px;
        text-align: center;
        font-weight: 700;
        letter-spacing: -0.5px;
        text-shadow: 0 2px 4px rgba(242, 93, 142, 0.1);
    }
    #bilibili-dev-settings .dev-warning {
        background: linear-gradient(135deg, #fff1f5, #fce8e6);
        color: #d93025;
        padding: 14px 18px;
        border-radius: 16px;
        margin-bottom: 24px;
        text-align: center;
        font-weight: 600;
        font-size: 14px;
        border: 2px solid rgba(217, 48, 37, 0.1);
        box-shadow: 0 4px 12px rgba(217, 48, 37, 0.05);
    }
    #bilibili-dev-settings .toggle-switch, #bilibili-unlock-settings .toggle-switch {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 12px;
        padding: 10px 15px;
        background-color: #ffffff;
        border-radius: 12px;
        transition: all 0.2s ease;
        border: 1px solid #e5e7eb;
        box-shadow: 0 1px 2px rgba(0,0,0,0.05), 0 1px 1px rgba(0,0,0,0.02);
    }
    #bilibili-dev-settings .toggle-switch .description, #bilibili-unlock-settings .toggle-switch .description {
        font-size: 13px;
        color: #666;
        margin-top: 4px;
    }
    #bilibili-dev-settings .toggle-switch label, #bilibili-unlock-settings .toggle-switch label {
        display: flex;
        flex-direction: column;
        font-size: 16px;
        color: #3c4043;
        font-weight: 600;
    }
    #bilibili-dev-settings .input-group, #bilibili-unlock-settings .input-group {
        background: #ffffff;
        border-radius: 16px;
        padding: 15px;
        margin-bottom: 16px;
        border: 1px solid #e5e7eb;
        transition: all 0.2s ease;
        display: flex;
        align-items: center;
        gap: 12px;
        box-shadow: 0 1px 2px rgba(0,0,0,0.05), 0 1px 1px rgba(0,0,0,0.02);
    }
    #bilibili-dev-settings .input-group.disabled, #bilibili-unlock-settings .input-group.disabled {
        opacity: 0.6;
        cursor: not-allowed;
    }
    #bilibili-dev-settings .input-group:hover, #bilibili-unlock-settings .input-group:hover {
        background: #f9fafb;
        border-color: #e1e7ef;
        box-shadow: 0 3px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.035);
    }
    #bilibili-dev-settings .input-group label, #bilibili-unlock-settings .input-group label {
        flex: 1;
        display: flex;
        flex-direction: column;
        color: #3c4043;
        font-weight: 600;
        font-size: 15px;
    }
    #bilibili-dev-settings .input-group .description, #bilibili-unlock-settings .input-group .description {
        font-size: 13px;
        color: #666;
        margin-top: 4px;
        font-weight: normal;
    }
    #bilibili-dev-settings .input-group input[type="number"], #bilibili-unlock-settings .input-group input[type="number"] {
        width: 80px;
        padding: 8px;
        border: 2px solid #dadce0;
        border-radius: 8px;
        font-size: 14px;
        font-weight: 500;
        color: #3c4043;
        transition: all 0.3s ease;
        background: #ffffff;
        -moz-appearance: textfield;
    }
    #bilibili-dev-settings .input-group .unit, #bilibili-unlock-settings .input-group .unit {
        color: #666;
        font-size: 14px;
        font-weight: normal;
        margin-left: 4px;
    }
    #bilibili-dev-settings .refresh-button {
        width: 100%;
        padding: 12px;
        background: #f25d8e;
        color: white;
        border: none;
        border-radius: 12px;
        font-size: 15px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.3s ease;
        margin-top: 20px;
    }
    #bilibili-dev-settings .refresh-button:hover {
        background: #e74d7b;
        transform: translateY(-2px);
        box-shadow: 0 4px 12px rgba(242, 93, 142, 0.2);
    }
    #bilibili-dev-settings .refresh-button:disabled {
        background: #ccc;
        cursor: not-allowed;
        transform: none;
        box-shadow: none;
    }
    #bilibili-dev-settings input:checked + .slider {
        background-color: #f25d8e;
    }
    #bilibili-dev-settings input:checked + .slider:before {
        transform: translateX(26px);
        box-shadow: 0 2px 4px rgba(242, 93, 142, 0.2);
    }
    #bilibili-dev-settings input:disabled + .slider {
        opacity: 0.5;
        cursor: not-allowed;
    }
    #bilibili-dev-settings input:disabled + .slider:before {
        box-shadow: none;
    }
    #bilibili-unlock-settings h2 {
        margin: 0 0 24px;
        color: #00a1d6;
        font-size: 28px;
        text-align: center;
        font-weight: 700;
        letter-spacing: -0.5px;
        text-shadow: 0 2px 4px rgba(0, 161, 214, 0.1);
    }
    #bilibili-unlock-settings .refresh-button {
        width: 100%;
        padding: 12px;
        background: #00a1d6;
        color: white;
        border: none;
        border-radius: 12px;
        font-size: 15px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.3s ease;
        margin-top: 20px;
    }
    #bilibili-unlock-settings .refresh-button:hover {
        background: #0090bd;
        transform: translateY(-2px);
        box-shadow: 0 4px 12px rgba(0, 161, 214, 0.2);
    }
    #bilibili-unlock-settings .refresh-button:disabled {
        background: #ccc;
        cursor: not-allowed;
        transform: none;
        box-shadow: none;
    }
    #bilibili-unlock-settings input:checked + .slider {
        background-color: #00a1d6;
    }
    #bilibili-unlock-settings input:checked + .slider:before {
        transform: translateX(26px);
        box-shadow: 0 2px 4px rgba(0, 161, 214, 0.2);
    }
    #bilibili-unlock-settings input:disabled + .slider {
        opacity: 0.5;
        cursor: not-allowed;
    }
    #bilibili-unlock-settings input:disabled + .slider:before {
        box-shadow: none;
    }
    @media (max-width: 480px) {
        #bilibili-dev-settings, #bilibili-unlock-settings {
            width: 95%;
            padding: 24px;
        }
        #bilibili-dev-settings .toggle-switch,
        #bilibili-dev-settings .input-group,
        #bilibili-unlock-settings .toggle-switch,
        #bilibili-unlock-settings .input-group {
            padding: 14px 16px;
        }
    }
    .bpx-player-ctrl-quality.quality-button-hidden {
        display: none !important;
    }
    .quality-section {
        margin-bottom: 20px;
    }
    .auto-quality-injected::after {
        content: "自动画质面板";
        position: absolute;
        bottom: 100%;
        left: 50%;
        transform: translateX(-50%);
        background-color: rgba(21, 21, 21, 0.9);
        color: #fff;
        padding: 5px 8px;
        border-radius: 4px;
        font-size: 12px;
        white-space: nowrap;
        pointer-events: none;
        opacity: 0;
        transition: opacity 0.2s ease;
        margin-bottom: 5px;
    }
    .auto-quality-injected:hover::after {
        opacity: 1;
    }
    .github-link {
        position: absolute;
        top: 30px;
        right: 30px;
        width: 24px;
        height: 24px;
        cursor: pointer;
        transition: transform 0.3s ease;
    }
    .github-link:hover {
        transform: scale(1.1);
    }
    .github-link svg {
        width: 100%;
        height: 100%;
        fill: #00a1d6;
    }
    #bilibili-dev-settings .github-link svg { fill: #f25d8e; }
    #bilibili-unlock-settings .github-link svg { fill: #00a1d6; }
    .quality-section-title {
        font-size: 16px;
        color: #00a1d6;
        font-weight: 600;
        margin: 20px 0 15px;
        padding-bottom: 8px;
        border-bottom: 2px solid rgba(0, 161, 214, 0.1);
    }
    .quality-section-description {
        font-size: 13px;
        color: #666;
        margin: -5px 0 15px;
        line-height: 1.4;
    }
    .live-quality-group {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: 12px;
        margin-bottom: 25px;
    }
    .live-quality-button {
        background-color: #ffffff;
        border: 1px solid #e5e7eb;
        border-radius: 12px;
        padding: 12px 8px;
        font-size: 15px;
        color: #3c4043;
        cursor: pointer;
        transition: all 0.2s ease;
        font-weight: 600;
        text-align: center;
        box-shadow: 0 1px 2px rgba(0,0,0,0.05), 0 1px 1px rgba(0,0,0,0.02);
    }
    .live-quality-button:hover {
        background-color: #f7f9fb;
        transform: translateY(-2px);
        box-shadow: 0 3px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.035);
    }

    .status-bar, .warning { box-shadow: 0 1px 2px rgba(0,0,0,0.05), 0 1px 1px rgba(0,0,0,0.02); }
    .live-quality-button.active {
        background-color: #00a1d6;
        color: white;
        border-color: #00a1d6;
        box-shadow: 0 6px 12px rgba(0, 161, 214, 0.3);
    }
    #bilibili-decode-settings .quality-group {
        grid-template-columns: repeat(2, 1fr);
        gap: 16px;
    }
    #bilibili-decode-settings .quality-button {
        padding: 12px 10px;
        font-size: 15px;
    }
    #bilibili-quality-selector,
    #bilibili-live-quality-selector,
    #bilibili-dev-settings,
    #bilibili-unlock-settings,
    #bilibili-decode-settings {
        -ms-overflow-style: none;
        scrollbar-width: none;
    }
    `);
    const Utils = {
        debounce(fn, wait) {
            let timeout;
            return function (...args) {
                clearTimeout(timeout);
                timeout = setTimeout(() => fn.apply(this, args), wait);
            };
        },
        delay(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        },
        query(selector, parent = document) {
            return parent.querySelector(selector);
        },
        queryAll(selector, parent = document) {
            return Array.from(parent.querySelectorAll(selector));
        }
    };
    const PANEL_IDS = [
        "bilibili-quality-selector",
        "bilibili-live-quality-selector",
        "bilibili-dev-settings",
        "bilibili-unlock-settings",
        "bilibili-decode-settings"
    ];
    const QUALITY_ORDER = [
        "8K",
        "杜比视界",
        "HDR",
        "4K",
        "1080P 高码率",
        "1080P 60帧",
        "1080P 高清",
        "720P 60帧",
        "720P",
        "480P",
        "360P",
        "默认"
    ];
    const TRIAL_KEYWORDS = ["限免中", "试用中", "可试用", "试用"];
    const CLEAN_KEYWORDS = ["大会员", ...TRIAL_KEYWORDS];
    function setSetting(stateKey, gmKey, value) {
        state[stateKey] = value;
        GM_setValue(gmKey, value);
    }
    function getDoubleCheckConfig(isLive) {
        const enabled = state.devModeEnabled ? (isLive ? state.liveQualityDoubleCheck : state.qualityDoubleCheck) : true;
        const delayMs = state.devModeEnabled ? state.devDoubleCheckDelay : 5000;
        return { enabled, delayMs };
    }
    function setDevPanelEnabled(panel, enabled) {
        panel.querySelectorAll('input[type="checkbox"]:not(#dev-mode), input[type="number"]').forEach(option => {
            option.disabled = !enabled;
        });
        panel.querySelectorAll('.input-group').forEach(group => {
            if (enabled) {
                group.classList.remove('disabled');
            } else {
                group.classList.add('disabled');
            }
        });
        const vipTabs = panel.querySelector('.vip-status-tabs');
        if (vipTabs) vipTabs.classList.toggle('disabled', !enabled);
    }
    function closeAllPanels() {
        PANEL_IDS.forEach(id => {
            const el = document.getElementById(id);
            if (el) el.classList.remove("show");
        });
    }
    ['fullscreenchange','webkitfullscreenchange'].forEach(e => document.addEventListener(e, closeAllPanels));
    const GITHUB_SVG = `
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
        <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
      </svg>`;
    function renderGithubLink() {
        return `<a href="https://github.com/AHCorn/Bilibili-Auto-Quality/" target="_blank" class="github-link">${GITHUB_SVG}</a>`;
    }
    function checkIfLivePage() {
        state.isLivePage = window.location.href.includes("live.bilibili.com");
    }
    function updateWarnings(panel) {
        if (!panel || state.isLoading || !state.vipStatusChecked) return;
        const nonVipWarning = panel.querySelector("#non-vip-warning");
        const audioWarning = panel.querySelector("#audio-warning");
        if (!state.isVipUser && ["8K", "杜比视界", "HDR", "4K", "1080P 高码率", "1080P 60帧"].includes(state.userQualitySetting)) {
            nonVipWarning.textContent = "无法使用此会员画质。已自动选择最高可用画质。";
            nonVipWarning.style.display = "block";
        } else {
            nonVipWarning.style.display = "none";
        }
        if (!state.isVipUser && (state.hiResAudioEnabled || state.dolbyAtmosEnabled)) {
            audioWarning.textContent = "非大会员用户不能使用高级音频选项。";
            audioWarning.style.display = "block";
        } else {
            audioWarning.style.display = "none";
        }
    }
    function updateQualityButtons(panel) {
        if (!panel) return;
        const statusBar = panel.querySelector(".status-bar");
        if (state.isLoading) {
            statusBar.textContent = "加载中,请稍候...";
            statusBar.className = "status-bar";
            Utils.queryAll(".quality-button, .toggle-switch", panel).forEach(el => {
                el.style.opacity = "0.5";
            });
        } else {
            Utils.queryAll(".quality-button, .toggle-switch", panel).forEach(el => {
                el.style.opacity = "1";
            });
            if (state.vipStatusChecked) {
                statusBar.textContent = state.isVipUser
                    ? "您是大会员用户,可正常使用所有选项。"
                    : "您不是大会员用户,部分会员选项不可用。";
                statusBar.className = "status-bar " + (state.isVipUser ? "vip" : "non-vip");
            }
        }
        Utils.queryAll(".quality-tab", panel).forEach(tab => {
            tab.classList.toggle("active", tab.getAttribute("data-tab") === state.activeQualityTab);
        });
        const tabs = panel.querySelector('.quality-tabs');
        if (tabs) tabs.setAttribute('data-active', state.activeQualityTab);
        Utils.queryAll(".quality-section", panel).forEach(section => {
            section.classList.toggle("active", section.getAttribute("data-section") === state.activeQualityTab);
        });
        Utils.queryAll(".quality-button", panel).forEach(button => {
            const section = button.closest(".quality-section");
            button.classList.remove("active", "vip-quality");
            if (
                (section.getAttribute("data-section") === "primary" && button.getAttribute("data-quality") === state.userQualitySetting) ||
                (section.getAttribute("data-section") === "backup" && button.getAttribute("data-quality") === state.userBackupQualitySetting)
            ) {
                button.classList.add("active");
                if (["8K", "杜比视界", "HDR", "4K", "1080P 高码率", "1080P 60帧"].includes(button.getAttribute("data-quality"))) {
                    button.classList.add("vip-quality");
                }
            }
        });
        const fallbackContainer = panel.querySelector("#highest-quality-fallback-container");
        if (fallbackContainer) {
            fallbackContainer.classList.toggle("show", state.userBackupQualitySetting !== "最高画质");
            fallbackContainer.classList.toggle("hide", state.userBackupQualitySetting === "最高画质");
        }
        const hiResAudioSwitch = panel.querySelector("#hi-res-audio");
        if (hiResAudioSwitch) hiResAudioSwitch.checked = state.hiResAudioEnabled;
        const dolbyAtmosSwitch = panel.querySelector("#dolby-atmos");
        if (dolbyAtmosSwitch) dolbyAtmosSwitch.checked = state.dolbyAtmosEnabled;
        const fallbackCheckbox = panel.querySelector("#highest-quality-fallback");
        if (fallbackCheckbox) fallbackCheckbox.checked = state.useHighestQualityFallback;
        updateWarnings(panel);
    }
    function createSettingsPanel() {
        const panel = document.createElement("div");
        panel.id = "bilibili-quality-selector";
        const QUALITIES = ["最高画质", "8K", "杜比视界", "HDR", "4K", "1080P 高码率", "1080P 60帧", "1080P 高清", "720P", "480P", "360P", "默认"];
        panel.innerHTML = `
          <h2>画质设置</h2>
          ${renderGithubLink()}
          <div class="status-bar"></div>
          <div class="quality-tabs" data-active="${state.activeQualityTab}">
            <div class="tab-indicator"></div>
            <div class="quality-tab ${state.activeQualityTab === 'primary' ? 'active' : ''}" data-tab="primary">首选画质</div>
            <div class="quality-tab ${state.activeQualityTab === 'backup' ? 'active' : ''}" data-tab="backup">备选画质</div>
          </div>
          <div class="quality-section ${state.activeQualityTab === 'primary' ? 'active' : ''}" data-section="primary">
            <div class="quality-group">
              ${QUALITIES.map(q => `<button class="quality-button" data-quality="${q}">${q}</button>`).join('')}
            </div>
          </div>
          <div class="quality-section ${state.activeQualityTab === 'backup' ? 'active' : ''}" data-section="backup">
            <div class="quality-group">
              ${QUALITIES.map(q => `<button class="quality-button" data-quality="${q}">${q}</button>`).join('')}
            </div>
          </div>
          <div id="non-vip-warning" class="warning" style="display:none;"></div>
          <div class="toggle-switch">
            <label for="hi-res-audio">Hi-Res 音质</label>
            <label class="switch">
              <input type="checkbox" id="hi-res-audio">
              <span class="slider vip-audio"></span>
            </label>
          </div>
          <div class="toggle-switch">
            <label for="dolby-atmos">杜比全景声</label>
            <label class="switch">
              <input type="checkbox" id="dolby-atmos">
              <span class="slider vip-audio"></span>
            </label>
          </div>
          <div id="audio-warning" class="warning" style="display:none;"></div>
          <div class="toggle-switch ${state.userBackupQualitySetting !== "最高画质" ? 'show' : 'hide'}" id="highest-quality-fallback-container">
            <label for="highest-quality-fallback">找不到备选画质时使用最高画质</label>
            <label class="switch">
              <input type="checkbox" id="highest-quality-fallback" ${state.useHighestQualityFallback ? 'checked' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="toggle-switch">
            <label for="inject-quality-button">注入画质选项</label>
            <label class="switch">
              <input type="checkbox" id="inject-quality-button" ${state.injectQualityButton ? 'checked' : ''}>
              <span class="slider"></span>
            </label>
          </div>
        `;
        panel.addEventListener("click", function (e) {
            const target = e.target;
            if (target.classList.contains("quality-tab") && !state.isLoading) {
                const prevTab = state.activeQualityTab;
                const tabName = target.getAttribute("data-tab");
                state.activeQualityTab = tabName;
                GM_setValue("activeQualityTab", tabName);
                Utils.queryAll(".quality-tab", panel).forEach(tab =>
                    tab.classList.toggle("active", tab.getAttribute("data-tab") === tabName)
                );
                const tabs = panel.querySelector('.quality-tabs');
                if (tabs) tabs.setAttribute('data-active', tabName);
                // 切换可见的画质区域
                Utils.queryAll(".quality-section", panel).forEach(section =>
                    section.classList.toggle("active", section.getAttribute("data-section") === tabName)
                );
                // 切换后同步状态与高亮
                updateQualityButtons(panel);
            } else if (target.classList.contains("quality-button") && !state.isLoading) {
                const section = target.closest(".quality-section");
                const quality = target.getAttribute("data-quality");
                if (section.getAttribute("data-section") === "primary") {
                    state.userQualitySetting = quality;
                    GM_setValue("qualitySetting", quality);
                } else {
                    state.userBackupQualitySetting = quality;
                    GM_setValue("backupQualitySetting", quality);
                    const fallbackContainer = Utils.query("#highest-quality-fallback-container", panel);
                    if (fallbackContainer) {
                        fallbackContainer.classList.toggle("show", quality !== "最高画质");
                        fallbackContainer.classList.toggle("hide", quality === "最高画质");
                    }
                }
                updateQualityButtons(panel);
                selectQualityBasedOnSetting();
            }
        });
        panel.querySelector("#highest-quality-fallback").addEventListener("change", function (e) {
            if (!state.isLoading) {
                state.useHighestQualityFallback = e.target.checked;
                GM_setValue("useHighestQualityFallback", state.useHighestQualityFallback);
                selectQualityBasedOnSetting();
            }
        });
        panel.querySelector("#hi-res-audio").addEventListener("change", function (e) {
            if (!state.isLoading) {
                state.hiResAudioEnabled = e.target.checked;
                GM_setValue("hiResAudio", state.hiResAudioEnabled);
                updateQualityButtons(panel);
                selectQualityBasedOnSetting();
            }
        });
        panel.querySelector("#dolby-atmos").addEventListener("change", function (e) {
            if (!state.isLoading) {
                state.dolbyAtmosEnabled = e.target.checked;
                GM_setValue("dolbyAtmos", state.dolbyAtmosEnabled);
                updateQualityButtons(panel);
                selectQualityBasedOnSetting();
            }
        });
        panel.querySelector("#inject-quality-button").addEventListener("change", function (e) {
            if (!state.isLoading) {
                state.injectQualityButton = e.target.checked;
                GM_setValue("injectQualityButton", state.injectQualityButton);
                ensureQualitySettingsButton(state.injectQualityButton);
            }
        });
        document.body.appendChild(panel);
        updateQualityButtons(panel);
    }
    function selectQualityBasedOnSetting() {
        if (state.isLivePage) {
            selectLiveQuality();
        } else {
            selectVideoQuality();
        }
    }
    class TaskQueue {
        constructor() {
            this.currentTaskId = 0;
            this.activeTimeouts = new Map();
            this.activeTask = null;
        }

        generateTaskId() {
            return ++this.currentTaskId;
        }

        clearPreviousTasks() {
            this.activeTimeouts.forEach((timeout, taskId) => {
                clearTimeout(timeout);
                console.log(`[任务管理] 取消等待任务 #${taskId}`);
            });
            this.activeTimeouts.clear();

            if (this.activeTask) {
                console.log(`[任务管理] 标记运行中任务 #${this.activeTask} 为已取消`);
                this.activeTask = null;
            }
        }

        isTaskCancelled(taskId) {
            // 检查任务是否过期
            if (taskId !== this.currentTaskId) {
                console.log(`[任务管理] 任务 #${taskId} 已过期,当前任务 #${this.currentTaskId}`);
                return true;
            }
            
            // 统一检测登录状态:未登录且未开启未登录模式时取消任务
            const loginButton = document.querySelector(".go-login-btn, .header-login-entry");
            const headerAvatar = document.querySelector(".v-popover-wrap.header-avatar-wrap");
            const noLoginMode = state.devModeEnabled && state.devModeNoLoginStatus;
            
            if (loginButton && !headerAvatar && !noLoginMode) {
                console.log(`[任务管理] 任务 #${taskId} 取消:检测到未登录且未开启未登录模式`);
                return true;
            }
            
            return false;
        }

        async scheduleTask(taskId, task, delay = 0) {
            if (this.isTaskCancelled(taskId)) return;

            // 设置当前活动任务
            this.activeTask = taskId;

            try {
                if (delay > 0) {
                    await new Promise((resolve, reject) => {
                        const timeout = setTimeout(async () => {
                            this.activeTimeouts.delete(taskId);
                            if (!this.isTaskCancelled(taskId)) {
                                try {
                                    await task();
                                    resolve();
                                } catch (error) {
                                    reject(error);
                                }
                            } else {
                                console.log(`[任务管理] 延迟任务 #${taskId} 已取消`);
                                resolve();
                            }
                        }, delay);
                        this.activeTimeouts.set(taskId, timeout);
                    });
                } else {
                    await task();
                }
            } finally {
                if (this.activeTask === taskId) {
                    this.activeTask = null;
                }
            }
        }
    }

    const taskQueue = new TaskQueue();

    async function setAudioQuality(retryCount = 0) {
        const taskId = taskQueue.currentTaskId;
        if (taskQueue.isTaskCancelled(taskId)) return;

        if (!state.isVipUser) {
            console.log("[音质设置] 非会员用户,略过音质设置");
            return;
        }

        const maxRetries = state.devModeEnabled ? state.devModeAudioRetries : 2;
        const baseDelay = state.devModeEnabled ? state.devModeAudioDelay : 4000;
        const retryInterval = baseDelay * Math.pow(2, retryCount);

        function tryToggle(buttonSelector, shouldEnable, label) {
            if (taskQueue.isTaskCancelled(taskId)) return false;

            const button = document.querySelector(buttonSelector);
            if (!button) return false;
            const isActive = button.classList.contains("bpx-state-active");
            if (shouldEnable && !isActive) {
                console.log(`[音质设置] 检测到需开启${label} (第${retryCount + 1}次尝试)`);
                button.click();
                return true;
            } else if (!shouldEnable && isActive) {
                console.log(`[音质设置] 检测到需关闭${label} (第${retryCount + 1}次尝试)`);
                button.click();
                return true;
            }
            return false;
        }

        console.log(`[音质设置] 开始第${retryCount + 1}次设置`);
        const hiResChanged = tryToggle(".bpx-player-ctrl-flac", state.hiResAudioEnabled, "Hi-Res音质");
        const dolbyChanged = tryToggle(".bpx-player-ctrl-dolby", state.dolbyAtmosEnabled, "杜比全景声");

        if ((hiResChanged || dolbyChanged) && retryCount < maxRetries) {
            console.log(`[音质设置] 等待 ${retryInterval / 1000} 秒后验证设置`);
            await taskQueue.scheduleTask(taskId, async () => {
                if (!taskQueue.isTaskCancelled(taskId)) {
                    await setAudioQuality(retryCount + 1);
                }
            }, retryInterval);
        } else {
            console.log("[音质设置] 设置完成或达到最大重试次数");
        }
    }

    async function selectVideoQuality() {
        const taskId = taskQueue.currentTaskId;
        const currentQualityEl = document.querySelector(".bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text");
        const currentQuality = currentQualityEl ? currentQualityEl.textContent : "";
        console.log("[画质设置] 当前画质:", currentQuality);
        console.log("[画质设置] 目标画质:", state.userQualitySetting);

        // 确保会员状态已检查
        if (!state.vipStatusChecked) {
            console.log("[画质设置] 等待会员状态检查完成");
            return;
        }

        const qualityMenu = document.querySelector(".bpx-player-ctrl-quality-menu");
        if (!qualityMenu) {
            console.warn("[画质设置] 未找到画质菜单节点,终止本次切换");
            return;
        }

        const qualityItems = Array.from(
            qualityMenu.querySelectorAll(".bpx-player-ctrl-quality-menu-item")
        );

        let availableQualities = qualityItems.map(item => {
            // 只在这儿查一次子元素,避免重复 querySelector
            const badge = item.querySelector(".bpx-player-ctrl-quality-badge-bigvip");
            const badgeText = badge ? badge.textContent : "";
            const text = (item.textContent || "").trim();
            const nameHasTrial = TRIAL_KEYWORDS.some(k => text.includes(k));
            const badgeHasTrial = !!(badgeText && TRIAL_KEYWORDS.some(k => badgeText.includes(k)));
            return {
                name: text,
                element: item,
                isVipOnly: !!badge,
                isFreeNow: nameHasTrial || badgeHasTrial
            };
        });

        // 开发者设置:禁用 HDR 选项时,过滤掉 HDR 和杜比视界画质
        if (state.devModeEnabled && state.disableHDROption) {
            availableQualities = availableQualities.filter(q => q.name.indexOf("HDR") === -1 && q.name.indexOf("杜比视界") === -1);
        }

        // 未登录模式下过滤掉高于1080P的画质
        if (state.devModeEnabled && state.devModeNoLoginStatus) {
            availableQualities = availableQualities.filter(q => {
                const name = q.name.trim();
                return !name.includes("8K") && !name.includes("杜比视界") && !name.includes("HDR") && !name.includes("4K");
            });
            console.log("[未登录模式] 已过滤高于1080P的画质,可用画质:", availableQualities.map(q => q.name));
        }

        console.log("[画质设置] 可用画质:", availableQualities.map(q => q.name));
        console.log("[画质设置] 会员状态:", state.isVipUser ? "是" : "否");

        const qualityPreferences = QUALITY_ORDER;
        let targetQuality;
        function cleanQuality(q) {
            if (!q) return "";
            let result = q;
            CLEAN_KEYWORDS.forEach(k => { result = result.replace(new RegExp(k, 'g'), ''); });
            return result.trim();
        }
        if (state.userQualitySetting === "最高画质") {
            const hasFreeVip = availableQualities.some(q => q.isFreeNow);
            const allowFreeVipForNonVip = state.devModeEnabled && state.devAllowFreeVipQualities;
            if (state.isVipUser) {
                targetQuality = availableQualities[0];
            } else if (hasFreeVip && allowFreeVipForNonVip) {
                // 非会员在允许试用时,选择首个“试用/限免”或首个非会员画质
                targetQuality = availableQualities.find(q => q.isFreeNow) || availableQualities.find(q => !q.isVipOnly);
            } else {
                availableQualities.sort((a, b) => {
                    function getQualityIndex(name) {
                        for (let i = 0; i < qualityPreferences.length; i++) {
                            if (name.includes(qualityPreferences[i])) return i;
                        }
                        return qualityPreferences.length;
                    }
                    return getQualityIndex(a.name) - getQualityIndex(b.name);
                });
                const filteredForNonVip = state.isVipUser ? availableQualities : availableQualities.filter(q => !q.isVipOnly && !q.isFreeNow);
                targetQuality = filteredForNonVip.find(q => cleanQuality(q.name).includes(state.userBackupQualitySetting));
                if (!targetQuality && state.useHighestQualityFallback)
                    targetQuality = (state.isVipUser || allowFreeVipForNonVip)
                        ? availableQualities.find(q => !q.isVipOnly || q.isFreeNow)
                        : availableQualities.find(q => !q.isVipOnly && !q.isFreeNow);
                if (!targetQuality && !state.useHighestQualityFallback) {
                    console.log("[画质设置] 未找到备选画质,保持当前画质");
                    await setAudioQuality();
                    return;
                }
            }
        } else if (state.userQualitySetting === "默认") {
            console.log("[画质设置] 使用默认画质");
            await setAudioQuality();
            return;
        } else {
            targetQuality = availableQualities.find(q => cleanQuality(q.name).includes(state.userQualitySetting));
            if (!targetQuality) {
                console.log("[画质设置] 未找到目标画质 " + state.userQualitySetting + ", 尝试使用备选画质");
                targetQuality = availableQualities.find(q => cleanQuality(q.name).includes(state.userBackupQualitySetting));
                if (!targetQuality && state.useHighestQualityFallback) {
                    const allowFreeVipForNonVip = state.devModeEnabled && state.devAllowFreeVipQualities;
                    targetQuality = state.isVipUser
                        ? availableQualities[0]
                        : (allowFreeVipForNonVip
                            ? (availableQualities.find(q => q.isFreeNow) || availableQualities.find(q => !q.isVipOnly))
                            : availableQualities.find(q => !q.isVipOnly && !q.isFreeNow));
                }
                if (!targetQuality && !state.useHighestQualityFallback) {
                    console.log("[画质设置] 未找到备选画质,保持当前画质");
                    await setAudioQuality();
                    return;
                }
            }
        }
        console.log("[画质设置] 实际目标画质: " + targetQuality.name);
        
        // 避免将更高画质切换到更低画质
        if (state.userQualitySetting === "最高画质") {
            const currentQualityItem = availableQualities.find(q => cleanQuality(q.name) === cleanQuality(currentQuality));
            const currentQualityIndex = currentQualityItem ? availableQualities.indexOf(currentQualityItem) : -1;
            const targetQualityIndex = availableQualities.indexOf(targetQuality);
            
            // 获取到的可用画质数组按从高到低排序,索引越大画质越低
            if (currentQualityIndex !== -1 && targetQualityIndex > currentQualityIndex) {
                console.log(`[画质设置] 防护触发:当前画质 ${currentQuality} (数组索引${currentQualityIndex}) 高于目标 ${targetQuality.name} (数组索引${targetQualityIndex}),放弃切换`);
                await setAudioQuality();
                return;
            }
        }
        
        const targetQualityNameClean = cleanQuality(targetQuality.name);
        targetQuality.element.click();

        // 二次验证逻辑
        const { enabled, delayMs } = getDoubleCheckConfig(false);
        if (enabled) {
            console.log(`[画质设置] 等待 ${delayMs} 毫秒后进行二次验证...`);
            await taskQueue.scheduleTask(taskId, async () => {
                if (taskQueue.isTaskCancelled(taskId)) return;

                const currentQualityAfterSwitchEl = document.querySelector(".bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text");
                const currentQualityAfterSwitch = currentQualityAfterSwitchEl ? currentQualityAfterSwitchEl.textContent : "";
                if (currentQualityAfterSwitch && cleanQuality(currentQualityAfterSwitch) !== cleanQuality(targetQualityNameClean)) {
                    console.log("[画质设置] 画质切换未成功,执行二次切换...");
                    // 重新打开清晰度菜单并重新定位目标项,避免旧元素失效
                    const qualityButton = document.querySelector('.bpx-player-ctrl-btn.bpx-player-ctrl-quality');
                    if (qualityButton) {
                        qualityButton.click();
                        await Utils.delay(80);
                    }
                    const qualityMenu = document.querySelector('.bpx-player-ctrl-quality-menu');
                    if (qualityMenu) {
                        const freshItems = Array.from(qualityMenu.querySelectorAll('.bpx-player-ctrl-quality-menu-item'));
                        const freshTarget = freshItems.find(item => cleanQuality((item.textContent || '').trim()).includes(targetQualityNameClean));
                        if (freshTarget && !taskQueue.isTaskCancelled(taskId)) {
                            freshTarget.click();
                        } else if (!freshTarget) {
                            console.warn("[画质设置] 无法重新定位目标画质元素:", targetQualityNameClean);
                        }
                    } else {
                        console.warn("[画质设置] 画质菜单未打开,无法执行二次切换");
                    }
                } else {
                    console.log("[画质设置] 画质切换验证成功,当前画质: " + currentQualityAfterSwitch);
                }
            }, delayMs);
        } else {
            console.log("[画质设置] 二次验证已关闭,跳过验证");
        }

        await setAudioQuality();
    }
    function createLiveSettingsPanel() {
        const panel = document.createElement("div");
        panel.id = "bilibili-live-quality-selector";
        function updatePanel() {
            const qualityCandidates = unsafeWindow.livePlayer.getPlayerInfo().qualityCandidates;
            const LIVE_QUALITIES = ["最高画质", "1080P 原画", "1080P 蓝光", "720P 超清"];
            panel.innerHTML = `
            <h2>直播设置</h2>
            ${renderGithubLink()}
            <div class="quality-section-title">画质选择</div>
            <div class="live-quality-group">
              ${LIVE_QUALITIES.map(quality => `<button class="live-quality-button ${quality === state.userLiveQualitySetting ? 'active' : ''}" data-quality="${quality}">${quality}</button>`).join('')}
            </div>
          `;
            panel.querySelectorAll(".live-quality-button").forEach(button => {
                button.addEventListener("click", () => {
                    state.userLiveQualitySetting = button.getAttribute("data-quality");
                    GM_setValue("liveQualitySetting", state.userLiveQualitySetting);
                    updatePanel();
                    selectLiveQuality();
                });
            });
        }
        document.body.appendChild(panel);
        panel.updatePanel = updatePanel;
        updatePanel();
    }
    async function selectLiveQuality() {
        const taskId = taskQueue.currentTaskId;
        const readyResult = await new Promise(resolve => {
            const timer = setInterval(() => {
                if (taskQueue.isTaskCancelled(taskId)) {
                    clearInterval(timer);
                    resolve("cancelled");
                    return;
                }
                if (
                    unsafeWindow.livePlayer &&
                    unsafeWindow.livePlayer.getPlayerInfo &&
                    unsafeWindow.livePlayer.getPlayerInfo().playurl &&
                    unsafeWindow.livePlayer.switchQuality
                ) {
                    clearInterval(timer);
                    resolve("ready");
                }
            }, 1000);
        });
        if (readyResult === "cancelled") {
            console.log("[直播画质] 任务已取消,停止后续操作");
            return;
        }
        const qualityCandidates = unsafeWindow.livePlayer.getPlayerInfo().qualityCandidates;
        console.log("[直播画质] 可用画质选项:", qualityCandidates.map((q, i) => `${i + 1}. ${q.desc} (qn: ${q.qn})`));
        console.log("[直播画质] 选择的画质:", state.userLiveQualitySetting);

        let targetQuality;
        if (state.userLiveQualitySetting === "最高画质") {
            targetQuality = qualityCandidates[0];
        } else {
            targetQuality = qualityCandidates.find(q => q.desc.includes(state.userLiveQualitySetting));
        }

        if (!targetQuality) {
            console.log("[直播画质] 画质切换失败 (列表加载失败),跳过切换。");
            return;
        }

        console.log("[直播画质] 目标画质:", targetQuality.desc, "(qn:", targetQuality.qn, ")");
        function switchQuality() {
            const currentQualityNumber = unsafeWindow.livePlayer.getPlayerInfo().quality;
            if (currentQualityNumber !== targetQuality.qn) {
                unsafeWindow.livePlayer.switchQuality(targetQuality.qn);
                console.log("[直播画质] 已切换到目标画质:", targetQuality.desc);
                updateLiveSettingsPanel();

                // 二次验证逻辑
                const { enabled, delayMs } = getDoubleCheckConfig(true);
                if (enabled) {
                    console.log(`[直播画质] 等待 ${delayMs} 毫秒后进行二次验证...`);
                    taskQueue.scheduleTask(taskId, async () => {
                        if (taskQueue.isTaskCancelled(taskId)) return;
                        const currentQualityAfterSwitch = unsafeWindow.livePlayer.getPlayerInfo().quality;
                        if (currentQualityAfterSwitch !== targetQuality.qn) {
                            console.log("[直播画质] 画质切换可能未成功,执行二次切换...");
                            unsafeWindow.livePlayer.switchQuality(targetQuality.qn);
                        } else {
                            console.log("[直播画质] 画质切换验证成功,当前画质:", targetQuality.desc);
                        }
                    }, delayMs);
                } else {
                    console.log("[直播画质] 二次验证已关闭,跳过验证");
                }
            } else {
                console.log("[直播画质] 已经是目标画质:", targetQuality.desc);
            }
        }
        switchQuality();
    }
    function updateLiveSettingsPanel() {
        const panel = document.getElementById("bilibili-live-quality-selector");
        if (panel && typeof panel.updatePanel === "function") panel.updatePanel();
    }
    function createDecodeSettingsPanel() {
        const panel = document.createElement("div");
        panel.id = "bilibili-decode-settings";
        const OPTIONS = ["默认", "AV1", "HEVC", "AVC"];
        panel.innerHTML = `
          <h2>解码设置</h2>
          ${renderGithubLink()}
          <div class="toggle-switch">
            <label for="decode-setting-enabled">启用解码设置</label>
            <label class="switch">
              <input type="checkbox" id="decode-setting-enabled" ${state.decodeSettingEnabled ? 'checked' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="quality-section-title">解码策略</div>
          <div class="quality-group">
            ${OPTIONS.map(o => `<button class="quality-button ${(state.isLivePage ? state.userLiveDecodeSetting : state.userVideoDecodeSetting) === o ? 'active' : ''}" data-decode="${o}">${o}</button>`).join('')}
          </div>
        `;
        panel.querySelector("#decode-setting-enabled").addEventListener("change", function (e) {
            state.decodeSettingEnabled = e.target.checked;
            GM_setValue("decodeSettingEnabled", state.decodeSettingEnabled);
            console.log(`[解码设置] 解码设置已${state.decodeSettingEnabled ? '启用' : '关闭'}`);
            if (state.decodeSettingEnabled) {
                applyDecodeSetting();
            }
        });
        panel.addEventListener("click", function (e) {
            const target = e.target;
            if (target.classList.contains("quality-button")) {
                const value = target.getAttribute("data-decode");
                if (state.isLivePage) {
                    state.userLiveDecodeSetting = value;
                    GM_setValue("liveDecodeSetting", value);
                } else {
                    state.userVideoDecodeSetting = value;
                    GM_setValue("videoDecodeSetting", value);
                }
                Utils.queryAll(".quality-button", panel).forEach(btn => {
                    btn.classList.toggle("active", btn === target);
                });
                if (state.decodeSettingEnabled) {
                    applyDecodeSetting();
                }
            }
        });
        document.body.appendChild(panel);
    }
    function updateDecodeButtons(panel) {
        if (!panel) return;
        const enableSwitch = panel.querySelector('#decode-setting-enabled');
        if (enableSwitch) {
            enableSwitch.checked = state.decodeSettingEnabled;
        }
        Utils.queryAll('.quality-button', panel).forEach(btn => {
            const wanted = state.isLivePage ? state.userLiveDecodeSetting : state.userVideoDecodeSetting;
            btn.classList.toggle('active', btn.getAttribute('data-decode') === (wanted || '默认'));
        });
    }
    function toggleDecodeSettingsPanel() {
        togglePanel("bilibili-decode-settings", createDecodeSettingsPanel, updateDecodeButtons);
    }
    function applyDecodeSetting(retryCount = 0) {
        if (!state.decodeSettingEnabled) {
            console.log('[解码设置] 解码设置未启用,跳过');
            return;
        }
        const maxRetries = 8;
        const wanted = state.isLivePage ? (state.userLiveDecodeSetting || '默认') : (state.userVideoDecodeSetting || '默认');
        // 直播页:点击 UL 列表项
        if (state.isLivePage) {
            const decodeList = document.querySelector('.YccudlUCmLKcUTg_yzKN');
            if (!decodeList) {
                if (retryCount < maxRetries) {
                    setTimeout(() => applyDecodeSetting(retryCount + 1), 1000);
                } else {
                    console.log('[解码设置] 未找到直播解码列表');
                }
                return;
            }
            const items = Array.from(decodeList.querySelectorAll('li.HfodaIBaRC9NaglgykBX'));
            if (items.length === 0) {
                if (retryCount < maxRetries) {
                    setTimeout(() => applyDecodeSetting(retryCount + 1), 1000);
                } else {
                    console.log('[解码设置] 直播解码选项为空');
                }
                return;
            }
            const selectedClass = 'fG2r2piYghHTQKQZF8bl';
            let target = items.find(li => (li.textContent || '').trim().includes(wanted)) || items[0];
            if (!target) return;
            if (target.classList.contains(selectedClass)) {
                console.log('[解码设置] 直播页已是目标解码:', (target.textContent || '').trim());
            } else {
                console.log('[解码设置] 直播页切换解码为:', (target.textContent || '').trim());
                target.click();
            }
        } else {
            // 视频页:点击 bui-radio 按钮
            const radioItems = Array.from(document.querySelectorAll('.bui-radio-wrap.bui-radio-button .bui-radio-item'));
            if (radioItems.length === 0) {
                if (retryCount < maxRetries) {
                    setTimeout(() => applyDecodeSetting(retryCount + 1), 1000);
                } else {
                    console.log('[解码设置] 未找到视频页编码单选项');
                }
                return;
            }
            function getText(el) {
                const t = el.querySelector('.bui-radio-text');
                return (t && t.textContent ? t.textContent : el.textContent || '').trim();
            }
            let target = radioItems.find(el => getText(el).includes(wanted));
            if (!target) {
                // 兜底:若未匹配到,优先第一个
                target = radioItems[0];
            }
            if (!target) return;
            // 若已选中则跳过
            const input = target.querySelector('input.bui-radio-input');
            if (input && (input.checked || input.getAttribute('checked') !== null)) {
                console.log('[解码设置] 视频页已是目标解码:', getText(target));
            } else {
                console.log('[解码设置] 视频页切换解码为:', getText(target));
                (input || target).click();
            }
        }
        const panel = document.getElementById('bilibili-decode-settings');
        if (panel) updateDecodeButtons(panel);
    }
    function createUnlockSettingsPanel() {
        const panel = document.createElement("div");
        panel.id = "bilibili-unlock-settings";
        panel.innerHTML = `
          <h2>解锁设置</h2>
          ${renderGithubLink()}
          <div class="toggle-switch">
            <label for="unlock-ua">
              开启 UA 修改
              <div class="description">启用后将模拟 Safari UA 并调整触控点</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="unlock-ua" ${state.unlockUA ? 'checked' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="toggle-switch">
            <label for="unlock-hdr">
              开启 HDR 修改
              <div class="description">写入本地标记以尝试显示 HDR 相关选项</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="unlock-hdr" ${state.unlockHDR ? 'checked' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="toggle-switch">
            <label for="unlock-marker">
              开启标记解锁
              <div class="description">写入本地标记以尝试解锁 杜比全景声/8K</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="unlock-marker" ${state.unlockMarker ? 'checked' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <button class="refresh-button">确认并刷新页面</button>
        `;
        document.body.appendChild(panel);
        panel.querySelector('#unlock-ua').addEventListener('change', function (e) {
            state.unlockUA = e.target.checked;
            GM_setValue("unlockUA", state.unlockUA);
            console.log(`[解锁设置] 开启 UA 修改: ${state.unlockUA}`);
        });
        panel.querySelector('#unlock-hdr').addEventListener('change', function (e) {
            state.unlockHDR = e.target.checked;
            GM_setValue("unlockHDR", state.unlockHDR);
            console.log(`[解锁设置] 开启 HDR 修改: ${state.unlockHDR}`);
        });
        panel.querySelector('#unlock-marker').addEventListener('change', function (e) {
            state.unlockMarker = e.target.checked;
            GM_setValue("unlockMarker", state.unlockMarker);
            console.log(`[解锁设置] 开启标记解锁: ${state.unlockMarker}`);
        });
        panel.querySelector('.refresh-button').addEventListener('click', function () {
            console.log('[解锁设置] 已确认,准备刷新页面以应用设置');
            location.reload();
        });
        return panel;
    }
    function createDevSettingsPanel() {
        const panel = document.createElement("div");
        panel.id = "bilibili-dev-settings";
        panel.innerHTML = `
          <h2>开发者设置</h2>
          ${renderGithubLink()}
          <div class="dev-warning">以下选项的错误配置可能会影响脚本正常工作</div>
          <div class="toggle-switch">
            <label for="dev-mode">
              开发者模式
              <div class="description">启用后可以使用开发者选项</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="dev-mode" ${state.devModeEnabled ? 'checked' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="vip-status-section">
            <div class="vip-status-title">模拟会员状态</div>
            <div class="vip-status-description">模拟脚本所识别到的大会员状态,<b>并非破解</b></div>
            <div class="vip-status-tabs ${!state.devModeEnabled ? 'disabled' : ''}" data-active="${state.devModeVipStatus}">
              <div class="vip-tab-indicator"></div>
              <div class="vip-status-tab ${state.devModeVipStatus === '默认' ? 'active' : ''}" data-status="默认">默认</div>
              <div class="vip-status-tab ${state.devModeVipStatus === '普通' ? 'active' : ''}" data-status="普通">普通</div>
              <div class="vip-status-tab ${state.devModeVipStatus === '会员' ? 'active' : ''}" data-status="会员">会员</div>
            </div>
          </div>
          <div class="toggle-switch">
            <label for="quality-double-check">
              视频画质二次验证
              <div class="description">启用后将在视频画质切换后等待指定时间后进行验证</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="quality-double-check" ${state.qualityDoubleCheck ? 'checked' : ''} ${!state.devModeEnabled ? 'disabled' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="toggle-switch">
            <label for="live-quality-double-check">
              直播画质二次验证
              <div class="description">启用后将在直播画质切换后等待指定时间后进行验证</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="live-quality-double-check" ${state.liveQualityDoubleCheck ? 'checked' : ''} ${!state.devModeEnabled ? 'disabled' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="toggle-switch">
            <label for="dev-allow-freevip">
              非会员允许限免画质
              <div class="description">默认关闭,避免可能出现的会员推广</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="dev-allow-freevip" ${state.devAllowFreeVipQualities ? 'checked' : ''} ${!state.devModeEnabled ? 'disabled' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="toggle-switch">
            <label for="dev-no-login">
              未登录模式
              <div class="description">启用后不等待头像加载,默认最高画质1080P</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="dev-no-login" ${state.devModeNoLoginStatus ? 'checked' : ''} ${!state.devModeEnabled ? 'disabled' : ''}>
              <span class="slider"></span>
            </label>
          </div>

          <div class="toggle-switch">
            <label for="preserve-touch-points">
              保留触控点
              <div class="description">启用后保留 maxTouchPoints 原值,确保触屏功能正常</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="preserve-touch-points" ${state.preserveTouchPoints ? 'checked' : ''} ${!state.devModeEnabled ? 'disabled' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="toggle-switch">
            <label for="disable-hdr">
              禁用 HDR 和杜比视界
              <div class="description">开启后选择 HDR 和杜比视界以外的最高画质</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="disable-hdr" ${state.disableHDROption ? 'checked' : ''} ${!state.devModeEnabled ? 'disabled' : ''}>
              <span class="slider"></span>
            </label>
          </div>

          <div class="toggle-switch">
            <label for="remove-quality-button">
              移除清晰度按钮
              <div class="description">启用后将隐藏播放器的清晰度按钮</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="remove-quality-button" ${state.takeOverQualityControl ? 'checked' : ''} ${!state.devModeEnabled ? 'disabled' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="input-group ${!state.devModeEnabled ? 'disabled' : ''}">
            <label for="dev-double-check-delay">
              二次验证等待时间
              <div class="description">画质切换后等待验证的时间</div>
            </label>
            <input type="number" id="dev-double-check-delay" value="${state.devDoubleCheckDelay}" min="0" max="20000" step="100" ${!state.devModeEnabled ? 'disabled' : ''}>
            <span class="unit">毫秒</span>
          </div>
          <div class="input-group ${!state.devModeEnabled ? 'disabled' : ''}">
            <label for="dev-audio-delay">
              音质切换初始延迟
              <div class="description">音质切换重试的初始等待时间,后续等待时间将以此为基数进行指数退避</div>
            </label>
            <input type="number" id="dev-audio-delay" value="${state.devModeAudioDelay}" min="0" max="10000" step="100" ${!state.devModeEnabled ? 'disabled' : ''}>
            <span class="unit">毫秒</span>
          </div>
          <div class="input-group ${!state.devModeEnabled ? 'disabled' : ''}">
            <label for="dev-audio-retries">
              音质切换重试次数
              <div class="description">音质切换失败后的重试次数</div>
            </label>
            <input type="number" id="dev-audio-retries" value="${state.devModeAudioRetries}" min="0" max="5" step="1" ${!state.devModeEnabled ? 'disabled' : ''}>
            <span class="unit" style="margin-left: 15px;">次</span>
          </div>
          <div id="dev-warning" class="warning" style="display: none;"></div>
          <button class="refresh-button">确认并刷新页面</button>
        `;
        document.body.appendChild(panel);
        panel.querySelector('#dev-mode').addEventListener('change', function (e) {
            setSetting("devModeEnabled", "devModeEnabled", e.target.checked);
            setDevPanelEnabled(panel, state.devModeEnabled);
        });
        panel.querySelector('#quality-double-check').addEventListener('change', function (e) {
            setSetting("qualityDoubleCheck", "qualityDoubleCheck", e.target.checked);
        });
        panel.querySelector('#live-quality-double-check').addEventListener('change', function (e) {
            setSetting("liveQualityDoubleCheck", "liveQualityDoubleCheck", e.target.checked);
        });
        // 会员状态按钮点击事件
        const vipStatusTabs = panel.querySelector('.vip-status-tabs');
        if (vipStatusTabs) {
            vipStatusTabs.addEventListener('click', function (e) {
                if (!state.devModeEnabled) return;
                const target = e.target;
                if (target.classList.contains('vip-status-tab')) {
                    const status = target.getAttribute('data-status');
                    setSetting("devModeVipStatus", "devModeVipStatus", status);

                    // 更新UI状态
                    vipStatusTabs.setAttribute('data-active', status);
                    vipStatusTabs.querySelectorAll('.vip-status-tab').forEach(tab => {
                        tab.classList.toggle('active', tab.getAttribute('data-status') === status);
                    });

                    console.log(`[开发者模式] 会员状态设置为: ${status}`);
                }
            });
        }
        panel.querySelector('#dev-no-login').addEventListener('change', function (e) {
            setSetting("devModeNoLoginStatus", "devModeNoLoginStatus", e.target.checked);
        });

        panel.querySelector('#dev-allow-freevip').addEventListener('change', function (e) {
            setSetting("devAllowFreeVipQualities", "devAllowFreeVipQualities", e.target.checked);
        });

        panel.querySelector('#preserve-touch-points').addEventListener('change', function(e) {
            setSetting("preserveTouchPoints", "preserveTouchPoints", e.target.checked);
        });
        panel.querySelector('#disable-hdr').addEventListener('change', function (e) {
            setSetting("disableHDROption", "disableHDR", e.target.checked);
        });

        panel.querySelector('#remove-quality-button').addEventListener('change', function (e) {
            setSetting("takeOverQualityControl", "takeOverQualityControl", e.target.checked);
        });
        // 绑定三个数字输入框的事件
        panel.querySelector('#dev-double-check-delay').addEventListener('input', function (e) {
            const value = parseInt(e.target.value, 10);
            if (!isNaN(value)) {
                setSetting("devDoubleCheckDelay", "devDoubleCheckDelay", value);
            }
        });
        panel.querySelector('#dev-audio-delay').addEventListener('input', function (e) {
            const value = parseInt(e.target.value, 10);
            if (!isNaN(value)) {
                setSetting("devModeAudioDelay", "devModeAudioDelay", value);
            }
        });
        panel.querySelector('#dev-audio-retries').addEventListener('input', function (e) {
            const value = parseInt(e.target.value, 10);
            if (!isNaN(value)) {
                setSetting("devModeAudioRetries", "devModeAudioRetries", value);
            }
        });
        panel.querySelector('.refresh-button').addEventListener('click', function () {
            location.reload();
        });
        return panel;
    }
    function togglePanel(panelId, createPanelFunc, updateFunc) {
        let panel = document.getElementById(panelId);
        if (!panel) {
            createPanelFunc();
            panel = document.getElementById(panelId);
        }
        // 打开时按需挂载到当前全屏根,避免全屏下不可见
        const root = document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement || document.body;
        if (panel && panel.parentElement !== root) root.appendChild(panel);
        function handleOutsideClick(event) {
            if (panel && !panel.contains(event.target)) {
                panel.classList.remove("show");
                document.removeEventListener("mousedown", handleOutsideClick);
            }
        }
        if (!panel.classList.contains("show")) {
            PANEL_IDS.forEach(id => {
                if (id !== panelId) {
                    const otherPanel = document.getElementById(id);
                    if (otherPanel && otherPanel.classList.contains("show")) otherPanel.classList.remove("show");
                }
            });
            panel.classList.add("show");
            document.addEventListener("mousedown", handleOutsideClick);
        } else {
            panel.classList.remove("show");
            document.removeEventListener("mousedown", handleOutsideClick);
        }
        if (updateFunc) updateFunc(panel);
    }
    function toggleSettingsPanel() {
        togglePanel("bilibili-quality-selector", createSettingsPanel, updateQualityButtons);
    }
    function toggleLiveSettingsPanel() {
        togglePanel("bilibili-live-quality-selector", createLiveSettingsPanel, updateLiveSettingsPanel);
    }
    function toggleDevSettingsPanel() {
        togglePanel("bilibili-dev-settings", createDevSettingsPanel, function (panel) {
            const removeQualityButton = panel.querySelector('#remove-quality-button');
            if (removeQualityButton) removeQualityButton.checked = state.takeOverQualityControl;

            // 更新会员状态UI
            const vipStatusTabs = panel.querySelector('.vip-status-tabs');
            if (vipStatusTabs) {
                vipStatusTabs.setAttribute('data-active', state.devModeVipStatus);
                vipStatusTabs.querySelectorAll('.vip-status-tab').forEach(tab => {
                    tab.classList.toggle('active', tab.getAttribute('data-status') === state.devModeVipStatus);
                });
            }
        });
    }
    function toggleUnlockSettingsPanel() {
        togglePanel("bilibili-unlock-settings", createUnlockSettingsPanel);
    }

    // 注入设置按钮相关操作
    function ensureQualitySettingsButton(shouldInject) {
        const qualityControl = document.querySelector('.bpx-player-ctrl-quality');
        if (!qualityControl) return;
        const parent = qualityControl.parentElement;
        if (!parent) return;

        const existing = parent.querySelector('.auto-quality-injected');
        if (shouldInject) {
            if (!existing) {
                const settingsButton = document.createElement('div');
                settingsButton.className = 'bpx-player-ctrl-btn bpx-player-ctrl-quality auto-quality-injected';
                settingsButton.innerHTML = '<div class="bpx-player-ctrl-btn-icon"><span class="bpx-common-svg-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="15" rx="2" ry="2"></rect><polyline points="8 20 12 20 16 20"></polyline></svg></span></div>';
                settingsButton.addEventListener('click', toggleSettingsPanel);
                parent.insertBefore(settingsButton, qualityControl);
            }
        } else if (existing) {
            existing.remove();
        }
    }
    GM_registerMenuCommand("画质设置", function () {
        checkIfLivePage();
        if (state.isLivePage) toggleLiveSettingsPanel();
        else toggleSettingsPanel();
    });
    GM_registerMenuCommand("解码设置", function () {
        checkIfLivePage();
        toggleDecodeSettingsPanel();
    });
    GM_registerMenuCommand("解锁设置", toggleUnlockSettingsPanel);
    GM_registerMenuCommand("开发者设置", toggleDevSettingsPanel);
    function initPlayerScripts() {
        checkIfLivePage();
        if (state.isLivePage) {
            selectLiveQuality().then(() => { createLiveSettingsPanel(); applyDecodeSetting(); });
        } else {
            const DOM = {
                selectors: {
                    qualityControl: '.bpx-player-ctrl-quality',
                    qualityButton: '.bpx-player-ctrl-btn.bpx-player-ctrl-quality',
                    playerControls: '.bpx-player-control-wrap',
                    headerAvatar: '.v-popover-wrap.header-avatar-wrap',
                    vipIcon: '.bili-avatar-icon.bili-avatar-right-icon.bili-avatar-icon-big-vip',
                    qualityMenu: '.bpx-player-ctrl-quality-menu',
                    qualityMenuItem: '.bpx-player-ctrl-quality-menu-item',
                    activeQuality: '.bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text'
                },
                elements: {},
                get(key) {
                    if (!this.elements[key]) this.elements[key] = document.querySelector(this.selectors[key]);
                    return this.elements[key];
                },
                refresh(key) {
                    this.elements[key] = document.querySelector(this.selectors[key]);
                    return this.elements[key];
                },
                clear() { this.elements = {}; }
            };

            function hideQualityButton() {
                const qualityControl = DOM.get('qualityButton');
                if (qualityControl && state.devModeEnabled && state.takeOverQualityControl && !qualityControl.classList.contains('auto-quality-injected')) {
                    qualityControl.classList.add('quality-button-hidden');
                }
            }

            function initQualitySettingsButton() { ensureQualitySettingsButton(state.injectQualityButton); }

            hideQualityButton();
            initQualitySettingsButton();

            window.playerControlsObserver = new MutationObserver(function () {
                const qualityControl = DOM.refresh('qualityControl');
                if (qualityControl) {
                    hideQualityButton();
                    initQualitySettingsButton();
                }
            });

            const playerControls = DOM.get('playerControls');
            if (playerControls) {
                window.playerControlsObserver.observe(playerControls, { childList: true, subtree: true });
            }

            const vipCheckObserver = new MutationObserver((mutations, observer) => {
                // 未登录模式:跳过等待头像
                if (state.devModeEnabled && state.devModeNoLoginStatus) {
                    observer.disconnect();
                    console.log("[未登录模式] 跳过等待头像元素,直接执行画质设置");
                    waitForPlayerWithBackoff(async () => {
                        state.isLoading = false;
                        await checkVipStatusAsync();
                        await selectVideoQuality();
                        updateQualityButtons(Utils.query("#bilibili-quality-selector"));
                        applyDecodeSetting();
                    }, 5, 1000, 0);
                    return;
                }

                // 已登录:检测到头像
                const headerAvatar = document.querySelector(".v-popover-wrap.header-avatar-wrap");
                if (headerAvatar) {
                    observer.disconnect();

                    let timeoutId = null;
                    let hasExecuted = false;

                    const executeQualitySettings = () => {
                        if (hasExecuted) return;
                        hasExecuted = true;
                        if (timeoutId) {
                            clearTimeout(timeoutId);
                            timeoutId = null;
                        }
                        waitForPlayerWithBackoff(async () => {
                            state.isLoading = false;
                            await checkVipStatusAsync();
                            await selectVideoQuality();
                            updateQualityButtons(Utils.query("#bilibili-quality-selector"));
                            applyDecodeSetting();
                        }, 5, 1000, 0);
                    };

                    // 等待会员图标加载完成
                    const vipIconObserver = new MutationObserver((mutations, observer) => {
                        const vipElement = document.querySelector(".bili-avatar-icon.bili-avatar-right-icon.bili-avatar-icon-big-vip");
                        if (vipElement || mutations.some(m => m.target.classList.contains('bili-avatar-icon-big-vip'))) {
                            observer.disconnect();
                            console.log("[会员状态] 会员图标已加载,开始执行画质设置");
                            executeQualitySettings();
                        }
                    });

                    vipIconObserver.observe(headerAvatar, {
                        childList: true,
                        subtree: true,
                        attributes: true,
                        attributeFilter: ['class']
                    });

                    // 优先保证会员切换速度,因为非会员用户 1080P 的切换频率并不高,并且 3.5 秒其实与之前版本体验一致。
                    timeoutId = setTimeout(() => {
                        vipIconObserver.disconnect();
                        console.log("[会员状态] 会员图标检测超时,继续执行画质设置");
                        executeQualitySettings();
                    }, 3500);
                }
            });

            vipCheckObserver.observe(document.body, { childList: true, subtree: true });

            window.addEventListener('popstate', () => { DOM.clear(); });
            window.addEventListener('beforeunload', () => { DOM.clear(); });
        }
    }
    window.addEventListener("DOMContentLoaded", initPlayerScripts, { once: true });

    function isPlayerReady() {
        const qualityMenu = document.querySelector('.bpx-player-ctrl-quality-menu');
        const qualityItems = qualityMenu ? qualityMenu.querySelectorAll('.bpx-player-ctrl-quality-menu-item') : null;
        const headerAvatar = document.querySelector(".v-popover-wrap.header-avatar-wrap");

        // 未登录模式下不检查头像元素
        const isReady = qualityItems && qualityItems.length > 0 && 
            (state.devModeEnabled && state.devModeNoLoginStatus ? true : headerAvatar);

        console.log(`[播放器状态]
        - 画质菜单: ${qualityMenu ? '已加载' : '未加载'}
        - 画质选项: ${qualityItems ? `${qualityItems.length}个选项` : '未加载'}
        - 用户头像: ${headerAvatar ? '已加载' : (state.devModeEnabled && state.devModeNoLoginStatus ? '未登录模式,跳过检查' : '未加载')}`);

        return isReady;
    }

    async function waitForPlayerWithBackoff(callback, maxRetries = 5, initialDelay = 1000, retryCount = 0) {
        const taskId = taskQueue.currentTaskId;

        if (taskQueue.isTaskCancelled(taskId)) {
            console.log(`[任务管理] 任务 #${taskId} 已取消,停止等待播放器`);
            return;
        }

        if (isPlayerReady()) {
            console.log(`[任务管理] 任务 #${taskId}: 播放器和用户信息已就绪`);
            await callback();
            return;
        }

        if (retryCount >= maxRetries) {
            console.log(`[任务管理] 任务 #${taskId}: 达到最大重试次数(${maxRetries}),强制执行回调`);
            await callback();
            return;
        }

        const delayTime = initialDelay * Math.pow(2, retryCount);
        console.log(`[任务管理] 任务 #${taskId}: 等待播放器加载中 (第${retryCount + 1}次尝试,等待${delayTime}ms)`);

        await taskQueue.scheduleTask(taskId, async () => {
            if (!taskQueue.isTaskCancelled(taskId)) {
                await waitForPlayerWithBackoff(callback, maxRetries, initialDelay, retryCount + 1);
            }
        }, delayTime);
    }

    async function checkVipStatusAsync() {
        // 直接使用缓存的结果
        if (state.sessionCache.vipChecked) {
            state.isVipUser = state.sessionCache.vipStatus;
            state.vipStatusChecked = true;
            console.log("[会员状态] 使用缓存状态:", state.isVipUser ? "是" : "否");
            return;
        }

        if (state.devModeEnabled) {
            // 未登录模式下直接返回非会员状态
            if (state.devModeNoLoginStatus) {
                state.isVipUser = false;
                state.vipStatusChecked = true;
                state.sessionCache.vipStatus = false;
                state.sessionCache.vipChecked = true;
                console.log("[开发者模式] 未登录模式,用户会员状态: 否");
                return;
            }

            // 模拟大会员状态
            if (state.devModeVipStatus === "会员") {
                state.isVipUser = true;
            } else if (state.devModeVipStatus === "普通") {
                state.isVipUser = false;
            } else {
                // 默认状态:不干预,保持原有检测逻辑
                const vipElement = document.querySelector(".bili-avatar-icon.bili-avatar-right-icon.bili-avatar-icon-big-vip");
                const currentQualityEl = document.querySelector(".bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text");
                const hasVipIcon = vipElement !== null;
                const isVipByQuality = !!(currentQualityEl && currentQualityEl.textContent && currentQualityEl.textContent.includes("大会员"));
                state.isVipUser = hasVipIcon || isVipByQuality;
            }
            state.vipStatusChecked = true;
            state.sessionCache.vipStatus = state.isVipUser;
            state.sessionCache.vipChecked = true;
            console.log("[开发者模式] 用户会员状态:", state.isVipUser ? "是" : "否");
            return;
        }

        // Directly query elements as the higher-level observer has already waited for them
        const vipElement = document.querySelector(".bili-avatar-icon.bili-avatar-right-icon.bili-avatar-icon-big-vip");
        const currentQualityEl = document.querySelector(".bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text");

        const hasVipIcon = vipElement !== null;
        const isVipByQuality = !!(currentQualityEl && currentQualityEl.textContent && currentQualityEl.textContent.includes("大会员"));
        const avatarPopover = document.querySelector(".v-popover-content.avatar-popover");
        const vipTitleEl = avatarPopover ? avatarPopover.querySelector(".vip-entry-desc .vip-entry-desc-title") : null;
        const isVipByVipEntryTitle = !!(vipTitleEl && ((vipTitleEl.textContent || "").trim().includes("我的大会员")));

        // 兜底:昵称颜色判定
        let isVipByNicknameColor = false;
        if (!hasVipIcon && !isVipByQuality && !isVipByVipEntryTitle && avatarPopover) {
            const nicknameEl = avatarPopover.querySelector(".nickname-item.light");
            if (nicknameEl) {
                const colorValue = (nicknameEl.style && nicknameEl.style.color ? nicknameEl.style.color : "").trim();
                // 只要不是默认的 var(--text1) 即视为会员
                if (colorValue && !/^var\(--text1\)$/i.test(colorValue)) {
                    isVipByNicknameColor = true;
                }
            }
        }

        state.isVipUser = hasVipIcon || isVipByQuality || isVipByVipEntryTitle || isVipByNicknameColor;
        state.vipStatusChecked = true;
        // 缓存结果
        state.sessionCache.vipStatus = state.isVipUser;
        state.sessionCache.vipChecked = true;

        console.log("[会员状态] 用户会员状态:", state.isVipUser ? "是" : "否");
        if (state.isVipUser) {
            const reason = hasVipIcon ? "发现会员图标" : (isVipByQuality ? "当前使用会员画质" : (isVipByVipEntryTitle ? "会员入口显示为大会员" : "昵称颜色非默认"));
            console.log("[会员状态] 判定依据:", reason);
        }
    }
    function canonicalUrl(rawUrl) {
        try {
            const urlObj = new URL(rawUrl);
            urlObj.pathname = urlObj.pathname.replace(/\/+$/, '');
            const p = urlObj.searchParams.get("p");
            urlObj.search = "";
            if (p !== null) urlObj.searchParams.set("p", p);
            return urlObj.toString();
        } catch (e) { return rawUrl; }
    }
    let lastCanonicalUrl = canonicalUrl(location.href);
    (function (history) {
        const pushState = history.pushState;
        const replaceState = history.replaceState;
        history.pushState = function () {
            const result = pushState.apply(history, arguments);
            window.dispatchEvent(new Event('locationchange'));
            return result;
        };
        history.replaceState = function () {
            const result = replaceState.apply(history, arguments);
            window.dispatchEvent(new Event('locationchange'));
            return result;
        };
    })(window.history);
    async function onUrlChange() {
        const newUrl = canonicalUrl(location.href);
        if (newUrl === lastCanonicalUrl) return;

        lastCanonicalUrl = newUrl;
        const taskId = taskQueue.generateTaskId();
        console.log(`[URL变更] 开始新任务 #${taskId}, URL: ${newUrl}`);

        taskQueue.clearPreviousTasks();
        state.isLoading = true;

        const panel = document.getElementById("bilibili-quality-selector");
        if (panel) updateQualityButtons(panel);

        try {
            await taskQueue.scheduleTask(taskId, async () => {
                if (!taskQueue.isTaskCancelled(taskId)) {
                    console.log(`[任务管理] 任务 #${taskId}: 开始检查播放器状态`);
                    await waitForPlayerWithBackoff(async () => {
                        if (!taskQueue.isTaskCancelled(taskId)) {
                            console.log(`[任务管理] 任务 #${taskId}: 播放器就绪,开始初始化画质设置`);
                            state.isLoading = false;

                            await checkVipStatusAsync();

                            checkIfLivePage();
                            if (state.isLivePage) {
                                await selectLiveQuality();
                                applyDecodeSetting();
                            } else {
                                await selectVideoQuality();
                                applyDecodeSetting();
                            }
                            if (panel) updateQualityButtons(panel);
                            console.log(`[任务管理] 任务 #${taskId}: 画质设置完成`);
                        }
                    });
                }
            }, 1000);
        } catch (error) {
            console.error(`[任务管理] 任务 #${taskId}: 执行出错:`, error);
        }
    }
    const urlChangeEvents = ['popstate', 'hashchange', 'locationchange'];
    urlChangeEvents.forEach(eventName => {
        window.addEventListener(eventName, onUrlChange);
    });
    window.addEventListener('beforeunload', () => {
        urlChangeEvents.forEach(eventName => {
            window.removeEventListener(eventName, onUrlChange);
        });
    });
})();