VideoPlayer 4.2.1

视频播放器

このスクリプトは単体で利用できません。右のようなメタデータを含むスクリプトから、ライブラリとして読み込まれます: // @require https://update.greasyfork.org/scripts/591096/1902703/VideoPlayer%20421.js

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
/**
 * VideoPlayer v4.2.1 (modified)
 * 一个功能丰富的 HTML5 视频播放器。
 * 修改点:
 * 1. _initHLS 方法支持从 <video> 内部的 <source> 标签提取 HLS 源。
 * 2. _loadSource 方法在 HLS 场景下直接使用 hls.js 加载,不再创建 <source> 标签。
 */
(function (global) {
    'use strict';

    // ---------- 工具函数 ----------
    var isElement = function (obj) { return !!(obj && obj.nodeType === 1); };
    var query = function (sel, ctx) { ctx = ctx || document; return ctx.querySelector(sel); };
    var extend = function (target, source) {
        for (var key in source) {
            if (source.hasOwnProperty(key)) target[key] = source[key];
        }
        return target;
    };
    var loadScript = function (url) {
        return new Promise(function (resolve, reject) {
            var script = document.createElement('script');
            script.src = url;
            script.onload = resolve;
            script.onerror = reject;
            document.head.appendChild(script);
        });
    };

    // ---------- SVG 图标 ----------
    var ICONS = {
        play: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M8 5v14l11-7z" fill="currentColor"/></svg>',
        pause: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" fill="currentColor"/></svg>',
        volumeHigh: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M3 9v6h4l5 5V4L7 9H3z"/><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/><path d="M14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z" fill="currentColor"/></svg>',
        volumeMuted: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z" fill="currentColor"/></svg>',
        fullscreen: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" fill="currentColor"/></svg>',
        fullscreenExit: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" fill="currentColor"/></svg>',
        menu: '<svg viewBox="0 0 24 24" width="24" height="24"><circle cx="12" cy="5" r="2" fill="currentColor"/><circle cx="12" cy="12" r="2" fill="currentColor"/><circle cx="12" cy="19" r="2" fill="currentColor"/></svg>',
        lock: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" fill="currentColor"/></svg>',
        unlock: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6h2c0-1.66 1.34-3 3-3s3 1.34 3 3v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm0 12H6V10h12v10z" fill="currentColor"/></svg>',
        abLoop: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z" fill="currentColor"/></svg>',
        pip: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M19 11h-8v6h8v-6zm-2 4h-4v-2h4v2zm4-12H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z" fill="currentColor"/></svg>',
        cast: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M21 3H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11z" fill="currentColor"/></svg>',
        orientationAuto: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M7 7h10v10H7z" fill="currentColor"/><path d="M9 2v2h6V2h2v2h3v2h-2v12h2v2h-2v2h-2v-2H9v2H7v-2H4v-2h2V6H4V4h3V2h2zm-1 4v12h8V6H8z" fill="currentColor"/></svg>',
        orientationLandscape: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M2 5h20v14H2z" fill="currentColor"/></svg>',
        orientationPortrait: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M5 2h14v20H5z" fill="currentColor"/></svg>',
        debug: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" fill="currentColor"/></svg>',
        close: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="currentColor"/></svg>',
        brightness: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3zm0-6v2h-2V3h2zm0 16v2h-2v-2h2zm8-9h-2v-2h2v2zm-16 0H2v-2h2v2zm12.36-5.64l-1.41 1.41-1.41-1.41 1.41-1.41 1.41 1.41zm-10.72 0l1.41 1.41L4.64 5.36 3.23 3.95l1.41 1.41zm12.72 12.72l-1.41-1.41 1.41-1.41 1.41 1.41-1.41 1.41zm-10.72 0l-1.41-1.41 1.41-1.41 1.41 1.41-1.41 1.41z"/></svg>',
        settings: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" fill="currentColor"/></svg>',
        download: '<svg viewBox="0 0 24 24" width="24" height="24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" fill="currentColor"/></svg>'
    };

    // ---------- 默认配置 ----------
    var DEFAULTS = {
        video: null,
        sources: null,
        tracks: [],
        poster: '',
        autoplay: false,
        loop: false,
        muted: false,
        controls: true,
        themeColor: '#6c5ce7',
        keyboardShortcuts: true,
        playbackRates: [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 5.0],
        controlsList: ['play', 'volume', 'progress', 'time', 'subtitles', 'abloop', 'fullscreen', 'orientation'],
        playlist: null,
        autoNext: false,
        enableProgressMemory: false,
        storageKey: 'video-player',
        enablePreferenceMemory: false,
        subtitleDelay: 0,
        subtitleStyle: null,
        audioGain: 1,
        downmixToStereo: false,
        wakeLock: false,
        videoFilter: null,
        videoZoom: 1,
        videoPan: {x:0, y:0},
        rotation: 0,
        eqSettings: null,
        hlsConfig: null,
        fallbackSources: null,
        chapters: null,
        controlsInitiallyVisible: false,
        aspectRatio: null,
        videoFit: 'contain',
        onReady: null,
        onPlay: null,
        onPause: null,
        onEnded: null,
        onTimeUpdate: null,
        onError: null,
        onQualityChange: null,
        onAudioTrackChange: null,
        onSubtitleTrackChange: null,
        onBufferProgress: null,
        onLoadProgress: null,
        onSourceChange: null,
        onChapter: null,
        onOrientationChange: null
    };

    // ---------- 事件总线 ----------
    function EventEmitter() {
        this._events = {};
    }
    EventEmitter.prototype.on = function (event, handler) {
        if (!this._events[event]) this._events[event] = [];
        this._events[event].push(handler);
        return this;
    };
    EventEmitter.prototype.off = function (event, handler) {
        if (!this._events[event]) return this;
        if (handler) {
            var idx = this._events[event].indexOf(handler);
            if (idx > -1) this._events[event].splice(idx, 1);
        } else {
            this._events[event] = [];
        }
        return this;
    };
    EventEmitter.prototype.emit = function (event, data) {
        if (!this._events[event]) return this;
        this._events[event].forEach(function (fn) {
            try { fn.call(this, data); } catch (e) { console.error(e); }
        }, this);
        return this;
    };

    // ---------- 样式注入 ----------
    var injectStyles = function () {
        if (document.getElementById('video-player-styles')) return;
        var style = document.createElement('style');
        style.id = 'video-player-styles';
        style.textContent = `
            /* ===== 播放器容器 ===== */
            .video-player { 
                position:relative; display:block; background:#000; border-radius:12px; overflow:hidden; 
                font-family:system-ui,sans-serif; line-height:1.4; color:#fff; user-select:none; 
                height:auto; aspect-ratio:auto; 
            }
            .video-player video { display:block; width:100%; height:auto; background:#000; outline:none; cursor:default; }
            .video-player video::cue {
                font-size: var(--cp-subtitle-size, 16px);
                color: var(--cp-subtitle-color, #ffffff);
                background-color: var(--cp-subtitle-bg, rgba(0,0,0,0.7));
                font-family: var(--cp-subtitle-font, system-ui, sans-serif);
                text-shadow: var(--cp-subtitle-shadow, none);
            }

            /* ===== 控制栏容器 ===== */
            .video-player .cp-controls { 
                position:absolute; bottom:0; left:0; right:0; 
                padding:8px 12px 10px;
                background:linear-gradient(0deg, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.3) 70%, transparent 100%);
                opacity:0; 
                transition:opacity 0.3s, transform 0.3s; 
                display:flex; flex-direction:column; gap:4px; 
                pointer-events:none; 
                transform:translateY(10px);
            }
            .video-player .cp-controls.cp-controls-visible { 
                opacity:1; pointer-events:auto; transform:translateY(0);
            }
            .video-player:hover .cp-controls, 
            .video-player.is-focused .cp-controls { 
                opacity:1; pointer-events:auto; transform:translateY(0);
            }

            /* ===== 控制栏顶部行 ===== */
            .video-player .cp-controls-top { 
                display:flex; justify-content:space-between; align-items:center; 
                font-size:13px; padding:0 2px; color:rgba(255,255,255,0.85); gap:6px; flex-wrap:wrap; 
            }

            /* ===== 进度条 ===== */
            .video-player .cp-progress { 
                width:100%; height:3px; background:rgba(255,255,255,0.2); 
                border-radius:2px; cursor:pointer; position:relative; touch-action:none; 
            }
            .video-player .cp-progress .cp-progress-filled { 
                height:100%; background:var(--cp-theme,#6c5ce7); 
                border-radius:2px; width:0%; transition:width 0.05s linear; position:relative; 
            }
            .video-player .cp-progress .cp-progress-filled::after { 
                content:''; position:absolute; right:-5px; top:-3.5px; 
                width:10px; height:10px; background:var(--cp-theme,#6c5ce7); 
                border-radius:50%; opacity:0; transition:opacity 0.2s; 
                box-shadow:0 0 8px rgba(0,0,0,0.3); 
            }
            .video-player .cp-progress:hover .cp-progress-filled::after { opacity:1; }
            .video-player .cp-progress .cp-progress-buffered { 
                position:absolute; left:0; top:0; height:100%; 
                background:rgba(255,255,255,0.25); border-radius:2px; 
                width:0%; pointer-events:none; z-index:0; 
            }
            .video-player .cp-progress .cp-progress-filled { z-index:1; }

            /* ===== 控制栏底部行 ===== */
            .video-player .cp-controls-bottom { 
                display:flex; align-items:center; gap:2px; flex-wrap:wrap; min-height:36px; 
            }

            /* ===== 按钮通用样式 ===== */
            .video-player .cp-btn { 
                background:none; border:none; color:rgba(255,255,255,0.9); 
                cursor:pointer; padding:4px; 
                display:inline-flex; align-items:center; justify-content:center; 
                width:36px; height:36px; border-radius:6px; 
                transition:background 0.15s, transform 0.15s; 
                background:transparent; flex-shrink:0; 
            }
            .video-player .cp-btn:hover { 
                background:rgba(255,255,255,0.1); 
            }
            .video-player .cp-btn:active { 
                transform:scale(0.92); 
            }
            .video-player .cp-btn:focus-visible { 
                outline:2px solid var(--cp-theme,#6c5ce7); outline-offset:2px; 
            }
            .video-player .cp-btn svg { 
                width:18px; height:18px; fill:currentColor; 
            }

            /* ===== 时间显示 ===== */
            .video-player .cp-time { 
                font-size:12px; color:rgba(255,255,255,0.75); 
                min-width:60px; letter-spacing:0.3px; 
                font-variant-numeric:tabular-nums; line-height:36px; padding:0 4px; 
            }

            /* ===== 音量控制 ===== */
            .video-player .cp-volume-wrap { display:flex; align-items:center; }

            /* ===== 字幕选择 ===== */
            .video-player .cp-subtitle-select { 
                background:rgba(0,0,0,0.4); border:1px solid rgba(255,255,255,0.1); 
                color:#fff; padding:2px 8px; border-radius:4px; font-size:11px; 
                cursor:pointer; outline:none; font-family:inherit; height:28px; 
                backdrop-filter:blur(4px);
            }
            .video-player .cp-subtitle-select option { background:#222; }

            /* ===== 全屏按钮 ===== */
            .video-player .cp-fullscreen-btn { margin-left:auto; }

            /* ===== 海报 ===== */
            .video-player .cp-poster { 
                position:absolute; top:0; left:0; right:0; bottom:0; 
                background-size:cover; background-position:center; 
                pointer-events:none; transition:opacity 0.5s; z-index:1; 
            }
            .video-player .cp-poster.hidden { opacity:0; }

            /* ===== 中间大播放按钮 ===== */
            .video-player .cp-big-play { 
                position:absolute; top:50%; left:50%; 
                transform:translate(-50%,-50%); 
                width:64px; height:64px; border-radius:50%; 
                background:rgba(0,0,0,0.3); 
                border:none; 
                color:#fff; display:flex; align-items:center; justify-content:center; 
                cursor:pointer; z-index:2; 
                transition:opacity 0.3s, transform 0.2s, background 0.2s; 
                backdrop-filter:blur(6px);
                -webkit-backdrop-filter:blur(6px);
                box-shadow:0 4px 20px rgba(0,0,0,0.25);
                opacity:0; pointer-events:none; 
            }
            .video-player .cp-big-play.show { opacity:1; pointer-events:auto; }
            .video-player .cp-big-play:hover { 
                transform:translate(-50%,-50%) scale(1.06); 
                background:rgba(0,0,0,0.45); 
            }
            .video-player .cp-big-play svg { width:30px; height:30px; }

            /* ===== 加载动画 ===== */
            .video-player .cp-loading { 
                position:absolute; top:50%; left:50%; 
                transform:translate(-50%,-50%); 
                width:40px; height:40px; 
                border:3px solid rgba(255,255,255,0.12); 
                border-top:3px solid var(--cp-theme,#6c5ce7); 
                border-radius:50%; animation:cp-spin 0.8s linear infinite; 
                display:none; z-index:3; 
            }
            @keyframes cp-spin { to { transform:translate(-50%,-50%) rotate(360deg); } }
            .video-player.is-loading .cp-loading { display:block; }

            /* ===== 右上角菜单(精简) ===== */
            .video-player .cp-top-right { 
                position:absolute; top:10px; right:10px; 
                display:flex; gap:4px; z-index:15; 
                opacity:0; transition:opacity 0.3s; pointer-events:none; 
            }
            .video-player:hover .cp-top-right, 
            .video-player.is-focused .cp-top-right { 
                opacity:1; pointer-events:auto; 
            }
            .video-player .cp-top-right .cp-btn { 
                width:28px; height:28px; padding:0; 
                background:rgba(0,0,0,0.25) !important; 
                backdrop-filter:blur(4px);
                -webkit-backdrop-filter:blur(4px);
                border-radius:6px;
            }
            .video-player .cp-top-right .cp-btn svg { width:16px; height:16px; }
            .video-player .cp-top-right .cp-btn:hover { 
                background:rgba(255,255,255,0.15) !important; 
            }

            /* ===== 菜单下拉 ===== */
            .video-player .cp-menu-dropdown { 
                display:none; position:absolute; top:calc(100% + 4px); right:0; 
                background:rgba(28,28,30,0.92); 
                backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px); 
                border-radius:8px; padding:4px; min-width:40px; 
                border:1px solid rgba(255,255,255,0.06); 
                box-shadow:0 8px 32px rgba(0,0,0,0.5); 
                z-index:30; flex-direction:column; gap:1px; 
            }
            .video-player .cp-menu-dropdown.show { display:flex; }
            .video-player .cp-menu-item { 
                background:transparent; border:none; color:#ccc; 
                padding:4px; border-radius:4px; cursor:pointer; 
                font-size:13px; text-align:left; white-space:nowrap; 
                transition:all 0.15s; font-weight:500; 
                width:100%; display:flex; align-items:center; 
                justify-content:center; gap:0; 
            }
            .video-player .cp-menu-item:hover { 
                background:rgba(255,255,255,0.06); color:#fff; 
            }
            .video-player .cp-menu-item svg { width:16px; height:16px; fill:currentColor; }

            /* ===== 右侧垂直按钮组 ===== */
            .video-player .cp-side-controls {
                position:absolute; right:10px; top:50%; 
                transform:translateY(-50%); 
                display:flex; flex-direction:column; gap:4px; 
                z-index:16; opacity:0; transition:opacity 0.3s; 
                pointer-events:none; 
            }
            .video-player:hover .cp-side-controls, 
            .video-player.is-focused .cp-side-controls { 
                opacity:1; pointer-events:auto; 
            }
            .video-player .cp-side-controls .cp-btn {
                width:32px; height:32px; padding:0; 
                background:rgba(0,0,0,0.25); 
                backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px);
                border-radius:6px;
            }
            .video-player .cp-side-controls .cp-btn:hover { 
                background:rgba(255,255,255,0.12); 
            }
            .video-player .cp-side-controls .cp-btn svg { width:16px; height:16px; }

            /* ===== A-B循环按钮:仅全屏显示 ===== */
            .video-player .cp-abloop-btn { display: none !important; }
            .video-player.is-fullscreen .cp-abloop-btn { display: inline-flex !important; }

            /* ===== 锁定状态 ===== */
            .video-player.is-locked .cp-side-controls .cp-btn:not(.cp-lock-btn) { 
                display: none !important; 
            }
            .video-player.is-locked .cp-side-controls { 
                opacity:1 !important; pointer-events:auto !important; 
            }
            .video-player .cp-abloop-btn.active { color:var(--cp-theme,#6c5ce7); }
            .video-player .cp-abloop-btn.set-a { color:#ffa500; }
            .video-player .cp-abloop-btn.set-b { color:#00e676; }

            .video-player.is-locked .cp-controls,
            .video-player.is-locked .cp-big-play,
            .video-player.is-locked .cp-poster,
            .video-player.is-locked .cp-top-right { 
                display:none !important; pointer-events:none !important; opacity:0 !important; 
            }
            .video-player.is-locked .cp-side-controls { 
                opacity:1 !important; pointer-events:auto !important; 
            }

            /* ===== 调试面板 ===== */
            .video-player .cp-debug-panel {
                position:absolute; top:40px; left:12px; right:12px; 
                background:rgba(28,28,30,0.92); 
                backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px); 
                border-radius:8px; padding:14px; 
                border:1px solid rgba(255,255,255,0.06); 
                box-shadow:0 8px 32px rgba(0,0,0,0.5); 
                z-index:40; display:none; 
            }
            .video-player .cp-debug-panel.show { display:block; }
            .video-player .cp-debug-panel .cp-debug-header { 
                display:flex; justify-content:space-between; align-items:center; 
                margin-bottom:8px; 
            }
            .video-player .cp-debug-panel .cp-debug-title { 
                font-size:13px; font-weight:600; color:#fff; 
            }
            .video-player .cp-debug-panel .cp-debug-close { 
                background:none; border:none; color:#ccc; cursor:pointer; 
                padding:2px; display:inline-flex; 
            }
            .video-player .cp-debug-panel .cp-debug-close svg { 
                width:18px; height:18px; fill:currentColor; 
            }
            .video-player .cp-debug-panel .cp-debug-row { 
                display:flex; justify-content:space-between; 
                font-size:11px; color:#ccc; padding:3px 0; 
            }
            .video-player .cp-debug-panel .cp-debug-value { 
                color:#fff; font-weight:500; 
            }

            /* ===== 手势指示器 ===== */
            .video-player .cp-gesture-indicator {
                position:absolute; top:50%; left:50%; 
                transform:translate(-50%,-50%); z-index:25; 
                background:rgba(0,0,0,0.5); 
                backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px); 
                border-radius:8px; padding:8px 14px; 
                display:flex; align-items:center; gap:6px; 
                opacity:0; pointer-events:none; 
                transition:opacity 0.2s; color:#fff; 
                font-size:14px; font-weight:600; min-width:60px; 
                justify-content:center; 
            }
            .video-player .cp-gesture-indicator.show { opacity:1; }
            .video-player .cp-gesture-indicator svg {
                width:20px; height:20px; fill:currentColor; flex-shrink:0; 
            }
            .video-player .cp-gesture-indicator .cp-gesture-value {
                white-space:nowrap; 
            }

            /* ===== 设置面板(齿轮菜单) ===== */
            .video-player .cp-settings-panel {
                display:none;
                position:absolute;
                bottom:calc(100% + 8px);
                right:0;
                background:rgba(28,28,30,0.94);
                backdrop-filter:blur(16px);
                -webkit-backdrop-filter:blur(16px);
                border-radius:10px;
                padding:10px 12px;
                min-width:160px;
                border:1px solid rgba(255,255,255,0.06);
                box-shadow:0 8px 40px rgba(0,0,0,0.6);
                z-index:35;
                flex-direction:column;
                gap:6px;
            }
            .video-player .cp-settings-panel.show {
                display:flex;
            }
            .video-player .cp-settings-panel .cp-settings-title {
                font-size:12px;
                font-weight:600;
                color:rgba(255,255,255,0.5);
                letter-spacing:0.5px;
                text-transform:uppercase;
                padding-bottom:4px;
                border-bottom:1px solid rgba(255,255,255,0.06);
                margin-bottom:2px;
            }
            .video-player .cp-settings-panel .cp-settings-item {
                display:flex;
                align-items:center;
                justify-content:space-between;
                padding:4px 0;
                font-size:13px;
                color:rgba(255,255,255,0.85);
            }
            .video-player .cp-settings-panel .cp-settings-item select,
            .video-player .cp-settings-panel .cp-settings-item .cp-settings-value {
                background:rgba(255,255,255,0.06);
                border:1px solid rgba(255,255,255,0.08);
                border-radius:4px;
                color:#fff;
                padding:2px 6px;
                font-size:12px;
                cursor:pointer;
                outline:none;
                font-family:inherit;
                min-width:60px;
                text-align:center;
            }
            .video-player .cp-settings-panel .cp-settings-item .cp-settings-value {
                background:transparent;
                border:none;
                padding:0 4px;
                font-weight:500;
                color:var(--cp-theme,#6c5ce7);
                cursor:default;
            }
            .video-player .cp-settings-panel .cp-settings-item select option {
                background:#222;
            }
            /* 下载项单独样式 */
            .video-player .cp-settings-panel .cp-settings-item[data-setting="download"] {
                cursor:pointer;
                transition:background 0.15s;
                padding:4px 8px;
                border-radius:4px;
                margin:0 -4px;
            }
            .video-player .cp-settings-panel .cp-settings-item[data-setting="download"]:hover {
                background:rgba(255,255,255,0.06);
            }
            .video-player .cp-settings-panel .cp-settings-item[data-setting="download"] .cp-settings-value {
                color:var(--cp-theme,#6c5ce7);
                font-weight:500;
            }
            /* 设置按钮特殊样式 */
            .video-player .cp-settings-btn {
                margin-left:2px;
            }
            .video-player .cp-settings-btn.active {
                color:var(--cp-theme,#6c5ce7);
            }

            /* ===== 自定义倍速输入框 ===== */
            .video-player .cp-settings-item .cp-custom-speed-wrap {
                display:flex;
                align-items:center;
                gap:6px;
                margin-top:4px;
                width:100%;
                justify-content:flex-end;
            }
            .video-player .cp-settings-item .cp-custom-speed-wrap input {
                width:58px;
                background:rgba(255,255,255,0.08);
                border:1px solid rgba(255,255,255,0.15);
                border-radius:4px;
                color:#fff;
                padding:2px 4px;
                font-size:12px;
                outline:none;
                text-align:center;
            }
            .video-player .cp-settings-item .cp-custom-speed-wrap input:focus {
                border-color:var(--cp-theme, #6c5ce7);
            }
            .video-player .cp-settings-item .cp-custom-speed-wrap .cp-speed-apply {
                background:var(--cp-theme, #6c5ce7);
                border:none;
                border-radius:4px;
                color:#fff;
                padding:2px 10px;
                font-size:12px;
                cursor:pointer;
                transition:opacity 0.15s;
            }
            .video-player .cp-settings-item .cp-custom-speed-wrap .cp-speed-apply:hover {
                opacity:0.8;
            }
            .video-player .cp-settings-item .cp-custom-speed-wrap .cp-speed-label {
                font-size:11px;
                color:rgba(255,255,255,0.5);
                margin-right:auto;
            }

            /* ===== 响应式 ===== */
            @media (max-width:600px) { 
                .video-player .cp-controls { padding:6px 8px 8px; } 
                .video-player .cp-btn { width:32px; height:32px; } 
                .video-player .cp-btn svg { width:16px; height:16px; }
                .video-player .cp-time { font-size:10px; min-width:50px; line-height:32px; }
                .video-player .cp-top-right { top:6px; right:6px; gap:3px; }
                .video-player .cp-top-right .cp-btn { width:24px; height:24px; }
                .video-player .cp-top-right .cp-btn svg { width:14px; height:14px; }
                .video-player .cp-menu-dropdown { min-width:36px; }
                .video-player .cp-menu-item { padding:3px; }
                .video-player .cp-menu-item svg { width:14px; height:14px; }
                .video-player .cp-side-controls { right:6px; gap:3px; }
                .video-player .cp-side-controls .cp-btn { width:28px; height:28px; }
                .video-player .cp-side-controls .cp-btn svg { width:14px; height:14px; }
                .video-player .cp-debug-panel { left:6px; right:6px; }
                .video-player .cp-gesture-indicator { padding:6px 10px; font-size:12px; }
                .video-player .cp-gesture-indicator svg { width:16px; height:16px; }
                .video-player .cp-big-play { width:52px; height:52px; }
                .video-player .cp-big-play svg { width:24px; height:24px; }
                .video-player .cp-settings-panel {
                    right:0;
                    min-width:130px;
                    padding:8px 10px;
                }
                .video-player .cp-settings-panel .cp-settings-item {
                    font-size:12px;
                }
                .video-player .cp-settings-panel .cp-settings-item select {
                    font-size:11px;
                    min-width:50px;
                }
            }
        `;
        document.head.appendChild(style);
    };

    // ---------- 播放器主类 ----------
    function VideoPlayer(options) {
        if (!(this instanceof VideoPlayer)) return new VideoPlayer(options);

        EventEmitter.call(this);

        this.config = extend({}, DEFAULTS);
        extend(this.config, options);

        // +MOD+ 确保倍速列表包含 3.0 和 5.0
        var userRates = this.config.playbackRates;
        if (userRates && Array.isArray(userRates)) {
            var defaultRates = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 5.0];
            var merged = userRates.slice();
            defaultRates.forEach(function(r) {
                if (merged.indexOf(r) === -1) merged.push(r);
            });
            this.config.playbackRates = merged;
        }

        this.controlsList = this.config.controlsList || DEFAULTS.controlsList;

        var video = this.config.video;
        if (!video) throw new Error('VideoPlayer: 必须提供 video 选项');
        if (typeof video === 'string') video = query(video);
        if (!isElement(video)) throw new Error('VideoPlayer: video 元素无效');
        if (video.tagName !== 'VIDEO') throw new Error('VideoPlayer: 传入的元素必须是 <video>');
        this.video = video;

        injectStyles();

        this.controlsEl = null;
        this.progressEl = null;
        this.progressFilled = null;
        this.progressBuffered = null;
        this.playBtn = null;
        this.timeDisplay = null;
        this.volumeBtn = null;
        this.subtitleSelect = null;
        this.fullscreenBtn = null;
        this.abLoopBtn = null;
        this.menuBtn = null;
        this.menuDropdown = null;
        this.lockBtn = null;
        this.posterEl = null;
        this.bigPlayBtn = null;
        this.loadingEl = null;
        this.sideControlsEl = null;
        this.orientationBtn = null;
        this.wrapper = null;
        this.debugPanelEl = null;
        this.debugCloseBtn = null;
        this.gestureIndicatorEl = null;
        this.gestureIndicatorIconEl = null;
        this.gestureIndicatorValueEl = null;
        this.settingsBtn = null;
        this.settingsPanel = null;

        this.abMarkA = null;
        this.abMarkB = null;

        this._blobUrls = [];
        this._sourceCleanup = null;

        this.volumeSlider = null;
        this.volumeFilled = null;

        this.isDragging = false;
        this.isFullscreen = false;
        this.isPip = false;
        this.isMuted = this.config.muted;
        this.volume = this.config.muted ? 0 : 1;
        this.playbackRate = 1.0;
        this._destroyed = false;
        this._hls = null;
        this._trackElements = [];
        this._tracks = this.config.tracks || [];
        this._stallRetries = 0;
        this._maxStallRetries = 3;
        this._isVolumeDragging = false;
        this._orientation = 'auto';
        this._abState = 'off';
        this._abA = 0;
        this._abB = 0;
        this._locked = false;

        this._controlsVisible = false;
        this._hideControlsTimer = null;
        this.touch = 'ontouchstart' in window;

        this._currentQualityIndex = -1;
        this._audioTracks = [];
        this._currentAudioTrackIndex = -1;
        this._subtitleTracks = [];
        this._currentSubtitleTrack = null;
        this._sourceIndex = 0;
        this._playlist = this.config.playlist || [];
        this._playlistIndex = 0;
        this._chapters = this.config.chapters || [];
        this._audioContext = null;
        this._gainNode = null;
        this._eqNodes = [];
        this._mediaSource = null;
        this._wakeLock = null;
        this._wakeLockSupported = 'wakeLock' in navigator;
        this._bufferInterval = null;
        this._qualityInterval = null;
        this._gesture = { startX:0, startY:0, isGesture:false, type:null, longPressTimer:null, tempRate:null, isLongPress:false, startBrightnessPercent:100, startVolume:1 };
        this._subtitleDelay = this.config.subtitleDelay || 0;
        this._prefsKey = this.config.storageKey + ':prefs';
        this._progressKey = this.config.storageKey + ':progress';
        this._lastChapterIndex = -1;
        this._originalControlsAttr = null;
        this._isDebugVisible = false;
        this._gestureIndicatorTimer = null;
        this._originalVideoStyle = null;

        this._currentSource = null;

        this._bindMethods();
        this._wrapAndBuild();
        this._bindEvents();

        this._initSubtitles();

        var self = this;
        var loadInitialSources = function() {
            if (self.config.sources && self.config.sources.length) {
                self._loadSource(self.config.sources);
            } else if (self._playlist && self._playlist.length) {
                self._loadPlaylistItem(0);
            } else {
                // 如果 video 内已有 source 标签,调用 load 来触发事件
                if (self.video.querySelector('source')) {
                    self.video.load();
                }
            }
        };
        this._initHLS(loadInitialSources);

        if (this.config.poster) {
            this.video.poster = this.config.poster;
            if (this.posterEl) this.posterEl.style.backgroundImage = 'url(' + this.config.poster + ')';
        }

        if (this.config.autoplay) this.video.autoplay = true;
        if (this.config.loop) this.video.loop = true;
        if (this.config.muted) {
            this.video.muted = true;
            this.video.volume = 0;
        }

        if (this.config.subtitleStyle) {
            this._applySubtitleStyle(this.config.subtitleStyle);
        }

        if (this.config.enablePreferenceMemory) {
            this._loadPreferences();
        }

        this._startMonitoring();

        if (this.config.wakeLock && this._wakeLockSupported) {
            this._requestWakeLock();
        }

        if (this.config.sources && this.config.sources.length && this.wrapper) {
            this.wrapper.classList.add('is-loading');
        }

        VideoPlayer._instances.push(this);

        if (typeof this.config.onReady === 'function') this.config.onReady.call(this, this);
        this.emit('ready', this);
    }

    VideoPlayer._instances = [];
    VideoPlayer.getInstances = function () { return VideoPlayer._instances.slice(); };
    VideoPlayer.destroyAll = function () {
        VideoPlayer._instances.forEach(function (player) { player.destroy(); });
        VideoPlayer._instances = [];
    };

    VideoPlayer.prototype = Object.create(EventEmitter.prototype);
    VideoPlayer.prototype.constructor = VideoPlayer;

    // ---------- 方法绑定 ----------
    VideoPlayer.prototype._bindMethods = function () {
        this.play = this.play.bind(this);
        this.pause = this.pause.bind(this);
        this.toggle = this.toggle.bind(this);
        this.seek = this.seek.bind(this);
        this.setVolume = this.setVolume.bind(this);
        this.toggleMute = this.toggleMute.bind(this);
        this.setPlaybackRate = this.setPlaybackRate.bind(this);
        this.toggleFullscreen = this.toggleFullscreen.bind(this);
        this.togglePip = this.togglePip.bind(this);
        this.load = this.load.bind(this);
        this.destroy = this.destroy.bind(this);
        this.setOrientation = this.setOrientation.bind(this);
        this.toggleOrientation = this.toggleOrientation.bind(this);
        this.requestCast = this.requestCast.bind(this);

        this._onPlay = this._onPlay.bind(this);
        this._onPause = this._onPause.bind(this);
        this._onEnded = this._onEnded.bind(this);
        this._onTimeUpdate = this._onTimeUpdate.bind(this);
        this._onLoadedMetadata = this._onLoadedMetadata.bind(this);
        this._onWaiting = this._onWaiting.bind(this);
        this._onCanPlay = this._onCanPlay.bind(this);
        this._onError = this._onError.bind(this);
        this._onVolumeChange = this._onVolumeChange.bind(this);
        this._onPipChange = this._onPipChange.bind(this);
        this._onStalled = this._onStalled.bind(this);
        this._onPlaying = this._onPlaying.bind(this);

        this._onProgressDown = this._onProgressDown.bind(this);
        this._onProgressMove = this._onProgressMove.bind(this);
        this._onProgressUp = this._onProgressUp.bind(this);

        this._onKeydown = this._onKeydown.bind(this);
        this._onFullscreenChange = this._onFullscreenChange.bind(this);
        this._onResize = this._onResize.bind(this);

        this._toggleMenu = this._toggleMenu.bind(this);
        this._closeMenu = this._closeMenu.bind(this);
        this._onOutsideClick = this._onOutsideClick.bind(this);
        this._toggleABLoop = this._toggleABLoop.bind(this);
        this._toggleLock = this._toggleLock.bind(this);
        this._toggleSettingsPanel = this._toggleSettingsPanel.bind(this);
        this._closeSettingsPanel = this._closeSettingsPanel.bind(this);
        this._downloadCurrentVideo = this._downloadCurrentVideo.bind(this);

        this._onVideoProgress = this._onVideoProgress.bind(this);
        this._onHlsLevelSwitched = this._onHlsLevelSwitched.bind(this);
        this._onHlsAudioTrackSwitched = this._onHlsAudioTrackSwitched.bind(this);
        this._onHlsError = this._onHlsError.bind(this);
        this._onGestureStart = this._onGestureStart.bind(this);
        this._onGestureMove = this._onGestureMove.bind(this);
        this._onGestureEnd = this._onGestureEnd.bind(this);
        this._onLongPressTimer = this._onLongPressTimer.bind(this);
        this._onVisibilityChange = this._onVisibilityChange.bind(this);

        this._toggleDebugPanel = this._toggleDebugPanel.bind(this);
        this._closeDebugPanel = this._closeDebugPanel.bind(this);
        this._updateDebugPanel = this._updateDebugPanel.bind(this);

        this._updateABMarks = this._updateABMarks.bind(this);
        this._updateSettingsPanel = this._updateSettingsPanel.bind(this);
    };

    // ---------- 构建 DOM ----------
    VideoPlayer.prototype._wrapAndBuild = function () {
        var video = this.video;
        this._originalControlsAttr = video.getAttribute('controls');
        this._originalVideoStyle = video.getAttribute('style');
        video.removeAttribute('controls');

        var parent = video.parentNode;
        var wrapper = document.createElement('div');
        wrapper.className = 'video-player';
        wrapper.style.setProperty('--cp-theme', this.config.themeColor || '#6c5ce7');
        parent.insertBefore(wrapper, video);
        wrapper.appendChild(video);
        this.wrapper = wrapper;

        if (typeof this.config.aspectRatio === 'string' && this.config.aspectRatio) {
            wrapper.style.aspectRatio = this.config.aspectRatio;
            wrapper.style.height = 'auto';
            video.style.position = 'absolute';
            video.style.top = '0';
            video.style.right = '0';
            video.style.bottom = '0';
            video.style.left = '0';
            video.style.width = '100%';
            video.style.height = '100%';
            video.style.objectFit = this.config.videoFit !== undefined ? this.config.videoFit : 'contain';
        } else {
            video.style.position = '';
            video.style.top = '';
            video.style.right = '';
            video.style.bottom = '';
            video.style.left = '';
            video.style.width = '100%';
            video.style.height = 'auto';
            video.style.objectFit = '';
        }

        if (this.config.poster) {
            var poster = document.createElement('div');
            poster.className = 'cp-poster';
            poster.style.backgroundImage = 'url(' + this.config.poster + ')';
            wrapper.appendChild(poster);
            this.posterEl = poster;
        }

        var bigPlay = document.createElement('div');
        bigPlay.className = 'cp-big-play show';
        bigPlay.setAttribute('aria-hidden', 'true');
        bigPlay.innerHTML = ICONS.play;
        wrapper.appendChild(bigPlay);
        this.bigPlayBtn = bigPlay;

        var loading = document.createElement('div');
        loading.className = 'cp-loading';
        wrapper.appendChild(loading);
        this.loadingEl = loading;

        var gestureIndicator = document.createElement('div');
        gestureIndicator.className = 'cp-gesture-indicator';
        gestureIndicator.innerHTML = '<span class="cp-gesture-icon"></span><span class="cp-gesture-value">100%</span>';
        wrapper.appendChild(gestureIndicator);
        this.gestureIndicatorEl = gestureIndicator;
        this.gestureIndicatorIconEl = gestureIndicator.querySelector('.cp-gesture-icon');
        this.gestureIndicatorValueEl = gestureIndicator.querySelector('.cp-gesture-value');

        var self = this;
        var topRight = document.createElement('div');
        topRight.className = 'cp-top-right';

        var menuWrap = document.createElement('div');
        menuWrap.className = 'cp-menu-wrap';
        var menuBtn = document.createElement('button');
        menuBtn.className = 'cp-btn cp-menu-btn';
        menuBtn.setAttribute('aria-label', '更多');
        menuBtn.innerHTML = ICONS.menu;
        this.menuBtn = menuBtn;
        menuWrap.appendChild(menuBtn);

        var dropdown = document.createElement('div');
        dropdown.className = 'cp-menu-dropdown';

        // 方向切换
        var orientItem = document.createElement('button');
        orientItem.className = 'cp-menu-item';
        orientItem.setAttribute('aria-label', '横竖屏切换');
        orientItem.innerHTML = ICONS.orientationAuto;
        orientItem.addEventListener('click', function (e) {
            e.stopPropagation();
            self.toggleOrientation();
            self._closeMenu();
        });
        this.orientationBtn = orientItem;
        dropdown.appendChild(orientItem);

        // 投屏
        var castItem = document.createElement('button');
        castItem.className = 'cp-menu-item';
        castItem.setAttribute('aria-label', '投屏');
        castItem.innerHTML = ICONS.cast;
        castItem.addEventListener('click', function (e) {
            e.stopPropagation();
            self.requestCast();
            self._closeMenu();
        });
        dropdown.appendChild(castItem);

        // 调试
        var debugItem = document.createElement('button');
        debugItem.className = 'cp-menu-item';
        debugItem.setAttribute('aria-label', '调试信息');
        debugItem.innerHTML = ICONS.debug;
        debugItem.addEventListener('click', function (e) {
            e.stopPropagation();
            self._toggleDebugPanel();
            self._closeMenu();
        });
        dropdown.appendChild(debugItem);

        this.menuDropdown = dropdown;
        menuWrap.appendChild(dropdown);

        menuBtn.addEventListener('click', function (e) {
            e.stopPropagation();
            self._toggleMenu();
        });

        topRight.appendChild(menuWrap);
        wrapper.appendChild(topRight);

        var debugPanel = document.createElement('div');
        debugPanel.className = 'cp-debug-panel';
        debugPanel.innerHTML = `
            <div class="cp-debug-header">
                <span class="cp-debug-title">播放调试信息</span>
                <button class="cp-debug-close" aria-label="关闭">${ICONS.close}</button>
            </div>
            <div class="cp-debug-body">
                <div class="cp-debug-row"><span>分辨率</span><span class="cp-debug-value" id="cp-debug-resolution">--</span></div>
                <div class="cp-debug-row"><span>码率</span><span class="cp-debug-value" id="cp-debug-bitrate">--</span></div>
                <div class="cp-debug-row"><span>缓冲进度</span><span class="cp-debug-value" id="cp-debug-buffer">--</span></div>
                <div class="cp-debug-row"><span>帧丢弃</span><span class="cp-debug-value" id="cp-debug-dropped">--</span></div>
                <div class="cp-debug-row"><span>当前时间</span><span class="cp-debug-value" id="cp-debug-time">--</span></div>
            </div>
        `;
        this.debugPanelEl = debugPanel;
        this.debugCloseBtn = debugPanel.querySelector('.cp-debug-close');
        this.debugCloseBtn.addEventListener('click', function (e) {
            e.stopPropagation();
            self._closeDebugPanel();
        });
        wrapper.appendChild(debugPanel);

        var sideControls = document.createElement('div');
        sideControls.className = 'cp-side-controls';
        this.sideControlsEl = sideControls;

        // A-B 循环按钮(仅全屏显示)
        var abBtn = document.createElement('button');
        abBtn.className = 'cp-btn cp-abloop-btn';
        abBtn.setAttribute('aria-label', 'A-B循环');
        abBtn.innerHTML = ICONS.abLoop;
        this.abLoopBtn = abBtn;
        abBtn.addEventListener('click', this._toggleABLoop);
        sideControls.appendChild(abBtn);

        var lockBtn = document.createElement('button');
        lockBtn.className = 'cp-btn cp-lock-btn';
        lockBtn.setAttribute('aria-label', '锁定控件');
        lockBtn.innerHTML = ICONS.unlock;
        this.lockBtn = lockBtn;
        lockBtn.addEventListener('click', function (e) {
            e.stopPropagation();
            self._toggleLock();
        });
        sideControls.appendChild(lockBtn);

        wrapper.appendChild(sideControls);

        if (!this.config.controls) {
            topRight.style.display = 'none';
            sideControls.style.display = 'none';
            if (this.bigPlayBtn) this.bigPlayBtn.classList.remove('show');
        }

        var controls = document.createElement('div');
        controls.className = 'cp-controls';
        if (!this.config.controls) controls.style.display = 'none';

        var top = document.createElement('div');
        top.className = 'cp-controls-top';

        if (this.controlsList.includes('time')) {
            var timeSpan = document.createElement('span');
            timeSpan.className = 'cp-time';
            timeSpan.textContent = '00:00 / 00:00';
            this.timeDisplay = timeSpan;
            top.appendChild(timeSpan);
        }

        // 字幕下拉框(保留)
        if (this.controlsList.includes('subtitles')) {
            var subSelect = document.createElement('select');
            subSelect.className = 'cp-subtitle-select';
            var offOpt = document.createElement('option');
            offOpt.value = 'off';
            offOpt.textContent = '字幕: 关闭';
            subSelect.appendChild(offOpt);
            this.subtitleSelect = subSelect;
            top.appendChild(subSelect);
        }

        controls.appendChild(top);

        if (this.controlsList.includes('progress')) {
            var progress = document.createElement('div');
            progress.className = 'cp-progress';
            progress.setAttribute('role', 'slider');
            progress.setAttribute('aria-label', '播放进度');
            progress.setAttribute('aria-valuemin', '0');
            progress.setAttribute('aria-valuemax', '0');
            progress.setAttribute('aria-valuenow', '0');

            var buffered = document.createElement('div');
            buffered.className = 'cp-progress-buffered';
            progress.appendChild(buffered);
            this.progressBuffered = buffered;

            var filled = document.createElement('div');
            filled.className = 'cp-progress-filled';
            progress.appendChild(filled);
            this.progressFilled = filled;

            var markA = document.createElement('div');
            markA.className = 'cp-ab-mark-a';
            markA.style.cssText = 'display:none;position:absolute;top:-6px;width:2px;height:16px;background:var(--cp-theme);z-index:4;border-radius:1px;pointer-events:none;';
            markA.innerHTML = '<span style="position:absolute;top:-18px;left:50%;transform:translateX(-50%);font-size:11px;color:var(--cp-theme);font-weight:bold;white-space:nowrap;">A</span>';
            progress.appendChild(markA);

            var markB = document.createElement('div');
            markB.className = 'cp-ab-mark-b';
            markB.style.cssText = 'display:none;position:absolute;top:-6px;width:2px;height:16px;background:var(--cp-theme);z-index:4;border-radius:1px;pointer-events:none;';
            markB.innerHTML = '<span style="position:absolute;top:-18px;left:50%;transform:translateX(-50%);font-size:11px;color:var(--cp-theme);font-weight:bold;white-space:nowrap;">B</span>';
            progress.appendChild(markB);

            this.progressEl = progress;
            this.abMarkA = markA;
            this.abMarkB = markB;
            controls.appendChild(progress);
        }

        var bottom = document.createElement('div');
        bottom.className = 'cp-controls-bottom';

        // 播放按钮(左侧)
        if (this.controlsList.includes('play')) {
            var playBtn = document.createElement('button');
            playBtn.className = 'cp-btn cp-play-btn';
            playBtn.setAttribute('aria-label', '播放');
            playBtn.setAttribute('aria-pressed', 'false');
            playBtn.innerHTML = ICONS.play;
            this.playBtn = playBtn;
            bottom.appendChild(playBtn);
        }

        // 弹性空白,将右侧按钮推至右侧
        var spacer = document.createElement('div');
        spacer.style.flex = '1';
        bottom.appendChild(spacer);

        // 音量按钮(右侧组)
        if (this.controlsList.includes('volume')) {
            var volWrap = document.createElement('div');
            volWrap.className = 'cp-volume-wrap';
            var volBtn = document.createElement('button');
            volBtn.className = 'cp-btn cp-vol-btn';
            volBtn.setAttribute('aria-label', '静音');
            volBtn.setAttribute('aria-pressed', this.isMuted ? 'true' : 'false');
            volBtn.innerHTML = this.isMuted ? ICONS.volumeMuted : ICONS.volumeHigh;
            this.volumeBtn = volBtn;
            volWrap.appendChild(volBtn);
            bottom.appendChild(volWrap);
        }

        // 设置按钮(右侧组)
        var settingsBtn = document.createElement('button');
        settingsBtn.className = 'cp-btn cp-settings-btn';
        settingsBtn.setAttribute('aria-label', '设置');
        settingsBtn.innerHTML = ICONS.settings;
        this.settingsBtn = settingsBtn;
        bottom.appendChild(settingsBtn);

        // 设置面板(绝对定位,不影响布局)
        var settingsPanel = document.createElement('div');
        settingsPanel.className = 'cp-settings-panel';
        settingsPanel.innerHTML = `
            <div class="cp-settings-title">设置</div>
            <div class="cp-settings-item" data-setting="speed">
                <span>倍速</span>
                <select id="cp-settings-speed-select"></select>
            </div>
            <div class="cp-settings-item" data-setting="subtitles">
                <span>字幕</span>
                <select id="cp-settings-subtitles"></select>
            </div>
            <div class="cp-settings-item" data-setting="quality">
                <span>清晰度</span>
                <select id="cp-settings-quality"></select>
            </div>
            <div class="cp-settings-item" data-setting="download" style="display:none;">
                <span>下载</span>
                <span class="cp-settings-value" id="cp-settings-download">⬇ 下载视频</span>
            </div>
        `;
        this.settingsPanel = settingsPanel;
        controls.appendChild(settingsPanel);

        // 全屏按钮(右侧组,最右)
        if (this.controlsList.includes('fullscreen')) {
            var fsBtn = document.createElement('button');
            fsBtn.className = 'cp-btn cp-fullscreen-btn';
            fsBtn.setAttribute('aria-label', '全屏');
            fsBtn.setAttribute('aria-pressed', 'false');
            fsBtn.innerHTML = ICONS.fullscreen;
            this.fullscreenBtn = fsBtn;
            bottom.appendChild(fsBtn);
        }

        controls.appendChild(bottom);
        wrapper.appendChild(controls);
        this.controlsEl = controls;

        this.video.volume = this.isMuted ? 0 : (this.volume || 1);
        this.video.muted = this.isMuted;

        // 绑定设置面板下载点击事件
        var downloadItem = this.settingsPanel.querySelector('[data-setting="download"]');
        if (downloadItem) {
            downloadItem.addEventListener('click', function(e) {
                e.stopPropagation();
                self._downloadCurrentVideo();
            });
        }

        // 初始化设置面板选项
        this._updateSettingsPanel();

        // 绑定设置面板中速度选择的 change 事件(一次性)
        var speedSelect = this.settingsPanel.querySelector('#cp-settings-speed-select');
        if (speedSelect) {
            speedSelect.addEventListener('change', function (e) {
                var rate = parseFloat(this.value);
                if (!isNaN(rate)) {
                    self.setPlaybackRate(rate);
                }
            });
        }
    };

    // ---------- 更新设置面板内容 ----------
    VideoPlayer.prototype._updateSettingsPanel = function () {
        var panel = this.settingsPanel;
        if (!panel) return;

        // ---- 倍速下拉 ----
        var speedSelect = panel.querySelector('#cp-settings-speed-select');
        if (speedSelect) {
            var currentRate = this.playbackRate || 1;
            while (speedSelect.options.length) speedSelect.remove(0);
            var rates = this.config.playbackRates || [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 5.0];
            rates.forEach(function (r) {
                var opt = document.createElement('option');
                opt.value = r;
                opt.textContent = (r === 1 ? '1×' : r + '×');
                if (Math.abs(r - currentRate) < 0.001) opt.selected = true;
                speedSelect.appendChild(opt);
            });
            // 若当前值不在列表中,补一个自定义项
            var found = false;
            for (var i = 0; i < speedSelect.options.length; i++) {
                if (Math.abs(parseFloat(speedSelect.options[i].value) - currentRate) < 0.001) {
                    found = true;
                    speedSelect.selectedIndex = i;
                    break;
                }
            }
            if (!found) {
                var customOpt = document.createElement('option');
                customOpt.value = currentRate;
                customOpt.textContent = currentRate + '×';
                customOpt.selected = true;
                speedSelect.appendChild(customOpt);
            }
        }

        // ---- 自定义倍速输入框(仅添加一次) ----
        var speedItem = panel.querySelector('[data-setting="speed"]');
        if (speedItem && !speedItem.querySelector('.cp-custom-speed-wrap')) {
            var wrap = document.createElement('div');
            wrap.className = 'cp-custom-speed-wrap';

            var label = document.createElement('span');
            label.className = 'cp-speed-label';
            label.textContent = '自定义';
            wrap.appendChild(label);

            var input = document.createElement('input');
            input.type = 'number';
            input.step = '0.25';
            input.min = '0.1';
            input.max = '16';
            input.placeholder = '倍速';
            input.value = this.playbackRate || 1;
            wrap.appendChild(input);

            var applyBtn = document.createElement('button');
            applyBtn.className = 'cp-speed-apply';
            applyBtn.textContent = '应用';
            wrap.appendChild(applyBtn);

            var self = this;
            var applySpeed = function() {
                var val = parseFloat(input.value);
                if (!isNaN(val) && val > 0) {
                    self.setPlaybackRate(val);
                    // 同步下拉选中
                    if (speedSelect) {
                        var found = false;
                        for (var i = 0; i < speedSelect.options.length; i++) {
                            if (Math.abs(parseFloat(speedSelect.options[i].value) - val) < 0.001) {
                                speedSelect.selectedIndex = i;
                                found = true;
                                break;
                            }
                        }
                        if (!found) {
                            var opt = document.createElement('option');
                            opt.value = val;
                            opt.textContent = val + '×';
                            opt.selected = true;
                            speedSelect.appendChild(opt);
                        }
                    }
                    input.value = val;
                } else {
                    input.style.borderColor = '#ff4444';
                    setTimeout(function() { input.style.borderColor = ''; }, 800);
                }
            };
            applyBtn.addEventListener('click', applySpeed);
            input.addEventListener('keydown', function(e) { if (e.key === 'Enter') applySpeed(); });
            input.addEventListener('blur', function() {
                var val = parseFloat(input.value);
                if (isNaN(val) || val <= 0) input.value = self.playbackRate || 1;
            });
            // 监听外部倍速变化
            this.on('playbackRateChange', function(rate) { input.value = rate; });

            speedItem.appendChild(wrap);
        } else if (speedItem) {
            var existingInput = speedItem.querySelector('.cp-custom-speed-wrap input');
            if (existingInput) existingInput.value = this.playbackRate || 1;
        }

        // ---- 字幕 ----
        var subSelect = panel.querySelector('#cp-settings-subtitles');
        if (subSelect) {
            while (subSelect.options.length) subSelect.remove(0);
            var offOpt = document.createElement('option');
            offOpt.value = 'off';
            offOpt.textContent = '关闭';
            subSelect.appendChild(offOpt);
            var self = this;
            this._trackElements.forEach(function (track, index) {
                var opt = document.createElement('option');
                opt.value = track.srclang;
                opt.textContent = track.label || track.srclang || ('字幕' + (index+1));
                if (self._currentSubtitleTrack && self._currentSubtitleTrack.srclang === track.srclang) {
                    opt.selected = true;
                }
                subSelect.appendChild(opt);
            });
            if (this._currentSubtitleTrack) subSelect.value = this._currentSubtitleTrack.srclang;
            else subSelect.value = 'off';
            subSelect.onchange = function () {
                var val = this.value;
                if (val === 'off') {
                    self._currentSubtitleTrack = null;
                    self._trackElements.forEach(function (t) { t.mode = 'hidden'; });
                } else {
                    var selected = null;
                    self._trackElements.forEach(function (t) {
                        if (t.srclang === val) { t.mode = 'showing'; selected = t; }
                        else t.mode = 'hidden';
                    });
                    self._currentSubtitleTrack = selected;
                }
                if (self.subtitleSelect) self.subtitleSelect.value = val;
                self.emit('subtitleTrackChange', val);
                if (typeof self.config.onSubtitleTrackChange === 'function') {
                    self.config.onSubtitleTrackChange.call(self, val);
                }
            };
        }

        // ---- 清晰度 ----
        var qualitySelect = panel.querySelector('#cp-settings-quality');
        if (qualitySelect) {
            while (qualitySelect.options.length) qualitySelect.remove(0);
            var qualities = this.getQualities();
            if (qualities.length === 0) {
                qualitySelect.parentNode.style.display = 'none';
            } else {
                qualitySelect.parentNode.style.display = 'flex';
                var self = this;
                qualities.forEach(function (q) {
                    var opt = document.createElement('option');
                    opt.value = q.index;
                    opt.textContent = q.label || (q.height ? q.height + 'p' : 'Auto');
                    if (self._currentQualityIndex === q.index) opt.selected = true;
                    qualitySelect.appendChild(opt);
                });
                qualitySelect.onchange = function () {
                    var idx = parseInt(this.value, 10);
                    self.setQuality(idx);
                };
            }
        }

        // ---- 下载项显示条件 ----
        var downloadItem = panel.querySelector('[data-setting="download"]');
        if (downloadItem) {
            var isM3U8 = this._isM3U8Source();
            if (isM3U8 || !this._currentSource) {
                downloadItem.style.display = 'none';
            } else {
                downloadItem.style.display = 'flex';
            }
        }
    };

    // ---------- 判断当前是否为 m3u8 ----------
    VideoPlayer.prototype._isM3U8Source = function () {
        if (this._hls) return true; // HLS 已初始化视为 m3u8
        var src = this.video.currentSrc || this.video.src || '';
        if (/\.m3u8/i.test(src)) return true;
        if (this._currentSource && this._currentSource.type === 'application/x-mpegURL') return true;
        return false;
    };

    // ---------- 下载当前视频(直接触发下载,无任何判断) ----------
    VideoPlayer.prototype._downloadCurrentVideo = function () {
        var src = this.video.currentSrc || this.video.src;
        if (!src) {
            console.warn('[VideoPlayer] 无视频源可下载');
            return;
        }
        this._triggerDownload(src, 'video.mp4');
    };

    VideoPlayer.prototype._triggerDownload = function (url, filename) {
        var a = document.createElement('a');
        a.href = url;
        a.download = filename || 'video.mp4';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    };

    // ---------- HLS 初始化(已修改:支持从 video 内部 source 标签提取) ----------
    VideoPlayer.prototype._initHLS = function (callback) {
        var sources = this.config.sources || [];

        // 如果 config.sources 为空,尝试从 video 内部已有的 source 标签提取
        if (!sources.length) {
            var sourceEls = this.video.querySelectorAll('source');
            for (var i = 0; i < sourceEls.length; i++) {
                var src = sourceEls[i].getAttribute('src');
                var type = sourceEls[i].getAttribute('type') || '';
                if (src) {
                    sources.push({ src: src, type: type });
                }
            }
            // 将提取的 sources 也保存到 config 中,供后续使用
            if (sources.length) this.config.sources = sources;
        }

        var hasHLS = sources.some(function (s) { 
            return (typeof s === 'string' ? false : s.type === 'application/x-mpegURL'); 
        });

        if (!hasHLS) {
            if (callback) callback();
            return;
        }

        var self = this;
        var done = function() {
            self._setupHls();
            if (callback) callback();
        };

        if (global.Hls) {
            done();
        } else {
            loadScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/hls.min.js')
                .then(done)
                .catch(function (err) {
                    console.warn('[VideoPlayer] hls.js 加载失败', err);
                    if (typeof self.config.onError === 'function') {
                        self.config.onError({ message: 'hls.js 加载失败,无法播放 HLS' });
                    }
                    if (callback) callback();
                });
        }
    };

    VideoPlayer.prototype._setupHls = function () {
        var video = this.video;
        var hlsConfig = extend({
            enableWorker: true,
            lowLatencyMode: true,
            backBufferLength: 30
        }, this.config.hlsConfig || {});
        var hls = new global.Hls(hlsConfig);
        this._hls = hls;

        hls.on(global.Hls.Events.ERROR, this._onHlsError);
        hls.on(global.Hls.Events.LEVEL_SWITCHED, this._onHlsLevelSwitched);
        hls.on(global.Hls.Events.AUDIO_TRACK_SWITCHED, this._onHlsAudioTrackSwitched);
    };

    VideoPlayer.prototype._onHlsError = function (event, data) {
        if (!this._hls) return;
        if (data.fatal) {
            switch (data.type) {
                case global.Hls.ErrorTypes.NETWORK_ERROR:
                    this._hls.startLoad();
                    break;
                case global.Hls.ErrorTypes.MEDIA_ERROR:
                    this._hls.recoverMediaError();
                    break;
                default:
                    console.error('[VideoPlayer] HLS 致命错误', data);
                    this._onError({ message: 'HLS 播放失败' });
                    break;
            }
        }
    };

    VideoPlayer.prototype._onHlsLevelSwitched = function (event, data) {
        this._currentQualityIndex = data.level;
        this.emit('qualityChange', { level: data.level, auto: data.auto });
        if (typeof this.config.onQualityChange === 'function') this.config.onQualityChange.call(this, data);
        this._savePreferencesIfNeeded();
        if (this._isDebugVisible) this._updateDebugPanel();
        this._updateSettingsPanel();
    };

    VideoPlayer.prototype._onHlsAudioTrackSwitched = function (event, data) {
        this._currentAudioTrackIndex = data.id;
        this.emit('audioTrackChange', { id: data.id });
        if (typeof this.config.onAudioTrackChange === 'function') this.config.onAudioTrackChange.call(this, data);
    };

    // ---------- 源加载(已修改:HLS 时直接使用 hls.js,不创建 source 标签) ----------
    VideoPlayer.prototype._loadSource = function (sources, sourceIndex) {
        if (!sources || !sources.length) return;
        if (sourceIndex === undefined) sourceIndex = 0;

        if (this._sourceCleanup) {
            this._sourceCleanup();
            this._sourceCleanup = null;
        }

        this._sourceIndex = sourceIndex;
        var self = this;
        var video = this.video;

        // 移除已有的 source 标签(如果有)
        video.querySelectorAll('source').forEach(function (s) { s.remove(); });

        var source = sources[sourceIndex];
        if (!source) {
            this._onError({ message: '所有源加载失败' });
            return;
        }

        if (typeof source === 'string') source = { src: source };
        this._currentSource = source;

        if (this._hls && source.type === 'application/x-mpegURL') {
            // 使用 hls.js 加载
            if (this._hls.media !== video) {
                this._hls.attachMedia(video);
            }
            this._hls.loadSource(source.src);
        } else {
            // 非 HLS,创建 source 并加载
            var sourceEl = document.createElement('source');
            sourceEl.src = source.src;
            if (source.type) sourceEl.type = source.type;
            video.appendChild(sourceEl);
            video.load();
        }

        var cleanup = function () {
            video.removeEventListener('error', onError);
            video.removeEventListener('canplay', onCanPlay);
            if (self._sourceCleanup === cleanup) {
                self._sourceCleanup = null;
            }
        };

        var onError = function () {
            cleanup();
            if (self._sourceIndex < sources.length - 1) {
                self._loadSource(sources, self._sourceIndex + 1);
                self.emit('sourceChange', { index: self._sourceIndex });
                if (typeof self.config.onSourceChange === 'function') {
                    self.config.onSourceChange.call(self, self._sourceIndex);
                }
            } else {
                self._onError({ message: '视频加载失败,无备用源' });
            }
        };

        var onCanPlay = function () {
            cleanup();
            self._updateSettingsPanel();
        };

        video.addEventListener('error', onError);
        video.addEventListener('canplay', onCanPlay, { once: true });

        this._sourceCleanup = cleanup;
    };

    // ---------- 字幕初始化 ----------
    VideoPlayer.prototype._initSubtitles = function () {
        this._clearSubtitles();

        var tracks = this._tracks;
        if (!tracks || !tracks.length) {
            return;
        }
        var video = this.video;
        var self = this;
        var defaultLang = navigator.language || navigator.userLanguage;

        tracks.forEach(function (track, index) {
            var src = track.src;
            var isSrt = /\.srt$/i.test(src);
            if (isSrt) {
                fetch(src)
                    .then(function (res) { return res.text(); })
                    .then(function (text) {
                        var vttUrl = self._srtToVttBlobUrl(text);
                        self._blobUrls.push(vttUrl);
                        self._addTrackElement(track, index, vttUrl, defaultLang);
                    })
                    .catch(function (err) {
                        console.warn('[VideoPlayer] SRT 转换失败', err);
                        self._addTrackElement(track, index, src, defaultLang);
                    });
            } else {
                self._addTrackElement(track, index, src, defaultLang);
            }
        });
    };

    VideoPlayer.prototype._clearSubtitles = function () {
        var video = this.video;
        if (!video) return;

        video.querySelectorAll('track').forEach(function (track) {
            track.parentNode.removeChild(track);
        });

        this._trackElements = [];
        this._currentSubtitleTrack = null;

        if (this.subtitleSelect) {
            while (this.subtitleSelect.options.length > 1) {
                this.subtitleSelect.remove(1);
            }
            this.subtitleSelect.value = 'off';
            this.subtitleSelect.style.display = 'none';
        }
        this._updateSettingsPanel();
    };

    VideoPlayer.prototype._addTrackElement = function (track, index, src, defaultLang) {
        var video = this.video;
        var trackEl = document.createElement('track');
        trackEl.kind = 'subtitles';
        trackEl.srclang = track.srclang || ('sub' + index);
        trackEl.label = track.label || track.srclang || ('字幕' + (index + 1));
        trackEl.src = src;
        trackEl.default = track.default || false;
        video.appendChild(trackEl);
        this._trackElements.push(trackEl);

        if (this._currentSubtitleTrack === null && (track.default || (defaultLang && defaultLang.indexOf(track.srclang) !== -1))) {
            trackEl.mode = 'showing';
            this._currentSubtitleTrack = trackEl;
        }

        if (this.subtitleSelect) {
            this.subtitleSelect.style.display = '';
            var opt = document.createElement('option');
            opt.value = trackEl.srclang;
            opt.textContent = trackEl.label;
            if (trackEl.mode === 'showing') opt.selected = true;
            this.subtitleSelect.appendChild(opt);
        }
        this._updateSettingsPanel();
    };

    VideoPlayer.prototype._srtToVttBlobUrl = function (srtText) {
        var lines = srtText.replace(/\r+/g, '').split('\n');
        var vttLines = ['WEBVTT', ''];
        for (var i = 0; i < lines.length; i++) {
            var line = lines[i];
            if (line.indexOf('-->') !== -1) {
                if (/\d+:\d{2}:\d{2}[,.]\d{3}/.test(line)) {
                    line = line.replace(/(\d+):(\d{2}):(\d{2})[,.](\d{3})/g, function(match, h, m, s, ms) {
                        var hours = parseInt(h, 10);
                        var hh = (hours < 10 ? '0' : '') + hours;
                        return hh + ':' + m + ':' + s + '.' + ms;
                    });
                } else {
                    line = line.replace(/(\d{1,2}):(\d{2})[,.](\d{3})/g, function(match, m, s, ms) {
                        return '00:' + (m.length === 1 ? '0' + m : m) + ':' + s + '.' + ms;
                    });
                }
            }
            vttLines.push(line);
        }
        return URL.createObjectURL(new Blob([vttLines.join('\n')], { type: 'text/vtt' }));
    };

    VideoPlayer.prototype._applySubtitleStyle = function (style) {
        if (!this.wrapper) return;
        var mapping = {
            fontSize: '--cp-subtitle-size',
            color: '--cp-subtitle-color',
            backgroundColor: '--cp-subtitle-bg',
            fontFamily: '--cp-subtitle-font',
            textShadow: '--cp-subtitle-shadow'
        };
        for (var key in mapping) {
            if (style[key]) this.wrapper.style.setProperty(mapping[key], style[key]);
        }
    };

    VideoPlayer.prototype.setSubtitleDelay = function (seconds) {
        this._subtitleDelay = seconds;
        this.emit('subtitleDelayChange', seconds);
    };

    // ---------- 清晰度、音轨 ----------
    VideoPlayer.prototype.getQualities = function () {
        if (this._hls && this._hls.levels) {
            return this._hls.levels.map(function (level, index) {
                return { index: index, height: level.height, bitrate: level.bitrate, label: (level.height ? level.height + 'p' : 'Auto') };
            });
        }
        return [];
    };

    VideoPlayer.prototype.setQuality = function (index) {
        if (!this._hls) return;
        if (index === -1) {
            this._hls.currentLevel = -1;
        } else if (index >= 0 && index < this._hls.levels.length) {
            this._hls.currentLevel = index;
        }
        this._currentQualityIndex = index;
        this._savePreferencesIfNeeded();
        if (this._isDebugVisible) this._updateDebugPanel();
        this._updateSettingsPanel();
    };

    VideoPlayer.prototype.getAudioTracks = function () {
        if (this._hls && this._hls.audioTracks) {
            return this._hls.audioTracks.map(function (track, index) {
                return { index: index, lang: track.lang, name: track.name, default: track.default };
            });
        }
        if (this.video.audioTracks) {
            var tracks = [];
            for (var i = 0; i < this.video.audioTracks.length; i++) {
                tracks.push({ index: i, lang: this.video.audioTracks[i].language, name: this.video.audioTracks[i].label });
            }
            return tracks;
        }
        return [];
    };

    VideoPlayer.prototype.setAudioTrack = function (index) {
        if (this._hls && this._hls.audioTracks) {
            this._hls.audioTrack = index;
            this._currentAudioTrackIndex = index;
            this._savePreferencesIfNeeded();
        } else if (this.video.audioTracks && this.video.audioTracks[index]) {
            for (var i = 0; i < this.video.audioTracks.length; i++) {
                this.video.audioTracks[i].enabled = (i === index);
            }
        }
    };

    // ---------- 播放列表 ----------
    VideoPlayer.prototype.setPlaylist = function (playlist) {
        this._playlist = playlist || [];
        this._playlistIndex = 0;
    };

    VideoPlayer.prototype.playNext = function () {
        if (this._playlist.length === 0) return;
        this._playlistIndex = (this._playlistIndex + 1) % this._playlist.length;
        this._loadPlaylistItem(this._playlistIndex);
    };

    VideoPlayer.prototype.playPrevious = function () {
        if (this._playlist.length === 0) return;
        this._playlistIndex = (this._playlistIndex - 1 + this._playlist.length) % this._playlist.length;
        this._loadPlaylistItem(this._playlistIndex);
    };

    VideoPlayer.prototype._loadPlaylistItem = function (index) {
        if (!this._playlist[index]) return;
        var item = this._playlist[index];
        this._playlistIndex = index;
        this.config.sources = [{ src: item.src, type: item.type }];
        this.load(this.config.sources);
        if (item.poster && this.posterEl) {
            this.posterEl.style.backgroundImage = 'url(' + item.poster + ')';
        }
        if (item.title) {
            this.emit('titleChange', item.title);
        }
    };

    // ---------- 快照 ----------
    VideoPlayer.prototype.captureSnapshot = function (type, quality) {
        type = type || 'image/png';
        quality = quality || 0.8;
        var video = this.video;
        var canvas = document.createElement('canvas');
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        var ctx = canvas.getContext('2d');
        ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
        return canvas.toDataURL(type, quality);
    };

    // ---------- 音频增强 ----------
    VideoPlayer.prototype._ensureAudioContext = function () {
        if (!this._audioContext) {
            var AudioContext = window.AudioContext || window.webkitAudioContext;
            if (!AudioContext) return null;
            try {
                this._audioContext = new AudioContext();
                var source = this._audioContext.createMediaElementSource(this.video);
                this._gainNode = this._audioContext.createGain();
                this._gainNode.gain.value = this.config.audioGain || 1;
                source.connect(this._gainNode);

                if (this.config.downmixToStereo) {
                    this._gainNode.channelCount = 2;
                    this._gainNode.channelCountMode = 'explicit';
                    this._gainNode.channelInterpretation = 'speakers';
                }

                if (this.config.eqSettings) {
                    this._setupEQ(this.config.eqSettings);
                } else {
                    this._gainNode.connect(this._audioContext.destination);
                }
            } catch (err) {
                console.warn('[VideoPlayer] AudioContext 创建失败', err);
                this._audioContext = null;
                this._gainNode = null;
                return null;
            }
        }
        if (this._audioContext && this._audioContext.state === 'suspended') {
            this._audioContext.resume().catch(function(e) {
                console.warn('[VideoPlayer] AudioContext resume failed', e);
            });
        }
        return this._audioContext;
    };

    VideoPlayer.prototype._suspendAudioContext = function () {
        if (this._audioContext && this._audioContext.state === 'running') {
            this._audioContext.suspend().catch(function(e) {
                console.warn('[VideoPlayer] AudioContext suspend failed', e);
            });
        }
    };

    VideoPlayer.prototype._resumeAudioContext = function () {
        if (this._audioContext && this._audioContext.state === 'suspended') {
            this._audioContext.resume().catch(function(e) {
                console.warn('[VideoPlayer] AudioContext resume failed', e);
            });
        }
    };

    VideoPlayer.prototype._setupEQ = function (eq) {
        if (!this._audioContext || !this._gainNode) return;

        this._gainNode.disconnect();
        this._eqNodes.forEach(function (node) { node.disconnect(); });
        this._eqNodes = [];

        var frequencies = { bass: 100, mid: 1000, treble: 5000 };
        var self = this;
        var lastNode = this._gainNode;
        var hasFilter = false;

        ['bass', 'mid', 'treble'].forEach(function (band) {
            if (typeof eq[band] !== 'number') return;
            hasFilter = true;
            var filter = self._audioContext.createBiquadFilter();
            filter.type = 'peaking';
            filter.frequency.value = frequencies[band];
            filter.gain.value = eq[band];
            filter.Q.value = 1.0;
            lastNode.connect(filter);
            lastNode = filter;
            self._eqNodes.push(filter);
        });

        if (hasFilter) {
            lastNode.connect(self._audioContext.destination);
        } else {
            this._gainNode.connect(this._audioContext.destination);
        }
    };

    VideoPlayer.prototype.setAudioGain = function (gain) {
        this.config.audioGain = gain;
        if (this._gainNode) {
            this._gainNode.gain.value = gain;
        }
    };

    VideoPlayer.prototype.setEQ = function (eq) {
        this.config.eqSettings = eq;
        if (this._audioContext) {
            this._setupEQ(eq);
        }
    };

    VideoPlayer.prototype.setDownmixToStereo = function (bool) {
        this.config.downmixToStereo = bool;
        if (this._audioContext && this._gainNode) {
            if (bool) {
                this._gainNode.channelCount = 2;
                this._gainNode.channelCountMode = 'explicit';
                this._gainNode.channelInterpretation = 'speakers';
            } else {
                this._gainNode.channelCountMode = 'max';
            }
        }
    };

    // ---------- 视频处理 ----------
    VideoPlayer.prototype.setVideoFilter = function (filter) {
        if (!filter) filter = {};
        var filterStr = '';
        if (filter.brightness !== undefined) filterStr += 'brightness(' + filter.brightness + ') ';
        if (filter.contrast !== undefined) filterStr += 'contrast(' + filter.contrast + ') ';
        if (filter.saturate !== undefined) filterStr += 'saturate(' + filter.saturate + ') ';
        if (filter.grayscale !== undefined) filterStr += 'grayscale(' + filter.grayscale + ') ';
        if (filter.blur !== undefined) filterStr += 'blur(' + filter.blur + 'px) ';
        if (filter.hueRotate !== undefined) filterStr += 'hue-rotate(' + filter.hueRotate + 'deg) ';
        if (filter.sepia !== undefined) filterStr += 'sepia(' + filter.sepia + ') ';
        this.video.style.filter = filterStr.trim();
    };

    VideoPlayer.prototype.setVideoZoom = function (scale) {
        this.config.videoZoom = scale;
        this._applyVideoTransform();
    };

    VideoPlayer.prototype.setVideoPan = function (x, y) {
        this.config.videoPan = { x: x, y: y };
        this._applyVideoTransform();
    };

    VideoPlayer.prototype.setRotation = function (deg) {
        this.config.rotation = deg;
        this._applyVideoTransform();
    };

    VideoPlayer.prototype._applyVideoTransform = function () {
        var transform = '';
        if (this.config.rotation) transform += 'rotate(' + this.config.rotation + 'deg) ';
        if (this.config.videoZoom !== 1) transform += 'scale(' + this.config.videoZoom + ') ';
        if (this.config.videoPan.x || this.config.videoPan.y) {
            transform += 'translate(' + this.config.videoPan.x + 'px,' + this.config.videoPan.y + 'px) ';
        }
        this.video.style.transform = transform.trim();
    };

    // ---------- 进度记忆与偏好 ----------
    VideoPlayer.prototype._loadPreferences = function () {
        var prefs = localStorage.getItem(this._prefsKey);
        if (prefs) {
            try {
                var data = JSON.parse(prefs);
                if (data.volume !== undefined) this.setVolume(data.volume);
                if (data.muted !== undefined && data.muted) this.toggleMute();
                if (data.playbackRate) this.setPlaybackRate(data.playbackRate);
                if (data.quality !== undefined) this.setQuality(data.quality);
                if (data.audioTrack !== undefined) this.setAudioTrack(data.audioTrack);
            } catch (e) {}
        }
        if (this.config.enableProgressMemory) {
            var progress = localStorage.getItem(this._progressKey);
            if (progress) {
                var time = parseFloat(progress);
                if (!isNaN(time)) this.video.currentTime = time;
            }
        }
    };

    VideoPlayer.prototype._savePreferencesIfNeeded = function () {
        if (!this.config.enablePreferenceMemory) return;
        var prefs = {
            volume: this.volume,
            muted: this.isMuted,
            playbackRate: this.playbackRate,
            quality: this._currentQualityIndex,
            audioTrack: this._currentAudioTrackIndex
        };
        localStorage.setItem(this._prefsKey, JSON.stringify(prefs));
    };

    VideoPlayer.prototype.saveProgress = function () {
        if (!this.config.enableProgressMemory) return;
        localStorage.setItem(this._progressKey, this.video.currentTime);
    };

    // ---------- 监控 ----------
    VideoPlayer.prototype._startMonitoring = function () {
        var self = this;
        this._bufferInterval = setInterval(function () {
            self._emitBufferInfo();
            self._emitPlaybackQuality();
            if (self._isDebugVisible) self._updateDebugPanel();
        }, 1000);
    };

    VideoPlayer.prototype._emitBufferInfo = function () {
        var video = this.video;
        var buffered = video.buffered;
        var currentTime = video.currentTime;
        var bufferedEnd = 0;
        for (var i = 0; i < buffered.length; i++) {
            if (buffered.start(i) <= currentTime && currentTime <= buffered.end(i)) {
                bufferedEnd = buffered.end(i);
                break;
            }
        }
        var percent = video.duration ? (bufferedEnd / video.duration * 100) : 0;
        if (this.progressBuffered) {
            this.progressBuffered.style.width = percent + '%';
        }
        this.emit('bufferProgress', { percent: percent, bufferedEnd: bufferedEnd, duration: video.duration });
        if (typeof this.config.onBufferProgress === 'function') this.config.onBufferProgress.call(this, { percent: percent, bufferedEnd: bufferedEnd });
    };

    VideoPlayer.prototype._emitPlaybackQuality = function () {
        if (this.video.getVideoPlaybackQuality) {
            var stats = this.video.getVideoPlaybackQuality();
            this.emit('playbackQuality', stats);
            if (typeof this.config.onPlaybackQuality === 'function') this.config.onPlaybackQuality.call(this, stats);
        }
    };

    // ---------- 调试面板 ----------
    VideoPlayer.prototype._toggleDebugPanel = function () {
        if (this._isDebugVisible) {
            this._closeDebugPanel();
        } else {
            this._showDebugPanel();
        }
    };

    VideoPlayer.prototype._showDebugPanel = function () {
        if (!this.debugPanelEl) return;
        this.debugPanelEl.classList.add('show');
        this._isDebugVisible = true;
        this._updateDebugPanel();
    };

    VideoPlayer.prototype._closeDebugPanel = function () {
        if (!this.debugPanelEl) return;
        this.debugPanelEl.classList.remove('show');
        this._isDebugVisible = false;
    };

    VideoPlayer.prototype._updateDebugPanel = function () {
        if (!this.debugPanelEl || !this._isDebugVisible) return;
        var video = this.video;

        var resolutionText = video.videoWidth ? video.videoWidth + ' x ' + video.videoHeight : '--';
        var resolutionEl = this.debugPanelEl.querySelector('#cp-debug-resolution');
        if (resolutionEl) resolutionEl.textContent = resolutionText;

        var bitrateText = '--';
        if (this._hls && this._hls.levels && this._hls.levels.length > 0) {
            var currentLevel = this._hls.currentLevel;
            if (currentLevel === -1) {
                bitrateText = 'Auto';
            } else if (currentLevel >= 0 && currentLevel < this._hls.levels.length) {
                var level = this._hls.levels[currentLevel];
                bitrateText = level.bitrate ? (level.bitrate / 1000).toFixed(1) + ' kbps' : '--';
            }
        }
        var bitrateEl = this.debugPanelEl.querySelector('#cp-debug-bitrate');
        if (bitrateEl) bitrateEl.textContent = bitrateText;

        var buffered = video.buffered;
        var currentTime = video.currentTime;
        var bufferedEnd = 0;
        for (var i = 0; i < buffered.length; i++) {
            if (buffered.start(i) <= currentTime && currentTime <= buffered.end(i)) {
                bufferedEnd = buffered.end(i);
                break;
            }
        }
        var bufferPercent = video.duration ? (bufferedEnd / video.duration * 100) : 0;
        var bufferEl = this.debugPanelEl.querySelector('#cp-debug-buffer');
        if (bufferEl) bufferEl.textContent = bufferPercent.toFixed(1) + '% (' + bufferedEnd.toFixed(1) + 's)';

        var droppedText = '--';
        if (video.getVideoPlaybackQuality) {
            var stats = video.getVideoPlaybackQuality();
            droppedText = stats.droppedVideoFrames + ' / ' + stats.totalVideoFrames;
        }
        var droppedEl = this.debugPanelEl.querySelector('#cp-debug-dropped');
        if (droppedEl) droppedEl.textContent = droppedText;

        var timeEl = this.debugPanelEl.querySelector('#cp-debug-time');
        if (timeEl) timeEl.textContent = this._formatTime(video.currentTime) + ' / ' + this._formatTime(video.duration);
    };

    VideoPlayer.prototype._formatTime = function (t) {
        t = Math.floor(t || 0);
        var h = Math.floor(t / 3600);
        var m = Math.floor((t % 3600) / 60);
        var s = t % 60;
        return (h > 0 ? h + ':' : '') + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
    };

    // ---------- 移动端手势 ----------
    VideoPlayer.prototype._showGestureIndicator = function (type) {
        if (!this.gestureIndicatorEl) return;
        if (type === 'brightness') {
            this.gestureIndicatorIconEl.innerHTML = ICONS.brightness;
        } else if (type === 'volume') {
            this.gestureIndicatorIconEl.innerHTML = ICONS.volumeHigh;
        }
        this.gestureIndicatorEl.classList.add('show');
    };

    VideoPlayer.prototype._updateGestureIndicator = function (type, percent) {
        if (!this.gestureIndicatorEl) return;
        if (type === 'brightness') {
            this.gestureIndicatorIconEl.innerHTML = ICONS.brightness;
        } else if (type === 'volume') {
            this.gestureIndicatorIconEl.innerHTML = (percent <= 0) ? ICONS.volumeMuted : ICONS.volumeHigh;
        }
        this.gestureIndicatorValueEl.textContent = Math.round(percent) + '%';
    };

    VideoPlayer.prototype._hideGestureIndicators = function () {
        if (!this.gestureIndicatorEl) return;
        if (this._gestureIndicatorTimer) clearTimeout(this._gestureIndicatorTimer);
        this._gestureIndicatorTimer = setTimeout(function () {
            if (this.gestureIndicatorEl) this.gestureIndicatorEl.classList.remove('show');
        }.bind(this), 500);
    };

    VideoPlayer.prototype._onGestureStart = function (e) {
        if (this._locked) return;
        var touch = e.touches[0];
        this._gesture.startX = touch.clientX;
        this._gesture.startY = touch.clientY;
        this._gesture.isGesture = true;
        this._gesture.type = null;
        this._gesture.longPressTimer = setTimeout(this._onLongPressTimer, 500);
        if (this._gestureIndicatorTimer) clearTimeout(this._gestureIndicatorTimer);
        var currentFilter = this.video.style.filter;
        var brightness = 1;
        if (currentFilter) {
            var match = currentFilter.match(/brightness\(([^)]+)\)/);
            if (match) brightness = parseFloat(match[1]);
        }
        this._gesture.startBrightnessPercent = Math.min(100, Math.max(0, Math.round(brightness * 100)));
        this._gesture.startVolume = this.volume;
    };

    VideoPlayer.prototype._onGestureMove = function (e) {
        if (!this._gesture.isGesture) return;
        var touch = e.touches[0];
        var dx = touch.clientX - this._gesture.startX;
        var dy = touch.clientY - this._gesture.startY;
        if (Math.abs(dx) < 10 && Math.abs(dy) < 10) return;

        if (this._gesture.longPressTimer) {
            clearTimeout(this._gesture.longPressTimer);
            this._gesture.longPressTimer = null;
        }

        if (!this._gesture.type) {
            if (Math.abs(dx) > Math.abs(dy)) {
                this._gesture.type = 'seek';
            } else {
                var rect = this.video.getBoundingClientRect();
                if (touch.clientX < rect.left + rect.width / 2) {
                    this._gesture.type = 'brightness';
                } else {
                    this._gesture.type = 'volume';
                }
                this._showGestureIndicator(this._gesture.type);
            }
        }

        if (this._gesture.type === 'seek') {
            var seekStep = dx > 0 ? 10 : -10;
            this.seek(this.video.currentTime + seekStep);
            this._gesture.startX = touch.clientX;
            this._gesture.startY = touch.clientY;
        } else if (this._gesture.type === 'volume') {
            var totalDyVolume = touch.clientY - this._gesture.startY;
            var volDelta = -totalDyVolume / 200 * 0.8;
            var newVolume = Math.min(1, Math.max(0, this._gesture.startVolume + volDelta));
            this.setVolume(newVolume);
            this._updateGestureIndicator('volume', newVolume * 100);
        } else if (this._gesture.type === 'brightness') {
            var totalDyBrightness = touch.clientY - this._gesture.startY;
            var brightnessDelta = -totalDyBrightness / 200 * 100 * 0.8;
            var base = Math.min(100, Math.max(0, this._gesture.startBrightnessPercent));
            var newBrightnessPercent = Math.min(100, Math.max(0, base + brightnessDelta));
            var newBrightness = newBrightnessPercent / 100;
            var currentFilter = this.video.style.filter || '';
            var newFilter = currentFilter.replace(/brightness\([^)]*\)/g, '').trim();
            newFilter = (newFilter ? newFilter + ' ' : '') + 'brightness(' + newBrightness + ')';
            this.video.style.filter = newFilter;
            this._updateGestureIndicator('brightness', newBrightnessPercent);
        }
    };

    VideoPlayer.prototype._onGestureEnd = function () {
        if (this._gesture.longPressTimer) {
            clearTimeout(this._gesture.longPressTimer);
            this._gesture.longPressTimer = null;
        }
        this._gesture.isGesture = false;
        this._hideGestureIndicators();
    };

    VideoPlayer.prototype._onLongPressTimer = function () {
        this._gesture.tempRate = this.playbackRate;
        this.setPlaybackRate(3.0);
        this._gesture.isLongPress = true;
        var self = this;
        var onLongPressEnd = function () {
            self.setPlaybackRate(self._gesture.tempRate || 1);
            self._gesture.isLongPress = false;
            document.removeEventListener('touchend', onLongPressEnd);
        };
        document.addEventListener('touchend', onLongPressEnd);
    };

    // ---------- 唤醒锁 ----------
    VideoPlayer.prototype._requestWakeLock = async function () {
        if (!this._wakeLockSupported) return;
        try {
            this._wakeLock = await navigator.wakeLock.request('screen');
            this._wakeLock.addEventListener('release', function () { this._wakeLock = null; }.bind(this));
        } catch (err) {
            console.warn('[VideoPlayer] 唤醒锁定失败', err);
        }
    };

    VideoPlayer.prototype._releaseWakeLock = function () {
        if (this._wakeLock) {
            this._wakeLock.release().catch(function () {});
            this._wakeLock = null;
        }
    };

    VideoPlayer.prototype._onVisibilityChange = function () {
        if (document.hidden) {
            this._suspendAudioContext();
            if (this._wakeLock) {
                this._releaseWakeLock();
            }
        } else if (!document.hidden) {
            if (this.config.wakeLock && !this._wakeLock && !this.video.paused) {
                this._requestWakeLock();
            }
            if (!this.video.paused) {
                this._resumeAudioContext();
            }
        }
    };

    // ---------- 键盘快捷键 ----------
    VideoPlayer.prototype._onKeydown = function (e) {
        var tag = e.target.tagName;
        if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
        var shortcuts = this.config.keyboardShortcuts;
        if (shortcuts === false) return;
        var key = e.key.toLowerCase();
        var handled = false;
        if (shortcuts === true) {
            if (key === ' ' || e.key === 'Space') { e.preventDefault(); this.toggle(); handled = true; }
            else if (key === 'arrowright') { e.preventDefault(); this.seek(this.video.currentTime + 5); handled = true; }
            else if (key === 'arrowleft') { e.preventDefault(); this.seek(this.video.currentTime - 5); handled = true; }
            else if (key === 'f') { this.toggleFullscreen(); handled = true; }
            else if (key === 'm') { this.toggleMute(); handled = true; }
            else if (key === 'p') { this.togglePip(); handled = true; }
            else if (key === 'o') { this.toggleOrientation(); handled = true; }
        } else if (typeof shortcuts === 'object') {
            if (shortcuts.play && key === shortcuts.play) { e.preventDefault(); this.toggle(); handled = true; }
            else if (shortcuts.forward && key === shortcuts.forward) { e.preventDefault(); this.seek(this.video.currentTime + 5); handled = true; }
            else if (shortcuts.backward && key === shortcuts.backward) { e.preventDefault(); this.seek(this.video.currentTime - 5); handled = true; }
            else if (shortcuts.fullscreen && key === shortcuts.fullscreen) { this.toggleFullscreen(); handled = true; }
            else if (shortcuts.mute && key === shortcuts.mute) { this.toggleMute(); handled = true; }
            else if (shortcuts.pip && key === shortcuts.pip) { this.togglePip(); handled = true; }
        }
        if (handled) e.preventDefault();
    };

    // ---------- 控制栏显隐 ----------
    VideoPlayer.prototype.toggleControls = function(show) {
        if (!this.controlsEl || this._locked) return;
        var visible = (typeof show === 'boolean') ? show : !this._controlsVisible;
        this._controlsVisible = visible;
        this.controlsEl.classList.toggle('cp-controls-visible', visible);
        this.wrapper.classList.toggle('is-focused', visible);
    };

    // ---------- 设置面板 ----------
    VideoPlayer.prototype._toggleSettingsPanel = function(e) {
        e.stopPropagation();
        if (this._locked) return;
        var panel = this.settingsPanel;
        if (!panel) return;
        panel.classList.toggle('show');
        this.settingsBtn.classList.toggle('active');
        this._closeMenu();
        if (this._isDebugVisible) this._closeDebugPanel();
        if (panel.classList.contains('show')) {
            this._updateSettingsPanel();
        }
    };

    VideoPlayer.prototype._closeSettingsPanel = function() {
        if (this.settingsPanel) {
            this.settingsPanel.classList.remove('show');
            if (this.settingsBtn) this.settingsBtn.classList.remove('active');
        }
    };

    // ---------- 事件绑定 ----------
    VideoPlayer.prototype._bindEvents = function () {
        var video = this.video;
        var self = this;

        video.addEventListener('play', this._onPlay);
        video.addEventListener('pause', this._onPause);
        video.addEventListener('ended', this._onEnded);
        video.addEventListener('timeupdate', this._onTimeUpdate);
        video.addEventListener('loadedmetadata', this._onLoadedMetadata);
        video.addEventListener('waiting', this._onWaiting);
        video.addEventListener('canplay', this._onCanPlay);
        video.addEventListener('error', this._onError);
        video.addEventListener('volumechange', this._onVolumeChange);
        video.addEventListener('enterpictureinpicture', this._onPipChange);
        video.addEventListener('leavepictureinpicture', this._onPipChange);
        video.addEventListener('stalled', this._onStalled);
        video.addEventListener('playing', this._onPlaying);
        video.addEventListener('progress', this._onVideoProgress);
        video.addEventListener('dblclick', this.toggle);

        video.addEventListener('touchstart', this._onGestureStart, { passive: false });
        video.addEventListener('touchmove', this._onGestureMove, { passive: false });
        video.addEventListener('touchend', this._onGestureEnd);
        video.addEventListener('touchcancel', this._onGestureEnd);

        if (this.progressEl) {
            this.progressEl.addEventListener('mousedown', this._onProgressDown);
            this.progressEl.addEventListener('touchstart', this._onProgressDown, { passive: false });
        }
        document.addEventListener('mousemove', this._onProgressMove);
        document.addEventListener('mouseup', this._onProgressUp);
        document.addEventListener('touchmove', this._onProgressMove, { passive: false });
        document.addEventListener('touchend', this._onProgressUp);

        if (this.playBtn) this.playBtn.addEventListener('click', this.toggle);
        if (this.bigPlayBtn) this.bigPlayBtn.addEventListener('click', this.toggle);
        if (this.volumeBtn) this.volumeBtn.addEventListener('click', this.toggleMute);
        if (this.fullscreenBtn) this.fullscreenBtn.addEventListener('click', this.toggleFullscreen);
        if (this.settingsBtn) this.settingsBtn.addEventListener('click', this._toggleSettingsPanel);

        if (this.subtitleSelect) {
            this.subtitleSelect.addEventListener('change', function () {
                var val = self.subtitleSelect.value;
                var selectedTrack = null;
                self._trackElements.forEach(function (track) {
                    if (track.srclang === val) {
                        track.mode = 'showing';
                        selectedTrack = track;
                    } else {
                        track.mode = 'hidden';
                    }
                });
                self._currentSubtitleTrack = selectedTrack;
                self.emit('subtitleTrackChange', val);
                if (typeof self.config.onSubtitleTrackChange === 'function') {
                    self.config.onSubtitleTrackChange.call(self, val);
                }
                self._updateSettingsPanel();
            });
        }

        if (this.config.keyboardShortcuts !== false) {
            document.addEventListener('keydown', this._onKeydown);
        }

        document.addEventListener('fullscreenchange', this._onFullscreenChange);
        document.addEventListener('webkitfullscreenchange', this._onFullscreenChange);
        document.addEventListener('mozfullscreenchange', this._onFullscreenChange);
        document.addEventListener('MSFullscreenChange', this._onFullscreenChange);

        document.addEventListener('click', this._onOutsideClick);
        window.addEventListener('resize', this._onResize);
        document.addEventListener('visibilitychange', this._onVisibilityChange);

        video.addEventListener('ended', function () {
            if (self.config.autoNext && self._playlist.length > 0) {
                self.playNext();
            }
        });

        // ---- 控制栏交互 ----
        var container = this.wrapper;

        var showAndReset = function() {
            if (self._locked) return;
            self.toggleControls(true);
            clearTimeout(self._hideControlsTimer);
            var baseDelay = self.touch ? 3000 : 2000;
            var delay = self.isFullscreen ? 4000 : baseDelay;
            self._hideControlsTimer = setTimeout(function() {
                if (!self.video.paused && !self.video.seeking) {
                    self.toggleControls(false);
                }
            }, delay);
        };

        container.addEventListener('touchstart', showAndReset, { passive: true });
        container.addEventListener('touchmove', showAndReset, { passive: true });
        container.addEventListener('mousemove', showAndReset);
        container.addEventListener('mouseenter', showAndReset);
        container.addEventListener('mouseleave', function() {
            if (self.video.paused) {
                clearTimeout(self._hideControlsTimer);
                self.toggleControls(false);
            }
        });

        video.addEventListener('play', function() {
            self.toggleControls(true);
            clearTimeout(self._hideControlsTimer);
        });
        video.addEventListener('pause', function() {
            self.toggleControls(true);
            clearTimeout(self._hideControlsTimer);
        });

        if (this.config.controlsInitiallyVisible) {
            this.toggleControls(true);
            setTimeout(function() {
                if (!self.video.paused && !self.video.seeking) {
                    self.toggleControls(false);
                }
            }, 3000);
        }

        // ---- 音量滚轮微调 ----
        if (this.volumeBtn) {
            var volWrap = this.volumeBtn.parentNode;
            if (volWrap) {
                volWrap.addEventListener('wheel', function(e) {
                    e.preventDefault();
                    var delta = e.deltaY > 0 ? -0.05 : 0.05;
                    var newVol = Math.min(1, Math.max(0, self.volume + delta));
                    self.setVolume(newVol);
                }, { passive: false });
            }
        }

        // ---- 点击外部关闭设置面板 ----
        document.addEventListener('click', function(e) {
            if (self.settingsPanel && self.settingsBtn) {
                if (!self.settingsBtn.contains(e.target) && !self.settingsPanel.contains(e.target)) {
                    self._closeSettingsPanel();
                }
            }
        });
    };

    // ---------- 事件处理 ----------
    VideoPlayer.prototype._onPlay = function () {
        if (this.playBtn) {
            this.playBtn.innerHTML = ICONS.pause;
            this.playBtn.setAttribute('aria-pressed', 'true');
        }
        if (this.bigPlayBtn) this.bigPlayBtn.classList.remove('show');
        if (this.posterEl) this.posterEl.classList.add('hidden');
        if (typeof this.config.onPlay === 'function') this.config.onPlay.call(this);
        this.emit('play');
        if (this.config.wakeLock) this._requestWakeLock();
        this._resumeAudioContext();
    };

    VideoPlayer.prototype._onPause = function () {
        if (this.playBtn) {
            this.playBtn.innerHTML = ICONS.play;
            this.playBtn.setAttribute('aria-pressed', 'false');
        }
        if (!this.video.ended && this.bigPlayBtn) this.bigPlayBtn.classList.add('show');
        if (typeof this.config.onPause === 'function') this.config.onPause.call(this);
        this.emit('pause');
        this._releaseWakeLock();
        this._suspendAudioContext();
        this.toggleControls(true);
        clearTimeout(this._hideControlsTimer);
    };

    VideoPlayer.prototype._onEnded = function () {
        if (this.playBtn) {
            this.playBtn.innerHTML = ICONS.play;
            this.playBtn.setAttribute('aria-pressed', 'false');
        }
        if (this.bigPlayBtn) this.bigPlayBtn.classList.add('show');
        if (typeof this.config.onEnded === 'function') this.config.onEnded.call(this);
        this.emit('ended');
        this.saveProgress();
    };

    VideoPlayer.prototype._onTimeUpdate = function () {
        if (this.isDragging) return;
        var current = this.video.currentTime || 0;
        var duration = this.video.duration || 0;
        var percent = duration ? (current / duration * 100) : 0;
        if (this.progressFilled) this.progressFilled.style.width = percent + '%';
        if (this.progressEl) {
            this.progressEl.setAttribute('aria-valuenow', current);
            this.progressEl.setAttribute('aria-valuemax', duration);
        }
        this._updateTimeDisplay(current, duration);
        if (typeof this.config.onTimeUpdate === 'function') this.config.onTimeUpdate.call(this, current, duration);
        this.emit('timeupdate', { currentTime: current, duration: duration });

        if (this._abState === 'looping' && this._abA < this._abB) {
            if (current >= this._abB) this.seek(this._abA);
        }

        if (this._chapters.length > 0) {
            for (var i = this._chapters.length - 1; i >= 0; i--) {
                if (current >= this._chapters[i].time) {
                    if (this._lastChapterIndex !== i) {
                        this._lastChapterIndex = i;
                        this.emit('chapter', this._chapters[i]);
                        if (typeof this.config.onChapter === 'function') this.config.onChapter.call(this, this._chapters[i]);
                    }
                    break;
                }
            }
        }

        if (this._abState !== 'off') {
            this._updateABMarks();
        }
    };

    VideoPlayer.prototype._onLoadedMetadata = function () {
        var duration = this.video.duration || 0;
        if (this.progressEl) this.progressEl.setAttribute('aria-valuemax', duration);
        this._updateTimeDisplay(0, duration);
        if (this.config.autoplay) this.play().catch(function () {});
        if (this.config.enableProgressMemory) {
            var saved = parseFloat(localStorage.getItem(this._progressKey));
            if (!isNaN(saved)) this.video.currentTime = saved;
        }
        this._updateABMarks();
        this._updateSettingsPanel();
    };

    VideoPlayer.prototype._onWaiting = function () {
        if (this.wrapper) this.wrapper.classList.add('is-loading');
        this.emit('waiting');
    };

    VideoPlayer.prototype._onCanPlay = function () {
        if (this.wrapper) this.wrapper.classList.remove('is-loading');
        this._stallRetries = 0;
        this.emit('canplay');
        this._updateSettingsPanel();
    };

    VideoPlayer.prototype._onError = function (e) {
        if (this.wrapper) this.wrapper.classList.remove('is-loading');
        if (typeof this.config.onError === 'function') this.config.onError.call(this, e);
        this.emit('error', e);
    };

    VideoPlayer.prototype._onVolumeChange = function () {
        var muted = this.video.muted;
        var vol = this.video.volume;
        this.isMuted = muted;
        this.volume = muted ? 0 : vol;
        if (this.volumeBtn) {
            this.volumeBtn.innerHTML = muted ? ICONS.volumeMuted : ICONS.volumeHigh;
            this.volumeBtn.setAttribute('aria-pressed', muted ? 'true' : 'false');
        }
        this._savePreferencesIfNeeded();
    };

    VideoPlayer.prototype._onPipChange = function () {
        var isPip = !!document.pictureInPictureElement;
        this.isPip = isPip;
        this.emit('pipChange', isPip);
    };

    VideoPlayer.prototype._onStalled = function () {
        if (this._stallRetries >= this._maxStallRetries) {
            if (typeof this.config.onError === 'function') this.config.onError({ message: '视频加载停滞次数过多,放弃重试' });
            return;
        }
        this._stallRetries++;
        console.warn('[VideoPlayer] 检测到 stalled,执行第 ' + this._stallRetries + ' 次重试...');
        var wasPaused = this.video.paused;
        this.video.load();
        if (!wasPaused) this.video.play().catch(function (err) { console.warn(err); });
    };

    VideoPlayer.prototype._onPlaying = function () {
        this._stallRetries = 0;
        this.emit('playing');
    };

    VideoPlayer.prototype._onVideoProgress = function () {
        this.emit('loadProgress');
        if (typeof this.config.onLoadProgress === 'function') this.config.onLoadProgress.call(this);
    };

    VideoPlayer.prototype._onFullscreenChange = function () {
        var isFull = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
        this.isFullscreen = isFull;
        if (this.wrapper) {
            if (isFull) this.wrapper.classList.add('is-fullscreen');
            else this.wrapper.classList.remove('is-fullscreen');
        }
        if (this.fullscreenBtn) {
            this.fullscreenBtn.innerHTML = isFull ? ICONS.fullscreenExit : ICONS.fullscreen;
            this.fullscreenBtn.setAttribute('aria-pressed', isFull ? 'true' : 'false');
        }
        this.emit('fullscreenChange', isFull);
    };

    VideoPlayer.prototype._onResize = function () {};

    // ---------- 其他交互 ----------
    VideoPlayer.prototype._toggleMenu = function () {
        if (this.menuDropdown) {
            this.menuDropdown.classList.toggle('show');
            if (this._isDebugVisible) this._closeDebugPanel();
            this._closeSettingsPanel();
        }
    };
    VideoPlayer.prototype._closeMenu = function () {
        if (this.menuDropdown) this.menuDropdown.classList.remove('show');
    };
    VideoPlayer.prototype._onOutsideClick = function (e) {
        if (this.menuDropdown && this.menuBtn) {
            if (!this.menuBtn.contains(e.target) && !this.menuDropdown.contains(e.target)) this._closeMenu();
        }
        if (this._isDebugVisible && !this.debugPanelEl.contains(e.target)) {
            this._closeDebugPanel();
        }
    };

    VideoPlayer.prototype._toggleABLoop = function () {
        if (!this.abLoopBtn) return;
        var video = this.video;
        var current = video.currentTime || 0;
        var duration = video.duration || 0;
        if (duration === 0) return;

        this.abLoopBtn.classList.remove('set-a', 'set-b', 'looping', 'active');

        switch (this._abState) {
            case 'off':
                this._abA = current;
                this._abState = 'setA';
                this.abLoopBtn.classList.add('set-a');
                this.abLoopBtn.title = '标记 B 点';
                break;
            case 'setA':
                if (current <= this._abA) {
                    console.warn('[VideoPlayer] B点必须大于A点');
                    this._abA = current;
                    this.abLoopBtn.classList.add('set-a');
                    this.abLoopBtn.title = '标记 B 点';
                    break;
                }
                this._abB = current;
                this._abState = 'looping';
                this.abLoopBtn.classList.add('looping', 'active');
                this.abLoopBtn.title = '取消循环';
                this.seek(this._abA);
                break;
            case 'looping':
                this._abState = 'off';
                this._abA = 0;
                this._abB = 0;
                this.abLoopBtn.title = 'A-B循环';
                break;
            default:
                this._abState = 'off';
                this.abLoopBtn.title = 'A-B循环';
        }
        this._updateABMarks();
    };

    VideoPlayer.prototype._toggleLock = function () {
        if (!this.wrapper || !this.lockBtn) return;
        this._locked = !this._locked;
        if (this._locked) {
            this.wrapper.classList.add('is-locked');
            this.lockBtn.innerHTML = ICONS.lock;
            this.lockBtn.setAttribute('aria-label', '解锁控件');
            this._closeMenu();
            this._closeSettingsPanel();
            if (this._isDebugVisible) this._closeDebugPanel();
            this.toggleControls(false);
        } else {
            this.wrapper.classList.remove('is-locked');
            this.lockBtn.innerHTML = ICONS.unlock;
            this.lockBtn.setAttribute('aria-label', '锁定控件');
        }
    };

    VideoPlayer.prototype._getClientX = function (e) {
        if (typeof e.clientX === 'number') return e.clientX;
        if (e.touches && e.touches[0] && typeof e.touches[0].clientX === 'number') return e.touches[0].clientX;
        if (e.changedTouches && e.changedTouches[0] && typeof e.changedTouches[0].clientX === 'number') return e.changedTouches[0].clientX;
        return null;
    };

    VideoPlayer.prototype._onProgressDown = function (e) {
        e.preventDefault();
        this.isDragging = true;
        if (this.wrapper) this.wrapper.classList.add('is-focused');
        this._updateProgressFromEvent(e);
    };
    VideoPlayer.prototype._onProgressMove = function (e) {
        if (!this.isDragging) return;
        e.preventDefault();
        this._updateProgressFromEvent(e);
    };
    VideoPlayer.prototype._onProgressUp = function (e) {
        if (!this.isDragging) return;
        this.isDragging = false;
        if (this.wrapper) this.wrapper.classList.remove('is-focused');
        this._updateProgressFromEvent(e, true);
    };
    VideoPlayer.prototype._updateProgressFromEvent = function (e, seek) {
        if (!this.progressEl) return;
        var clientX = this._getClientX(e);
        if (clientX === null) return;
        var rect = this.progressEl.getBoundingClientRect();
        var x = clientX - rect.left;
        var percent = Math.min(1, Math.max(0, x / rect.width));
        var duration = this.video.duration || 0;
        var time = percent * duration;
        if (duration && isFinite(time)) {
            if (this.progressFilled) this.progressFilled.style.width = (percent * 100) + '%';
            this.progressEl.setAttribute('aria-valuenow', time);
            this._updateTimeDisplay(time, duration);
            if (seek) this.seek(time);
        }
    };

    VideoPlayer.prototype._updateABMarks = function () {
        if (!this.progressEl || !this.abMarkA || !this.abMarkB) return;
        var duration = this.video.duration || 0;
        if (duration === 0) {
            this.abMarkA.style.display = 'none';
            this.abMarkB.style.display = 'none';
            return;
        }

        var showA = (this._abState === 'setA' || this._abState === 'looping');
        var showB = (this._abState === 'looping');

        if (showA && typeof this._abA === 'number' && this._abA >= 0) {
            this.abMarkA.style.display = 'block';
            this.abMarkA.style.left = Math.min(100, Math.max(0, (this._abA / duration) * 100)) + '%';
        } else {
            this.abMarkA.style.display = 'none';
        }

        if (showB && typeof this._abB === 'number' && this._abB > this._abA) {
            this.abMarkB.style.display = 'block';
            this.abMarkB.style.left = Math.min(100, Math.max(0, (this._abB / duration) * 100)) + '%';
        } else {
            this.abMarkB.style.display = 'none';
        }
    };

    // ---------- 公共 API ----------
    VideoPlayer.prototype.play = function () {
        this._ensureAudioContext();
        return this.video.play();
    };
    
    VideoPlayer.prototype.pause = function () { this.video.pause(); };
    
    VideoPlayer.prototype.toggle = function () {
        if (this.video.paused) this.play();
        else this.pause();
    };
    
    VideoPlayer.prototype.seek = function (time) {
        if (!isFinite(time)) return;
        var duration = this.video.duration || 0;
        var target = Math.min(duration, Math.max(0, time));
        this.video.currentTime = target;
        var percent = duration ? (target / duration * 100) : 0;
        if (this.progressFilled) this.progressFilled.style.width = percent + '%';
        if (this.progressEl) this.progressEl.setAttribute('aria-valuenow', target);
        this._updateTimeDisplay(target, duration);
        this.saveProgress();
    };
    
    VideoPlayer.prototype.setVolume = function (vol) {
        if (!isFinite(vol)) return;
        vol = Math.min(1, Math.max(0, vol));
        this.video.volume = vol;
        this.video.muted = (vol === 0);
        this.isMuted = (vol === 0);
        this.volume = vol;
        if (this.volumeBtn) {
            this.volumeBtn.innerHTML = (vol === 0) ? ICONS.volumeMuted : ICONS.volumeHigh;
            this.volumeBtn.setAttribute('aria-pressed', (vol === 0) ? 'true' : 'false');
        }
        this._savePreferencesIfNeeded();
    };
    
    VideoPlayer.prototype.toggleMute = function () {
        if (this.video.muted) {
            this.video.muted = false;
            this.video.volume = this.volume || 0.5;
        } else {
            this.video.muted = true;
        }
    };
    
    VideoPlayer.prototype.setPlaybackRate = function (rate) {
        this.video.playbackRate = rate;
        this.playbackRate = rate;
        this._updateSettingsPanel();
        this._savePreferencesIfNeeded();
        this.emit('playbackRateChange', rate);
    };
    
    VideoPlayer.prototype.toggleFullscreen = function () {
        var el = this.wrapper;
        if (!el) return;
        if (!this.isFullscreen) {
            if (el.requestFullscreen) el.requestFullscreen();
            else if (el.webkitRequestFullscreen) el.webkitRequestFullscreen();
            else if (el.mozRequestFullScreen) el.mozRequestFullScreen();
            else if (el.msRequestFullscreen) el.msRequestFullscreen();
        } else {
            if (document.exitFullscreen) document.exitFullscreen();
            else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
            else if (document.mozCancelFullScreen) document.mozCancelFullScreen();
            else if (document.msExitFullscreen) document.msExitFullscreen();
        }
    };
    
    VideoPlayer.prototype.togglePip = function () {
        var video = this.video;
        if (document.pictureInPictureElement === video) {
            document.exitPictureInPicture().catch(function () {});
        } else if (document.pictureInPictureEnabled) {
            video.requestPictureInPicture().catch(function (err) {
                console.warn('[VideoPlayer] PiP 不支持:', err);
            });
        }
    };
    
    VideoPlayer.prototype.load = function (sources) {
        if (!sources || !sources.length) return;
        this.video.pause();
        if (this._hls) {
            this._hls.destroy();
            this._hls = null;
        }
        this.config.sources = sources;

        var self = this;
        this._initHLS(function() {
            self._loadSource(sources);
            if (!self.video.paused) self.play().catch(function () {});
        });
    };
    
    VideoPlayer.prototype.setOrientation = function (mode) {
        if (['auto', 'landscape', 'portrait'].indexOf(mode) === -1) return;
        this._orientation = mode;
        if (this.orientationBtn) {
            if (mode === 'auto') {
                this.orientationBtn.innerHTML = ICONS.orientationAuto;
                this.orientationBtn.setAttribute('aria-label', '自动方位');
            } else if (mode === 'landscape') {
                this.orientationBtn.innerHTML = ICONS.orientationLandscape;
                this.orientationBtn.setAttribute('aria-label', '横屏方位');
            } else if (mode === 'portrait') {
                this.orientationBtn.innerHTML = ICONS.orientationPortrait;
                this.orientationBtn.setAttribute('aria-label', '竖屏方位');
            }
        }
        if (this.wrapper) {
            this.wrapper.classList.remove('cp-orientation-auto', 'cp-orientation-landscape', 'cp-orientation-portrait');
            this.wrapper.classList.add('cp-orientation-' + mode);
        }
        if (typeof this.config.onOrientationChange === 'function') this.config.onOrientationChange.call(this, mode);
        this.emit('orientationChange', mode);
    };
    
    VideoPlayer.prototype.toggleOrientation = function () {
        var modes = ['auto', 'landscape', 'portrait'];
        var idx = modes.indexOf(this._orientation);
        idx = (idx + 1) % modes.length;
        this.setOrientation(modes[idx]);
    };
    
    VideoPlayer.prototype.requestCast = function () {
        if (navigator.presentation && navigator.presentation.requestSession) {
            navigator.presentation.requestSession().catch(function (err) {
                console.warn('[VideoPlayer] 投屏请求失败:', err);
                if (typeof this.config.onError === 'function') this.config.onError({ message: '投屏请求失败: ' + err.message });
            }.bind(this));
        } else if (window.chrome && window.chrome.cast && window.chrome.cast.isAvailable) {
            console.warn('[VideoPlayer] 请使用 Cast API 集成');
            if (typeof this.config.onError === 'function') this.config.onError({ message: '请使用 Cast API 集成' });
        } else {
            console.warn('[VideoPlayer] 当前浏览器不支持投屏');
            if (typeof this.config.onError === 'function') this.config.onError({ message: '当前浏览器不支持投屏' });
        }
    };

    VideoPlayer.prototype.setTracks = function (tracks) {
        this._tracks = tracks || [];
        this._clearSubtitles();
        this._initSubtitles();
        this._updateSettingsPanel();
    };

    // ---------- 销毁 ----------
    VideoPlayer.prototype.destroy = function () {
        if (this._destroyed) return;
        this._destroyed = true;

        this.video.pause();
        if (this._hls) {
            this._hls.destroy();
            this._hls = null;
        }
        if (this._audioContext) {
            this._audioContext.close();
            this._audioContext = null;
        }
        this._releaseWakeLock();
        clearInterval(this._bufferInterval);

        this._blobUrls.forEach(function (url) {
            URL.revokeObjectURL(url);
        });
        this._blobUrls = [];

        var video = this.video;
        video.removeEventListener('play', this._onPlay);
        video.removeEventListener('pause', this._onPause);
        video.removeEventListener('ended', this._onEnded);
        video.removeEventListener('timeupdate', this._onTimeUpdate);
        video.removeEventListener('loadedmetadata', this._onLoadedMetadata);
        video.removeEventListener('waiting', this._onWaiting);
        video.removeEventListener('canplay', this._onCanPlay);
        video.removeEventListener('error', this._onError);
        video.removeEventListener('volumechange', this._onVolumeChange);
        video.removeEventListener('enterpictureinpicture', this._onPipChange);
        video.removeEventListener('leavepictureinpicture', this._onPipChange);
        video.removeEventListener('stalled', this._onStalled);
        video.removeEventListener('playing', this._onPlaying);
        video.removeEventListener('progress', this._onVideoProgress);
        video.removeEventListener('dblclick', this.toggle);
        video.removeEventListener('touchstart', this._onGestureStart);
        video.removeEventListener('touchmove', this._onGestureMove);
        video.removeEventListener('touchend', this._onGestureEnd);
        video.removeEventListener('touchcancel', this._onGestureEnd);

        document.removeEventListener('mousemove', this._onProgressMove);
        document.removeEventListener('mouseup', this._onProgressUp);
        document.removeEventListener('touchmove', this._onProgressMove);
        document.removeEventListener('touchend', this._onProgressUp);
        document.removeEventListener('fullscreenchange', this._onFullscreenChange);
        document.removeEventListener('webkitfullscreenchange', this._onFullscreenChange);
        document.removeEventListener('mozfullscreenchange', this._onFullscreenChange);
        document.removeEventListener('MSFullscreenChange', this._onFullscreenChange);
        document.removeEventListener('keydown', this._onKeydown);
        document.removeEventListener('click', this._onOutsideClick);
        document.removeEventListener('visibilitychange', this._onVisibilityChange);
        window.removeEventListener('resize', this._onResize);

        if (this.wrapper && this.wrapper.parentNode) {
            var parent = this.wrapper.parentNode;
            parent.insertBefore(this.video, this.wrapper);
            this.wrapper.remove();
        }

        if (this._originalControlsAttr !== null) {
            this.video.setAttribute('controls', this._originalControlsAttr);
        } else {
            this.video.removeAttribute('controls');
        }

        if (this._originalVideoStyle !== undefined) {
            this.video.setAttribute('style', this._originalVideoStyle);
        } else {
            this.video.removeAttribute('style');
        }

        this.wrapper = null;
        this.controlsEl = null;
        this.progressEl = null;
        this.progressFilled = null;
        this.progressBuffered = null;
        this.playBtn = null;
        this.timeDisplay = null;
        this.volumeBtn = null;
        this.subtitleSelect = null;
        this.fullscreenBtn = null;
        this.abLoopBtn = null;
        this.menuBtn = null;
        this.menuDropdown = null;
        this.lockBtn = null;
        this.posterEl = null;
        this.bigPlayBtn = null;
        this.loadingEl = null;
        this.sideControlsEl = null;
        this.orientationBtn = null;
        this.debugPanelEl = null;
        this.debugCloseBtn = null;
        this.gestureIndicatorEl = null;
        this.gestureIndicatorIconEl = null;
        this.gestureIndicatorValueEl = null;
        this.settingsBtn = null;
        this.settingsPanel = null;
        this.abMarkA = null;
        this.abMarkB = null;
        this._sourceCleanup = null;
        this.video = null;

        var idx = VideoPlayer._instances.indexOf(this);
        if (idx > -1) VideoPlayer._instances.splice(idx, 1);
    };

    VideoPlayer.prototype._updateTimeDisplay = function (current, duration) {
        var fmt = function (t) {
            t = Math.floor(t || 0);
            var h = Math.floor(t / 3600);
            var m = Math.floor((t % 3600) / 60);
            var s = t % 60;
            return (h > 0 ? h + ':' : '') + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
        };
        if (this.timeDisplay) {
            this.timeDisplay.textContent = fmt(current) + ' / ' + fmt(duration);
        }
    };

    global.VideoPlayer = VideoPlayer;
})(typeof window !== 'undefined' ? window : this);