Greasy Fork

Greasy Fork is available in English.

哔哩哔哩自动画质

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

当前为 2025-02-11 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         哔哩哔哩自动画质
// @namespace    https://github.com/AHCorn/Bilibili-Auto-Quality/
// @version      4.5-Beta
// @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/*
// @exclude      *://live.bilibili.com/
// @exclude      *://live.bilibili.com/*/*
// @match        *://live.bilibili.com/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(async function () {
    "use strict";
    if (typeof unsafeWindow === "undefined") { unsafeWindow = window; }
    window.localStorage.bilibili_player_force_hdr = 1;
    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"),
        userHasChangedQuality: false,
        takeOverQualityControl: GM_getValue("takeOverQualityControl", false),
        isVipUser: false,
        vipStatusChecked: false,
        isLoading: true,
        isLivePage: false,
        userLiveQualitySetting: GM_getValue("liveQualitySetting", "原画"),
        devModeEnabled: GM_getValue("devModeEnabled", false),
        devModeVipStatus: GM_getValue("devModeVipStatus", false),
        devModeDisableUA: GM_getValue("devModeDisableUA", false),
        devModeAudioRetries: GM_getValue("devModeAudioRetries", 2),
        devModeAudioDelay: GM_getValue("devModeAudioDelay", 4000),
        devDoubleCheckDelay: GM_getValue("devDoubleCheckDelay", 5000),
        injectQualityButton: GM_getValue("injectQualityButton", true),
        qualityDoubleCheck: GM_getValue("qualityDoubleCheck", true),
        liveQualityDoubleCheck: GM_getValue("liveQualityDoubleCheck", true),
        disableHDROption: GM_getValue("disableHDR", false),
        sessionCache: {
            vipStatus: null,
            vipChecked: false
        }
    };
    try {
        if (!state.devModeDisableUA || !state.devModeEnabled) {
            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.2 Safari/605.1.15",
                configurable: true
            });
            Object.defineProperty(navigator, 'platform', {
                value: "MacIntel",
                configurable: true
            });
            console.log("[系统设置] UA 和平台标识修改成功");
        } else {
            console.log("[开发者模式] 已禁用 UA 修改");
        }
    } catch (error) {
        console.error("[系统设置] 修改 UserAgent 失败,解锁功能可能失效:", error);
    }
    GM_addStyle(`
    #bilibili-quality-selector, #bilibili-live-quality-selector, #bilibili-dev-settings {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: linear-gradient(135deg, #f6f8fa, #e9ecef);
        border-radius: 24px;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1), 0 1px 8px rgba(0, 0, 0, 0.06);
        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: #00a1d6;
        color: white;
    }
    .quality-section {
        display: none;
    }
    .quality-section.active {
        display: block;
    }
    .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-live-quality-selector h3 {
        margin: 0 0 20px;
        color: #00a1d6;
        font-size: 28px;
        text-align: center;
        font-weight: 700;
    }
    #bilibili-live-quality-selector h3 {
        font-size: 24px;
        margin-top: 20px;
    }
    #bilibili-quality-selector p, #bilibili-live-quality-selector p {
        margin: 0 0 25px;
        color: #5f6368;
        font-size: 14px;
        text-align: center;
    }
    .quality-group {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
        gap: 12px;
        margin-bottom: 25px;
    }
    .line-group {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: 12px;
        margin-bottom: 25px;
    }
    .quality-button, .line-button {
        background-color: #ffffff;
        border: 2px solid #dadce0;
        border-radius: 12px;
        padding: 10px 8px;
        font-size: 14px;
        color: #3c4043;
        cursor: pointer;
        transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
        font-weight: 600;
        text-align: center;
    }
    .line-button {
        font-size: 12px;
        padding: 8px 4px;
    }
    .quality-button:hover, .line-button:hover {
        background-color: #f1f3f4;
        transform: translateY(-2px);
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    }
    .quality-button.active, .line-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);
    }
    .quality-button.unavailable {
        opacity: 0.5;
        cursor: not-allowed;
    }
    .toggle-switch {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 12px;
        padding: 10px 15px;
        border-radius: 12px;
        transition: all 0.3s ease;
    }
    .toggle-switch:hover {
        background-color: #e8eaed;
    }
    .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 {
        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 {
            width: 95%;
            padding: 20px;
            max-height: 80vh;
        }
        .quality-group {
            grid-template-columns: repeat(2, 1fr);
            gap: 8px;
        }
        .line-group {
            grid-template-columns: repeat(2, 1fr);
            gap: 8px;
        }
        .quality-button, .line-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;
        }
        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 {
            max-height: 90vh;
            padding: 15px;
        }
        .quality-group, .line-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 12px 36px rgba(0, 0, 0, 0.15), 0 4px 12px rgba(0, 0, 0, 0.08);
        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 {
        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 {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 12px;
        padding: 10px 15px;
        background-color: #f1f3f4;
        border-radius: 12px;
        transition: all 0.3s ease;
    }
    #bilibili-dev-settings .toggle-switch .description {
        font-size: 13px;
        color: #666;
        margin-top: 4px;
    }
    #bilibili-dev-settings .toggle-switch label {
        display: flex;
        flex-direction: column;
        font-size: 16px;
        color: #3c4043;
        font-weight: 600;
    }
    #bilibili-dev-settings .input-group {
        background: #f8f9fa;
        border-radius: 16px;
        padding: 15px;
        margin-bottom: 16px;
        border: 2px solid transparent;
        transition: all 0.3s ease;
        display: flex;
        align-items: center;
        gap: 12px;
    }
    #bilibili-dev-settings .input-group.disabled {
        opacity: 0.6;
        cursor: not-allowed;
    }
    #bilibili-dev-settings .input-group:hover {
        background: #f1f3f4;
        border-color: rgba(242, 93, 142, 0.1);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
    }
    #bilibili-dev-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 {
        font-size: 13px;
        color: #666;
        margin-top: 4px;
        font-weight: normal;
    }
    #bilibili-dev-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 {
        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;
    }
    @media (max-width: 480px) {
        #bilibili-dev-settings {
            width: 95%;
            padding: 24px;
        }
        #bilibili-dev-settings .toggle-switch,
        #bilibili-dev-settings .input-group {
            padding: 14px 16px;
        }
    }
    .bpx-player-ctrl-quality.quality-button-hidden {
        display: none !important;
    }
    .quality-section {
        margin-bottom: 20px;
    }
    .quality-label {
        font-size: 14px;
        color: #666;
        margin-bottom: 10px;
        text-align: left;
    }
    .quality-group.backup-quality .quality-button {
        font-size: 12px;
        padding: 8px 6px;
    }
    .quality-settings-btn {
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        width: 40px;
        height: 100%;
        opacity: 0.9;
        transition: opacity 0.3s ease;
        position: relative;
    }
    .quality-settings-btn:hover {
        opacity: 1;
    }
    .quality-settings-btn .bpx-player-ctrl-btn-icon {
        width: 22px;
        height: 22px;
        margin-bottom: 12px;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .quality-settings-btn svg {
        width: 100%;
        height: 100%;
        stroke: #ffffff;
    }
    .quality-settings-btn::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;
    }
    .quality-settings-btn: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;
    }
    .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: 2px solid #dadce0;
        border-radius: 12px;
        padding: 12px 8px;
        font-size: 15px;
        color: #3c4043;
        cursor: pointer;
        transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
        font-weight: 600;
        text-align: center;
    }
    .live-quality-button:hover {
        background-color: #f1f3f4;
        transform: translateY(-2px);
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    }
    .live-quality-button.active {
        background-color: #00a1d6;
        color: white;
        border-color: #00a1d6;
        box-shadow: 0 6px 12px rgba(0, 161, 214, 0.3);
    }
    #bilibili-quality-selector,
    #bilibili-live-quality-selector,
    #bilibili-dev-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));
        }
    };
    function checkIfLivePage() {
        state.isLivePage = window.location.href.includes("live.bilibili.com");
    }
    function checkVipStatus() {
        if (state.devModeEnabled) {
            state.isVipUser = state.devModeVipStatus;
            state.vipStatusChecked = true;
            // 缓存会员状态
            state.sessionCache.vipStatus = state.isVipUser;
            state.sessionCache.vipChecked = true;
            console.log("[开发者模式] 用户会员状态:", state.isVipUser ? "是" : "否");
            return;
        }

        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");

        state.isVipUser = vipElement !== null || (currentQualityEl && currentQualityEl.textContent.includes("大会员"));
        state.vipStatusChecked = true;
        // 缓存会员状态
        state.sessionCache.vipStatus = state.isVipUser;
        state.sessionCache.vipChecked = true;

        console.log("[会员状态] 用户会员状态:", state.isVipUser ? "是" : "否");
        if (state.isVipUser) {
            console.log("[会员状态] 判定依据:", vipElement ? "发现会员图标" : "当前使用会员画质");
        }
    }
    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);
        });
        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>
          <a href="https://github.com/AHCorn/Bilibili-Auto-Quality/" target="_blank" class="github-link">
            <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>
          </a>
          <div class="status-bar"></div>
          <div class="quality-tabs">
            <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 id="quality-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 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)
                );
                Utils.queryAll(".quality-section", panel).forEach(section =>
                    section.classList.toggle("active", section.getAttribute("data-section") === tabName)
                );
            } 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);
                const qualityControlElement = document.querySelector(".bpx-player-ctrl-quality");
                if (qualityControlElement) {
                    if (state.injectQualityButton) {
                        let settingsButton = qualityControlElement.previousElementSibling;
                        if (!settingsButton || !settingsButton.classList.contains("quality-settings-btn")) {
                            settingsButton = document.createElement("div");
                            settingsButton.className = "bpx-player-ctrl-btn quality-settings-btn";
                            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);
                            qualityControlElement.parentElement.insertBefore(settingsButton, qualityControlElement);
                        }
                    } else {
                        const existingButton = qualityControlElement.previousElementSibling;
                        if (existingButton && existingButton.classList.contains("quality-settings-btn")) {
                            existingButton.remove();
                        }
                    }
                }
            }
        });
        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;
            }
            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 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 qualityItems = document.querySelectorAll(".bpx-player-ctrl-quality-menu .bpx-player-ctrl-quality-menu-item");
        let availableQualities = Array.from(qualityItems).map(item => ({
            name: item.textContent.trim(),
            element: item,
            isVipOnly: !!item.querySelector(".bpx-player-ctrl-quality-badge-bigvip"),
            isFreeNow: !!(item.querySelector(".bpx-player-ctrl-quality-badge-bigvip") &&
                item.querySelector(".bpx-player-ctrl-quality-badge-bigvip").textContent.includes("限免中"))
        }));

        if (state.disableHDROption) {
            availableQualities = availableQualities.filter(q => q.name.indexOf("HDR") === -1);
        }

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

        const qualityPreferences = ["8K", "杜比视界", "HDR", "4K", "1080P 高码率", "1080P 60帧", "1080P 高清", "720P 60帧", "720P", "480P", "360P", "默认"];
        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);
        });
        let targetQuality;
        function cleanQuality(q) { return q ? q.replace(/大会员|限免中/g, '').trim() : ""; }
        if (state.userQualitySetting === "最高画质") {
            const hasFreeVip = availableQualities.some(q => q.isFreeNow);
            if (state.isVipUser || hasFreeVip) {
                targetQuality = availableQualities[0];
            } else {
                targetQuality = availableQualities.find(q => cleanQuality(q.name).includes(state.userBackupQualitySetting));
                if (!targetQuality && state.useHighestQualityFallback)
                    targetQuality = availableQualities.find(q => !q.isVipOnly);
                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)
                    targetQuality = state.isVipUser ? availableQualities[0] : availableQualities.find(q => !q.isVipOnly);
                if (!targetQuality && !state.useHighestQualityFallback) {
                    console.log("[画质设置] 未找到备选画质,保持当前画质");
                    await setAudioQuality();
                    return;
                }
            }
        }
        console.log("[画质设置] 实际目标画质: " + targetQuality.name);
        targetQuality.element.click();
        if (state.qualityDoubleCheck) {
            await Utils.delay(state.devDoubleCheckDelay);
            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(targetQuality.name)) {
                console.log("[画质设置] 画质切换未成功,执行二次切换...");
                targetQuality.element.click();
            } else {
                console.log("[画质设置] 画质切换验证成功,当前画质: " + currentQualityAfterSwitch);
            }
            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 = ["原画", "蓝光", "超清", "高清"];
            const lineSelector = document.querySelector(".YccudlUCmLKcUTg_yzKN");
            const lines = lineSelector ? Array.from(lineSelector.children).map(li => li.textContent) : ["加载中..."];
            const currentLineIndex = lineSelector ? Array.from(lineSelector.children).findIndex(li => li.classList.contains("fG2r2piYghHTQKQZF8bl")) : 0;
            panel.innerHTML = `
            <h2>直播设置</h2>
            <a href="https://github.com/AHCorn/Bilibili-Auto-Quality/" target="_blank" class="github-link">
              <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>
            </a>
            <div class="quality-section-title">线路选择</div>
            <div class="line-group">
              ${lines.map((line, index) => `<button class="line-button ${index === currentLineIndex ? 'active' : ''}" data-line="${index}">${line}</button>`).join('')}
            </div>
            <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(".line-button").forEach(button => {
                button.addEventListener("click", () => {
                    const lineIndex = parseInt(button.getAttribute("data-line"), 10);
                    changeLine(lineIndex);
                });
            });
            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() {
        await new Promise(resolve => {
            const timer = setInterval(() => {
                if (
                    unsafeWindow.livePlayer &&
                    unsafeWindow.livePlayer.getPlayerInfo &&
                    unsafeWindow.livePlayer.getPlayerInfo().playurl &&
                    unsafeWindow.livePlayer.switchQuality
                ) {
                    clearInterval(timer);
                    resolve();
                }
            }, 1000);
        });
        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 = qualityCandidates.find(q => q.desc === state.userLiveQualitySetting);
        if (!targetQuality) {
            const qualityPriority = ["原画", "蓝光", "超清", "高清"];
            for (let quality of qualityPriority) {
                targetQuality = qualityCandidates.find(q => q.desc === quality);
                if (targetQuality) break;
            }
        }
        if (!targetQuality) targetQuality = qualityCandidates[0];
        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);
                state.userLiveQualitySetting = targetQuality.desc;
                GM_setValue("liveQualitySetting", state.userLiveQualitySetting);
                updateLiveSettingsPanel();
                if (state.liveQualityDoubleCheck) {
                    setTimeout(() => {
                        const currentQualityAfterSwitch = unsafeWindow.livePlayer.getPlayerInfo().quality;
                        if (currentQualityAfterSwitch !== targetQuality.qn) {
                            console.log("[直播画质] 画质切换可能未成功,执行二次切换...");
                            unsafeWindow.livePlayer.switchQuality(targetQuality.qn);
                        } else {
                            console.log("[直播画质] 画质切换验证成功,当前画质:", targetQuality.desc);
                        }
                    }, 5000);
                }
            } else {
                console.log("[直播画质] 已经是目标画质:", targetQuality.desc);
            }
        }
        switchQuality();
    }
    function changeLine(lineIndex) {
        const lineSelector = document.querySelector(".YccudlUCmLKcUTg_yzKN");
        if (lineSelector && lineSelector.children[lineIndex]) {
            lineSelector.children[lineIndex].click();
            console.log("[直播线路] 已切换到线路:", lineSelector.children[lineIndex].textContent);
            const panel = document.getElementById("bilibili-live-quality-selector");
            if (panel) {
                Utils.queryAll(".line-button", panel).forEach((button, index) => {
                    button.classList.toggle("active", index === lineIndex);
                });
            }
        } else {
            console.log("[直播线路] 无法切换线路");
        }
    }
    function observeLineChanges() {
        const lineSelector = document.querySelector(".YccudlUCmLKcUTg_yzKN");
        if (lineSelector) {
            const observer = new MutationObserver(Utils.debounce(mutations => {
                mutations.forEach(mutation => {
                    if (mutation.type === "attributes" && mutation.attributeName === "class") {
                        Array.from(lineSelector.children).forEach(li => {
                            if (li.classList.contains("fG2r2piYghHTQKQZF8bl")) updateLiveSettingsPanel();
                        });
                    }
                });
            }, 300));
            observer.observe(lineSelector, { attributes: true, subtree: true, attributeFilter: ["class"] });
        }
    }
    function updateLiveSettingsPanel() {
        const panel = document.getElementById("bilibili-live-quality-selector");
        if (panel && typeof panel.updatePanel === "function") panel.updatePanel();
    }
    function createDevSettingsPanel() {
        const panel = document.createElement("div");
        panel.id = "bilibili-dev-settings";
        panel.innerHTML = `
          <h2>开发者设置</h2>
          <a href="https://github.com/AHCorn/Bilibili-Auto-Quality/" target="_blank" class="github-link">
            <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>
          </a>
          <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="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-vip">
              模拟大会员状态
              <div class="description">模拟脚本所识别到的大会员状态,<b>并非破解</b></div>
            </label>
            <label class="switch">
              <input type="checkbox" id="dev-vip" ${state.devModeVipStatus ? 'checked' : ''} ${!state.devModeEnabled ? 'disabled' : ''}>
              <span class="slider"></span>
            </label>
          </div>
          <div class="toggle-switch">
            <label for="dev-ua">
              禁用 UA 修改
              <div class="description">禁用后部分旧版本浏览器可能无法解锁画质</div>
            </label>
            <label class="switch">
              <input type="checkbox" id="dev-ua" ${state.devModeDisableUA ? '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" ${!state.devModeEnabled ? 'disabled' : ''}>确认并刷新页面</button>
        `;
        document.body.appendChild(panel);
        panel.querySelector('#dev-mode').addEventListener('change', function (e) {
            state.devModeEnabled = e.target.checked;
            GM_setValue("devModeEnabled", state.devModeEnabled);
        });
        panel.querySelector('#quality-double-check').addEventListener('change', function (e) {
            state.qualityDoubleCheck = e.target.checked;
            GM_setValue("qualityDoubleCheck", state.qualityDoubleCheck);
        });
        panel.querySelector('#live-quality-double-check').addEventListener('change', function (e) {
            state.liveQualityDoubleCheck = e.target.checked;
            GM_setValue("liveQualityDoubleCheck", state.liveQualityDoubleCheck);
        });
        panel.querySelector('#dev-vip').addEventListener('change', function (e) {
            state.devModeVipStatus = e.target.checked;
            GM_setValue("devModeVipStatus", state.devModeVipStatus);
        });
        panel.querySelector('#dev-ua').addEventListener('change', function (e) {
            state.devModeDisableUA = e.target.checked;
            GM_setValue("devModeDisableUA", state.devModeDisableUA);
        });
        panel.querySelector('#disable-hdr').addEventListener('change', function (e) {
            state.disableHDROption = e.target.checked;
            GM_setValue("disableHDR", state.disableHDROption);
        });
        panel.querySelector('#remove-quality-button').addEventListener('change', function (e) {
            state.takeOverQualityControl = e.target.checked;
            GM_setValue("takeOverQualityControl", state.takeOverQualityControl);
        });
        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);
        }
        function handleOutsideClick(event) {
            if (panel && !panel.contains(event.target)) {
                panel.classList.remove("show");
                document.removeEventListener("mousedown", handleOutsideClick);
            }
        }
        if (!panel.classList.contains("show")) {
            ["bilibili-quality-selector", "bilibili-live-quality-selector", "bilibili-dev-settings"].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;
        });
    }
    GM_registerMenuCommand("设置面板", function () {
        checkIfLivePage();
        if (state.isLivePage) toggleLiveSettingsPanel();
        else toggleSettingsPanel();
    });
    GM_registerMenuCommand("开发者选项", toggleDevSettingsPanel);
    window.addEventListener("load", function () {
        if (state.isLivePage) {
            observeLineChanges();
        }
    });
    window.onload = function () {
        checkIfLivePage();
        if (state.isLivePage) {
            selectLiveQuality().then(() => { createLiveSettingsPanel(); });
        } 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',
                    controlBottomRight: '.bpx-player-control-bottom-right'
                },
                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.takeOverQualityControl) qualityControl.classList.add('quality-button-hidden');
            }

            function initQualitySettingsButton() {
                const controlBottomRight = DOM.get('controlBottomRight');
                const qualityControl = DOM.get('qualityControl');
                if (controlBottomRight && qualityControl && state.injectQualityButton) {
                    let existingSettingsBtn = controlBottomRight.querySelector('.quality-settings-btn');
                    if (!existingSettingsBtn) {
                        const settingsButton = document.createElement('div');
                        settingsButton.className = 'bpx-player-ctrl-btn quality-settings-btn';
                        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);
                        qualityControl.parentElement.insertBefore(settingsButton, qualityControl);
                    }
                }
            }

            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) => {
                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"));
                        }, 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(); });
        }
    };
    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 && headerAvatar;

        console.log(`[播放器状态]
        - 画质菜单: ${qualityMenu ? '已加载' : '未加载'}
        - 画质选项: ${qualityItems ? `${qualityItems.length}个选项` : '未加载'}
        - 用户头像: ${headerAvatar ? '已加载' : '未加载'}`);

        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) {
            state.isVipUser = state.devModeVipStatus;
            state.vipStatusChecked = true;
            state.sessionCache.vipStatus = state.isVipUser;
            state.sessionCache.vipChecked = true;
            console.log("[开发者模式] 用户会员状态:", state.isVipUser ? "是" : "否");
            return;
        }

        try {
            const [vipElement, currentQualityEl] = await Promise.all([
                waitForElement(() => document.querySelector(".bili-avatar-icon.bili-avatar-right-icon.bili-avatar-icon-big-vip"), 3000),
                waitForElement(() => document.querySelector(".bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text"), 3000)
            ]);

            state.isVipUser = vipElement !== null || (currentQualityEl && currentQualityEl.textContent.includes("大会员"));
            state.vipStatusChecked = true;
            // 缓存结果
            state.sessionCache.vipStatus = state.isVipUser;
            state.sessionCache.vipChecked = true;

            console.log("[会员状态] 用户会员状态:", state.isVipUser ? "是" : "否");
            if (state.isVipUser) {
                console.log("[会员状态] 判定依据:", vipElement ? "发现会员图标" : "当前使用会员画质");
            }
        } catch (error) {
            console.log("[会员状态] 检查超时,默认为非会员用户");
            state.isVipUser = false;
            state.vipStatusChecked = true;
            // 缓存结果
            state.sessionCache.vipStatus = state.isVipUser;
            state.sessionCache.vipChecked = true;
        }
    }

    function waitForElement(selector, timeout = 5000) {
        return new Promise((resolve, reject) => {
            const element = selector();
            if (element) {
                resolve(element);
                return;
            }

            const observer = new MutationObserver((mutations, obs) => {
                const element = selector();
                if (element) {
                    obs.disconnect();
                    resolve(element);
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['class']
            });

            if (timeout) {
                setTimeout(() => {
                    observer.disconnect();
                    resolve(null);
                }, timeout);
            }
        });
    }

    function createCleanupFunction() {
        const observers = new Set();
        const eventListeners = new Set();
        const timeouts = new Set();
        const intervals = new Set();

        return {
            addObserver: (observer) => observers.add(observer),
            addEventListener: (element, type, listener) => {
                element.addEventListener(type, listener);
                eventListeners.add({ element, type, listener });
            },
            setTimeout: (callback, delay) => {
                const id = setTimeout(callback, delay);
                timeouts.add(id);
                return id;
            },
            setInterval: (callback, delay) => {
                const id = setInterval(callback, delay);
                intervals.add(id);
                return id;
            },
            cleanup: () => {
                observers.forEach(observer => observer.disconnect());
                observers.clear();

                eventListeners.forEach(({ element, type, listener }) => {
                    element.removeEventListener(type, listener);
                });
                eventListeners.clear();

                timeouts.forEach(clearTimeout);
                timeouts.clear();

                intervals.forEach(clearInterval);
                intervals.clear();
            }
        };
    }

    const cleanup = createCleanupFunction();

    function initQualitySettingsButton() {
        const controlBottomRight = DOM.get('controlBottomRight');
        const qualityControl = DOM.get('qualityControl');

        if (controlBottomRight && qualityControl && state.injectQualityButton) {
            const existingSettingsBtn = controlBottomRight.querySelector('.quality-settings-btn');
            if (!existingSettingsBtn) {
                const settingsButton = document.createElement('div');
                settingsButton.className = 'bpx-player-ctrl-btn quality-settings-btn';
                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>';

                const handleClick = () => toggleSettingsPanel();
                settingsButton.addEventListener('click', handleClick);
                cleanup.addEventListener(settingsButton, 'click', handleClick);

                qualityControl.parentElement.insertBefore(settingsButton, qualityControl);
            }
        }
    }

    function observePlayerControls() {
        const playerControls = DOM.get('playerControls');
        if (playerControls) {
            const observer = new MutationObserver(() => {
                const qualityControl = DOM.refresh('qualityControl');
                if (qualityControl) {
                    hideQualityButton();
                    initQualitySettingsButton();
                }
            });

            observer.observe(playerControls, {
                childList: true,
                subtree: true,
                attributes: false
            });

            cleanup.addObserver(observer);
        }
    }

    // 清理资源
    window.addEventListener('beforeunload', () => {
        cleanup.cleanup();
    });

    let currentUrlChangeTaskId = 0;
    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;

                            // 第二次切换就用缓存
                            if (!state.sessionCache.vipChecked) {
                                console.log("[会员状态] 首次检查会员状态");
                                await checkVipStatusAsync();
                            } else {
                                state.isVipUser = state.sessionCache.vipStatus;
                                state.vipStatusChecked = true;
                                console.log("[会员状态] 使用缓存状态:", state.isVipUser ? "是" : "否");
                            }

                            checkIfLivePage();
                            if (state.isLivePage) {
                                await selectLiveQuality();
                            } else {
                                await selectVideoQuality();
                            }
                            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);
        });
    });
})();