Ultimate Video Optimizer

高效视频流媒体性能优化脚本

当前为 2025-06-09 提交的版本,查看 最新版本

// ==UserScript==
// @name         Ultimate Video Optimizer
// @namespace    https://gf.qytechs.cn/zh-CN/users/1474228-moyu001
// @version      1.0
// @description  高效视频流媒体性能优化脚本
// @author       moyu001
// @match        *://*/*
// @grant        unsafeWindow
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // 配置管理
    const ConfigManager = {
        defaults: {
            enableBufferOptimization: true,  // 是否启用缓冲优化
            maxBufferSize: 15,  // 最大缓冲时长(秒)
            minBufferSize: 2,  // 最小缓冲阈值(秒)
            hlsJsUrl: null,  // 用户自定义 Hls.js 路径(留空则使用默认 CDN)
            hlsJsVersion: '1.4.3',  // 用户自定义 Hls.js 版本(默认1.4.3,格式:主版本.次版本.修订版)
            logLevel: 'warn',  // 日志级别(error/warn/info/debug)
            scanInterval: 3000,  // 视频扫描间隔(毫秒)
            maxScanAttempts: 15,  // 最大扫描次数(避免无限扫描)
            suppressStallWarningsAfter: 3,  // 卡顿警告抑制阈值(超过后转为 debug 日志)
            bufferRatios: { hls: 1.3, mp4: 0.8, dash: 1.0, webm: 1.0 }, // 各类型视频的缓冲比例
            enableDebugLog: false, // 是否启用调试/优化日志(关闭可减少资源占用)
        },

        getConfig() {
            let storedConfig = {};
            try {
                if (typeof GM_getValue === 'function') {
                    storedConfig = GM_getValue('videoOptimizerConfig') || {};
                }
            } catch (e) {
                this.log('warn', 'GM_getValue 不可用,使用默认配置');
            }
            const allowedKeys = ['enableBufferOptimization', 'maxBufferSize', 'minBufferSize', 'hlsJsUrl', 'hlsJsVersion', 'logLevel', 'scanInterval', 'maxScanAttempts', 'suppressStallWarningsAfter', 'bufferRatios'];

            const safeStoredConfig = Object.fromEntries(
                Object.entries(storedConfig)
                    .filter(([key]) => allowedKeys.includes(key))
                    .filter(([key, value]) => {
                        if (['maxBufferSize', 'minBufferSize', 'scanInterval', 'maxScanAttempts', 'suppressStallWarningsAfter'].includes(key)) {
                            const numValue = typeof value === 'string' ? parseFloat(value) : value;
                            return typeof numValue === 'number' && !isNaN(numValue);
                        }
                        return true;
                    })
                    .map(([key, value]) => {
                        if (['maxBufferSize', 'minBufferSize', 'scanInterval', 'maxScanAttempts', 'suppressStallWarningsAfter'].includes(key) && typeof value === 'string') {
                            return [key, parseFloat(value)];
                        }
                        return [key, value];
                    })
            );
            const mergedConfig = { ...this.defaults, ...safeStoredConfig };

            const allowedLevels = ['error', 'warn', 'info', 'debug'];
            if (!allowedLevels.includes(mergedConfig.logLevel)) {
                ConfigManager.log('error', `无效的日志级别: ${mergedConfig.logLevel}(使用默认值 'info')`);
                mergedConfig.logLevel = 'info';
            }

            return mergedConfig;
        },

        get(key) {
            return this.getConfig()[key];
        },

        log(level, message) {
            const allowedLevels = ['error', 'warn', 'info', 'debug'];
            const configLevel = this.get('logLevel');
            const minLevelIndex = allowedLevels.indexOf(configLevel);
            const currentLevelIndex = allowedLevels.indexOf(level);
            const enableDebugLog = this.get('enableDebugLog');

            // enableDebugLog 为 true 时,所有 info/debug 日志都输出;warn/error 始终输出
            if (
                level === 'error' || level === 'warn' ||
                (enableDebugLog && (level === 'info' || level === 'debug')) ||
                (!enableDebugLog && currentLevelIndex <= minLevelIndex)
            ) {
                const prefix = `[VideoOptimizer:${level.toUpperCase()}]`;
                if (console[level]) {
                    console[level](`${prefix} ${message}`);
                } else {
                    console.log(`${prefix} ${message}`);
                }
            }
        },

        setConfig(newConfig) {
            try {
                if (typeof GM_setValue === 'function') {
                    GM_setValue('videoOptimizerConfig', newConfig);
                }
            } catch (e) {
                this.log('warn', 'GM_setValue 不可用,配置未保存');
            }
        },
    };

    // 智能视频检测器
    class VideoDetector {
        constructor() {
            this.videoElements = new WeakMap();
            this.scanAttempts = 0;
            this.maxScanAttempts = Math.max(ConfigManager.get('maxScanAttempts'), 1);
            this.scanInterval = ConfigManager.get('scanInterval');

            ConfigManager.log('info', 'Starting video detection');
            this.startMonitoring();
        }

        startMonitoring() {
            // 调整首次扫描时机:仅当 DOM 已加载时立即执行,否则等待 DOMContentLoaded
            if (document.readyState === 'interactive' || document.readyState === 'complete') {
                this.scanExistingVideos();
            } else {
                document.addEventListener('DOMContentLoaded', () => this.scanExistingVideos(), { once: true });
            }

            // 设置定期扫描
            this.scanIntervalId = setInterval(() => {
                this.scanExistingVideos();

                // 达到最大扫描次数后停止
                if (this.scanAttempts >= this.maxScanAttempts) {
                    ConfigManager.log('info', `Reached max scan attempts (${this.maxScanAttempts})`);
                    this.stopScanning();
                }
            }, this.scanInterval);

            // 设置DOM变化监听
            this.setupMutationObserver();
        }

        scanExistingVideos() {
            this.scanAttempts++;

            // 获取所有视频元素
            const videos = document.querySelectorAll('video');
            ConfigManager.log('debug', `Found ${videos.length} video elements on scan attempt ${this.scanAttempts}`);

            // 处理每个视频
            videos.forEach(video => {
                // 新增:检查视频源是否变更(通过 currentSrc 对比)
                const lastProcessedSrc = video.dataset.uvoLastSrc;
                const currentSrc = video.currentSrc || video.src;
                if (lastProcessedSrc && lastProcessedSrc !== currentSrc) {
                    // 源变更时清除标记,允许重新处理
                    video.removeAttribute('data-uvo-processed');
                    ConfigManager.log('debug', `Video source changed (${lastProcessedSrc} → ${currentSrc}), resetting processed flag`);
                }
                video.dataset.uvoLastSrc = currentSrc; // 记录当前源

                // 跳过已处理的视频(双重检查)
                if (this.videoElements.has(video) || video.hasAttribute('data-uvo-processed')) {
                    return;
                }

                // 检查有效性
                const validation = this.validateVideoElement(video);
                if (validation.valid) {
                    this.processVideoElement(video, validation.type);
                    video.setAttribute('data-uvo-processed', 'true'); // 标记为已处理
                } else {
                    ConfigManager.log('debug', `Skipping invalid video: ${validation.reason}`);
                }
            });
        }

        validateVideoElement(video) {
            // 基本类型检查
            if (!(video instanceof HTMLVideoElement)) {
                return { valid: false, reason: 'Not a video element' };
            }
            // 可见性检查
            if (document.readyState === 'interactive' || document.readyState === 'complete') {
                const computedStyle = getComputedStyle(video);
                let parent = video.parentElement;
                let isParentVisible = true;
                while (parent) {
                    const parentStyle = getComputedStyle(parent);
                    if (
                        parentStyle.display === 'none' ||
                        parentStyle.visibility === 'hidden' ||
                        parseFloat(parentStyle.opacity) <= 0
                    ) {
                        isParentVisible = false;
                        break;
                    }
                    parent = parent.parentElement;
                }
                const isVisible = (
                    computedStyle.display !== 'none' &&
                    computedStyle.visibility !== 'hidden' &&
                    parseFloat(computedStyle.opacity) > 0 &&
                    !video.hidden &&
                    isParentVisible &&
                    video.getBoundingClientRect().width > 0 &&
                    video.getBoundingClientRect().height > 0
                );
                if (!isVisible) {
                    return { valid: false, reason: 'Element or its parent is not visible' };
                }
            }
            // 尺寸检查
            const hasMinimumSize = (
                (video.offsetWidth > 50 && video.offsetHeight > 30) ||
                (video.clientWidth > 50 && video.clientHeight > 30)
            );
            if (!hasMinimumSize) {
                return { valid: false, reason: 'Element too small (width>50px & height>30px required)' };
            }
            // 内容检查
            const hasContent = (
                video.src ||
                video.currentSrc ||
                video.querySelector('source[src]') ||
                video.querySelector('source[type]')
            );
            if (!hasContent) {
                return { valid: false, reason: 'No video source' };
            }
            // 识别媒体类型
            const type = this.identifyMediaType(video);
            if (!type) {
                return { valid: false, reason: 'Unsupported media type' };
            }
            return { valid: true, type };
        }

        processVideoElement(video, type) {
            ConfigManager.log('info', `Processing ${type.toUpperCase()} video element`);

            try {
                // 创建优化器
                const optimizer = new VideoOptimizer(video, type);
                this.videoElements.set(video, optimizer);

                // 添加销毁监听
                this.setupCleanupListener(video);

                // 优化:标记具体优化类型(如 data-uvo-active="hls")
                video.setAttribute('data-uvo-active', type);

                return true;
            } catch (e) {
                ConfigManager.log('error', `Failed to process video: ${e.message}`);
                return false;
            }
        }

        setupCleanupListener(video) {
            // 初始化观察者数组(修正:移除错误的覆盖操作)
            video._cleanupObservers = [];

            // 原有父节点观察者
            const parentObserver = new MutationObserver(() => {
                if (!document.contains(video)) {
                    ConfigManager.log('info', 'Video element removed from DOM (via parent mutation)');
                    this.cleanupVideo(video);
                    parentObserver.disconnect(); // 触发时断开自身
                }
            });
            if (video.parentNode) {
                parentObserver.observe(video.parentNode, { childList: true });
            }
            video._cleanupObservers.push(parentObserver); // 正确添加

            // 新增自身观察者
            const selfObserver = new MutationObserver(() => {
                if (!document.contains(video)) {
                    ConfigManager.log('info', 'Video element removed from DOM (via self mutation)');
                    this.cleanupVideo(video);
                    selfObserver.disconnect(); // 触发时断开自身
                }
            });
            selfObserver.observe(video, { attributes: false, childList: false, subtree: false });
            video._cleanupObservers.push(selfObserver); // 正确添加

            // 新增:监听视频元素src变更和子source元素变化
            const updateObserver = new MutationObserver(() => {
                const oldType = video.dataset.uvoActive;
                const newValidation = this.validateVideoElement(video);

                // 类型变更或片源更新时重新初始化
                if (newValidation.valid && newValidation.type !== oldType) {
                    ConfigManager.log('info', `Video source updated (${oldType} → ${newValidation.type})`);
                    this.cleanupVideo(video);
                    this.processVideoElement(video, newValidation.type);

                    // 新增:触发后断开并移除自身引用
                    updateObserver.disconnect();
                    const index = video._cleanupObservers.indexOf(updateObserver);
                    if (index > -1) {
                        video._cleanupObservers.splice(index, 1);
                    }
                }
            });

            updateObserver.observe(video, {
                attributes: true,
                attributeFilter: ['src', 'currentSrc'], // 监听src属性变更
                childList: true, // 监听子source元素增删
                subtree: true
            });

            // 存储观察者引用,用于清理
            video._cleanupObservers.push(updateObserver); // 正确添加
        }

        cleanupVideo(video) {
            const optimizer = this.videoElements.get(video);
            if (optimizer) {
                optimizer.cleanup();
                this.videoElements.delete(video);
            }

            // 新增:空值检查(避免 undefined 调用 forEach)
            if (video._cleanupObservers && Array.isArray(video._cleanupObservers)) {
                video._cleanupObservers.forEach(observer => observer.disconnect());
                delete video._cleanupObservers;
            }
        }

        /**
         * 识别视频媒体类型(检测优先级从高到低):
         * 1. 视频元素自身的 type 属性(如 video.type)
         * 2. 视频源 URL 的扩展名(如 .m3u8、.mpd)
         * 3. 子 source 元素的 type 属性或 src 扩展名
         * 4. 视频元素的 data-type 属性(如 data-type="hls")
         * 5. 默认返回 mp4(兼容未识别的视频)
         * @param {HTMLVideoElement} video 目标视频元素
         * @returns {'hls'|'mp4'|'dash'|'webm'|null} 媒体类型
         */
        identifyMediaType(video) {
            // 修正:正则表达式匹配主文件名(排除查询参数和哈希)
            const hlsRegex = /\.m3u8(\?.*)?$/i;
            const dashRegex = /\.mpd(\?.*)?$/i;
            const mp4Regex = /\.mp4(\?.*)?$/i;
            const m4sRegex = /\.m4s(\?.*)?$/i;
            const webmRegex = /\.webm(\?.*)?$/i;
            // 1. 检查type属性(修正:用 getAttribute 获取)
            const typeAttr = video.getAttribute('type');
            if (typeAttr === 'application/vnd.apple.mpegurl') return 'hls';
            if (typeAttr === 'video/mp4') return 'mp4';
            if (typeAttr === 'application/dash+xml') return 'dash';
            if (typeAttr === 'video/webm') return 'webm';
            // 2. 检查src/扩展名
            const src = video.currentSrc || video.src;
            if (src) {
                if (hlsRegex.test(src)) return 'hls';
                if (dashRegex.test(src)) return 'dash';
                if (mp4Regex.test(src)) return 'mp4';
                if (m4sRegex.test(src)) return 'mp4';
                if (webmRegex.test(src)) return 'webm';
            }
            // 3. 检查source元素
            const sources = video.querySelectorAll('source');
            for (const source of sources) {
                const sourceType = source.getAttribute('type');
                if (sourceType === 'application/vnd.apple.mpegurl') return 'hls';
                if (sourceType === 'video/mp4') return 'mp4';
                if (sourceType === 'application/dash+xml') return 'dash';
                if (sourceType === 'video/webm') return 'webm';
                if (source.src) {
                    if (hlsRegex.test(source.src)) return 'hls';
                    if (dashRegex.test(source.src)) return 'dash';
                    if (mp4Regex.test(source.src)) return 'mp4';
                    if (m4sRegex.test(source.src)) return 'mp4';
                    if (webmRegex.test(source.src)) return 'webm';
                }
            }
            // 4. 检查data属性
            if (video.dataset.type === 'hls') return 'hls';
            if (video.dataset.type === 'dash') return 'dash';
            if (video.dataset.type === 'mp4') return 'mp4';
            // 5. 未识别类型返回 null
            return null;
        }

        setupMutationObserver() {
            this.mutationTimeout = null;
            this.mutationDebounce = ConfigManager.get('mutationDebounce') || 200;
            this.lastScanTime = 0; // 新增:记录上次扫描时间
            this.minScanInterval = 1000; // 新增:最小扫描间隔(1秒)
            this.mutationObserver = new MutationObserver(mutations => {
                const now = Date.now();
                // 新增:控制扫描频率,避免短时间内重复扫描
                if (now - this.lastScanTime < this.minScanInterval) {
                    ConfigManager.log('debug', '跳过高频 DOM 变化扫描(间隔不足)');
                    return;
                }
                this.lastScanTime = now;

                // 立即执行一次扫描(处理紧急变更)
                this.scanExistingVideos();
                // 清除上一次未执行的防抖任务
                clearTimeout(this.mutationTimeout);
                // 延迟 200ms 处理 DOM 变化,合并短时间内的多次突变
                this.mutationTimeout = setTimeout(() => {
                    mutations.forEach(mutation => {
                        for (const node of mutation.addedNodes) {
                            // 检查添加的节点是否是视频
                            if (node.nodeName === 'VIDEO') {
                                const validation = this.validateVideoElement(node);
                                if (validation.valid) {
                                    this.processVideoElement(node, validation.type);
                                }
                            }
                            // 检查添加的节点内是否包含视频
                            else if (node.querySelectorAll) {
                                const videos = node.querySelectorAll('video');
                                videos.forEach(video => {
                                    const validation = this.validateVideoElement(video);
                                    if (validation.valid) {
                                        this.processVideoElement(video, validation.type);
                                    }
                                });
                            }
                        }
                    });
                }, this.mutationDebounce);
            });

            this.mutationObserver.observe(document, {
                childList: true,
                subtree: true,
                attributes: false
            });
        }

        stopScanning() {
            if (this.scanIntervalId) {
                clearInterval(this.scanIntervalId);
                this.scanIntervalId = null;
                ConfigManager.log('info', 'Stopped periodic scanning');
            }
            if (this.mutationTimeout !== null) {
                clearTimeout(this.mutationTimeout);
                this.mutationTimeout = null;
            }
            this.mutationTimeout = undefined;
        }
    }

    // 视频优化器
    class VideoOptimizer {
        constructor(video, type) {
            this.video = video;
            this.type = type;
            this.initialized = false;
            this.active = true;
            this.stallCount = 0; // 新增:记录卡顿次数
            this.lastStallTime = 0; // 新增:记录上次卡顿时间

            // 添加优化器引用
            video.optimizerInstance = this;

            ConfigManager.log('info', `Creating optimizer for ${type.toUpperCase()} video`);

            // 启动异步初始化(不阻塞构造函数)
            this.startInitialization();
        }

        // 新增:独立异步初始化方法
        async startInitialization() {
            try {
                await this.initOptimizer();
                this.initialized = true; // 初始化完成后标记
            } catch (e) {
                ConfigManager.log('error', `初始化失败: ${e.message}`);
                this.cleanup();
            }
        }

        /**
         * 初始化优化器(异步入口方法)
         * @returns {Promise<void>}
         */
        async initOptimizer() {
            if (!this.active) {
                ConfigManager.log('warn', 'Optimizer is inactive, skipping initialization');
                return;
            }

            try {
                // 新增:初始化过程中定期检查 active 状态(防止中途被清理)
                const checkActive = () => {
                    if (!this.active) {
                        throw new Error('Optimizer was deactivated during initialization');
                    }
                };

                // HLS 类型初始化(新增:加载前、加载后双重检查)
                if (this.type === 'hls') {
                    checkActive(); // 初始化前检查
                    await this.initHlsOptimizer();
                    checkActive(); // 加载完成后强制检查
                }

                // 缓冲优化器初始化(新增:类存在性检查后检查)
                if (typeof BufferOptimizer === 'undefined') {
                    throw new Error('BufferOptimizer class is not defined');
                }
                checkActive();

                // 初始化缓冲优化器(原有逻辑)
                this.bufferOptimizer = new BufferOptimizer(this.video, this.type);
                checkActive(); // 检查状态

                // 设置事件监听(原有逻辑)
                this.setupEventListeners();
                checkActive(); // 检查状态

                this.initialized = true;
                ConfigManager.log('info', '优化器初始化成功');

                // 延迟触发初始缓冲检查(原有逻辑)
                setTimeout(() => {
                    this.bufferOptimizer?.ensureBuffer();
                }, 1000);
            } catch (e) {
                ConfigManager.log('error', `优化器初始化失败: ${e.message}`);
                this.cleanup(); // 初始化失败时清理资源
            }
        }

        /**
         * 初始化 HLS 优化器(异步方法)
         * @returns {Promise<void>}
         */

        async initHlsOptimizer() {
            // 优先使用用户配置的 Hls.js 路径
            const customHlsUrl = ConfigManager.get('hlsJsUrl');
            const hlsVersion = ConfigManager.get('hlsJsVersion');
            const versionRegex = /^\d+\.\d+\.\d+$/;
            if (!versionRegex.test(hlsVersion)) {
                throw new Error(`无效的 Hls.js 版本格式: ${hlsVersion}(需为 x.y.z 格式)`);
            }

            // 新增:明确版本比较逻辑(主版本 >=1,次版本 >=3)
            const [major, minor, patch] = hlsVersion.split('.').map(Number);
            if (major < 1 || (major === 1 && minor < 3)) {
                throw new Error(`Hls.js 版本过低: ${hlsVersion}(最低要求 1.3.0)`);
            }

            const defaultHlsUrls = [
                `https://cdn.jsdelivr.net/npm/hls.js@${hlsVersion}/dist/hls.min.js`,
                `https://unpkg.com/hls.js@${hlsVersion}/dist/hls.min.js`
            ];

            const hlsUrls = customHlsUrl ? [customHlsUrl] : defaultHlsUrls;

            for (const url of hlsUrls) {
                let script; // 声明 script 变量用于后续清理
                try {
                    if (typeof window.Hls !== 'undefined') break;

                    ConfigManager.log('info', `尝试加载 Hls.js 地址: ${url}`);
                    await Promise.race([
                        new Promise((resolve, reject) => {
                            script = document.createElement('script'); // 保存 script 引用
                            script.src = url;
                            // 保存事件监听器引用(新增)
                            script.onload = () => {
                                script.onload = null; // 清理监听器
                                resolve();
                            };
                            script.onerror = (e) => {
                                script.onerror = null; // 清理监听器
                                reject(e);
                            };
                            document.head.appendChild(script);
                        }),
                        new Promise((_, reject) =>
                            setTimeout(() => reject(new Error('Script load timeout')), 5000)
                        )
                    ]);

                    ConfigManager.log('info', 'Hls.js 加载成功');
                    try {
                        this.setupHlsPlayer();
                        return;
                    } catch (setupError) {
                        ConfigManager.log('error', `HLS 播放器配置失败: ${setupError.message}`);
                    }
                } catch (e) {
                    ConfigManager.log('warn', `Hls.js 地址 ${url} 加载失败: ${e.message}(等待 500ms 后尝试下一个)`);
                    // 新增:加载失败时移除残留的 script 标签
                    if (script) {
                        // 移除前清理事件监听器(新增)
                        script.onload = null;
                        script.onerror = null;
                        document.head.removeChild(script);
                        script = null;
                    }
                    await new Promise(resolve => setTimeout(resolve, 500));
                }
            }

            throw new Error(`所有 Hls.js 地址加载失败(尝试地址: ${hlsUrls.join(', ')})`);
        }

        /**
         * 配置 HLS 播放器(依赖 Hls.js)
         */
        setupHlsPlayer() {
            // 检查库是否成功加载
            if (typeof window.Hls === 'undefined') {
                ConfigManager.log('error', 'Hls.js 库未加载');
                return;
            }

            // 新增:检查视频元素是否仍存在于DOM中
            if (!document.contains(this.video)) {
                ConfigManager.log('warn', '视频元素已被移除,跳过 HLS 播放器配置');
                return;
            }

            ConfigManager.log('info', 'Setting up Hls.js player');

            try {
                this.hls = new window.Hls({
                    maxBufferLength: ConfigManager.get('maxBufferSize'),
                    backBufferLength: 5,
                    lowLatencyMode: true,
                    maxMaxBufferLength: 30
                });

                // 附加到视频元素
                this.hls.attachMedia(this.video);

                // 加载源
                const url = this.video.currentSrc || this.video.src;
                if (url) {
                    this.hls.loadSource(url);
                    ConfigManager.log('info', `Loading HLS stream from: ${url}`);
                } else {
                    ConfigManager.log('warn', 'No video source available for HLS');
                }

                // 新增:监听 HLS 质量切换事件(记录清晰度变化)
                this.hls.on(window.Hls.Events.LEVEL_SWITCHED, (event, data) => {
                    const level = this.hls.levels[data.level];
                    ConfigManager.log('debug', `HLS 质量切换: 分辨率 ${level.width}x${level.height}, 码率 ${level.bitrate}bps`);
                });

                // 新增:监听缓冲追加事件(优化缓冲策略)
                this.hls.on(window.Hls.Events.BUFFER_APPENDED, (event, data) => {
                    if (this.bufferOptimizer) {
                        this.bufferOptimizer.handleBufferUpdate(data.details);
                    }
                });

                // 新增:监听质量加载事件(跟踪加载进度)
                this.hls.on(window.Hls.Events.LEVEL_LOADING, (event, data) => {
                    ConfigManager.log('debug', `加载质量 ${data.level}(类型: ${data.type})`);
                });

                // 监听HLS事件(优化错误记录)
                this.hls.on(window.Hls.Events.ERROR, (event, data) => {
                    const errorType = data.type || '未知类型';
                    const errorDetails = data.details || '无详细错误信息';
                    const isFatal = data.fatal ? '致命' : '非致命';

                    ConfigManager.log(
                        data.fatal ? 'error' : 'warn',
                        `HLS ${isFatal}错误(类型: ${errorType}, 详情: ${errorDetails}`
                    );
                });
            } catch (e) {
                ConfigManager.log('error', `HLS 播放器配置失败: ${e.message}`);
            }
        }

        setupEventListeners() {
            // 确保所有事件都能捕获到
            const events = [
                'play', 'pause', 'seeking', 'seeked',
                'timeupdate', 'error', 'progress',
                'waiting', 'stalled', 'canplay', 'loadedmetadata',
                'loadeddata', 'ended'
            ];
            this.eventHandlers = this.eventHandlers || {};
            const playHandler = () => {
                this.bufferOptimizer?.onPlay && this.bufferOptimizer.onPlay();
            };
            this.video.addEventListener('play', playHandler);
            this.eventHandlers['play'] = playHandler;
            const stallHandler = () => {
                this.handleStall();
            };
            this.video.addEventListener('stalled', stallHandler);
            this.eventHandlers['stalled'] = stallHandler;
            events.forEach(eventName => {
                const handler = (e) => this.handlePlayerEvent(eventName, e);
                this.video.addEventListener(eventName, handler);
                this.eventHandlers[eventName] = handler;
            });
            ConfigManager.log('debug', 'Event listeners set up');
        }

        /**
         * 处理播放器事件(核心逻辑)
         * @param {string} type 事件类型
         * @param {Event} event 事件对象
         */
        handlePlayerEvent(type, event) {
            switch(type) {
                case 'seeking':
                    ConfigManager.log('info', `Seeking to ${this.video.currentTime.toFixed(2)}s`);
                    break;

                case 'play':
                    ConfigManager.log('info', 'Playback started');
                    if (this.bufferOptimizer) {
                        this.bufferOptimizer.ensureBuffer();
                    }
                    break;

                case 'pause':
                    ConfigManager.log('debug', 'Playback paused');
                    break;

                case 'timeupdate': {
                    // 根据日志级别调整记录频率:debug 全量记录,info 抽样(10%)
                    const shouldLog = ConfigManager.get('logLevel') === 'debug'
                        ? true
                        : Math.random() < 0.1;
                    if (shouldLog) {
                        ConfigManager.log('debug', `当前播放时间: ${this.video.currentTime.toFixed(2)}s`);
                    }
                    break;
                }

                case 'error':
                    ConfigManager.log('warn', 'Video error detected: ' + (this.video.error ? this.video.error.message : 'Unknown error'));
                    break;

                case 'seeked':
                    ConfigManager.log('debug', `Seeked to ${this.video.currentTime.toFixed(2)}s`);
                    break;

                case 'progress': {
                    if (this.video.buffered.length === 0) break;

                    // 根据日志级别调整记录频率:debug 全量记录,info 抽样(5%)
                    const shouldLog = ConfigManager.get('logLevel') === 'debug'
                        ? true
                        : Math.random() < 0.05;
                    if (shouldLog) {
                        const bufferedEnd = this.video.buffered.end(this.video.buffered.length - 1);
                        ConfigManager.log('debug', `已缓冲至: ${bufferedEnd.toFixed(2)}s`);
                    }
                    break;
                }

                case 'ended':
                    ConfigManager.log('info', '视频播放结束,清理缓冲资源');
                    this.bufferOptimizer?.stopOptimization(); // 触发缓冲优化器停止
                    break;

                case 'waiting':
                case 'stalled': {
                    const now = Date.now();
                    const stallWarningThreshold = ConfigManager.get('suppressStallWarningsAfter');
                    if (now - this.lastStallTime > 1000) {
                        this.stallCount++;
                        this.lastStallTime = now;
                    }
                    const logLevel = this.stallCount > stallWarningThreshold ? 'debug' : 'warn';
                    ConfigManager.log(
                        logLevel,
                        `播放卡顿(类型: ${type}, 累计次数: ${this.stallCount}, 阈值: ${stallWarningThreshold})`
                    );
                    break;
                }

                case 'canplay':
                    ConfigManager.log('debug', 'Enough data available to play');
                    break;

                case 'loadedmetadata':
                    ConfigManager.log('debug', `Video metadata loaded. Duration: ${this.video.duration.toFixed(1)}s`);
                    break;
            }
        }

        cleanup() {
            ConfigManager.log('info', '清理优化器资源(开始)');
            if (this.eventHandlers && typeof this.eventHandlers === 'object') {
                Object.entries(this.eventHandlers).forEach(([eventName, handler]) => {
                    this.video.removeEventListener(eventName, handler);
                });
                this.eventHandlers = null;
            }
            if (this.hls) {
                this.hls.destroy();
                this.hls = null;
            }
            if (this.bufferOptimizer) {
                this.bufferOptimizer.stopOptimization && this.bufferOptimizer.stopOptimization();
                this.bufferOptimizer = null;
            }
            if (this.video.optimizerInstance) {
                delete this.video.optimizerInstance;
            }
            // 移除 hasEventListener 检查(非标准 API)
            this.stallCount = 0;
            this.lastStallTime = 0;
            this.active = false;
            this.initialized = false;
            ConfigManager.log('info', '清理优化器资源(完成)');
        }
    }

    /**
     * 缓冲优化器(核心性能模块)
     * 负责根据网络速度动态调整视频缓冲大小,平衡加载速度与播放流畅性。
     * 支持 HLS 等流媒体协议的缓冲策略优化,通过采样网络速度、监听缓冲事件实现智能调整。
     */
    class BufferOptimizer {
        /**
         * 初始化缓冲优化器
         * @param {HTMLVideoElement} video 目标视频元素
         * @param {string} type 视频类型(hls/mp4/dash)
         */
        constructor(video, type) {
            this.video = video;
            this.type = type;
            this.optimizationInterval = null;
            this.networkSpeedKBps = 0;
            this.speedSamples = [];
            this.targetBuffer = ConfigManager.get('maxBufferSize');
            this.minBuffer = ConfigManager.get('minBufferSize');
            this.active = true;
            const bufferRatios = ConfigManager.get('bufferRatios') || {};
            const defaultRatios = { hls: 1.3, mp4: 0.8, dash: 1.0, webm: 1.0 };
            this.bufferRatio = typeof bufferRatios[type] === 'number' && bufferRatios[type] > 0
                ? bufferRatios[type]
                : defaultRatios[type];
            const baseConfig = ConfigManager.getConfig();
            this.maxBufferSize = Math.max(Math.round(baseConfig.maxBufferSize * this.bufferRatio), 1);
            this.minBufferSize = Math.max(Math.round(baseConfig.minBufferSize * this.bufferRatio), 1);
            ConfigManager.log('info', `Creating buffer optimizer for ${type.toUpperCase()} video`);
            this.startOptimizationLoop();
        }

        /**
         * 动态调整缓冲阈值(示例逻辑:HLS增加30%,MP4减少20%)
         * @param {number} baseSize 基础阈值
         * @returns {number} 调整后的阈值
         */
        getDynamicBufferSize(baseSize) {
            switch (this.type) {
                case 'hls':
                    return Math.round(baseSize * 1.3);
                case 'mp4':
                    return Math.round(baseSize * 0.8);
                default:
                    return baseSize;
            }
        }

        /**
         * 启动缓冲优化循环(定期检查缓冲状态)
         */
        startOptimizationLoop() {
            ConfigManager.log('debug', '启动缓冲优化循环');
            this.optimizationInterval = setInterval(() => {
                this.ensureBuffer();
                this.adjustBufferSize();
            }, 2000);
        }

        /**
         * 确保当前缓冲满足最小阈值(播放前/恢复播放时调用)
         * 若当前缓冲小于最小阈值,触发缓冲策略调整(如降低清晰度、增加预加载)
         * @returns {void}
         */
        ensureBuffer() {
            if (this.video.buffered.length === 0) {
                ConfigManager.log('info', 'Starting buffer preload');
                this.preloadNextSegment(this.video.currentTime);
            } else {
                const end = this.video.buffered.end(this.video.buffered.length - 1);
                const bufferSize = end - this.video.currentTime;
                ConfigManager.log('debug', `Current buffer: ${bufferSize.toFixed(1)}s`);
            }
        }

        /**
         * 动态调整目标缓冲大小(根据网络速度和播放状态)
         * @returns {void}
         */
        adjustBufferSize() {
            // 使用带时间戳的采样计算平均速度
            const validSamples = this.speedSamples.map(s => s.speed);
            const avgSpeed = validSamples.reduce((sum, val) => sum + val, 0) / validSamples.length || 0;

            // 网络速度越快,允许更大的缓冲(最大不超过 maxBufferSize)
            this.targetBuffer = Math.min(
                ConfigManager.get('maxBufferSize'),
                this.minBuffer + (avgSpeed / 100) * 2 // 示例公式:每100KB/s增加2秒缓冲
            );

            // 新增:确保目标缓冲不低于最小阈值(避免弱网下缓冲过小)
            this.targetBuffer = Math.max(this.targetBuffer, this.minBuffer);

            ConfigManager.log('info', `优化:动态调整目标缓冲至 ${this.targetBuffer.toFixed(1)}s(当前网速: ${avgSpeed.toFixed(1)}KB/s)`);
        }

        /**
         * 处理缓冲更新事件(带空采样防御)
         * @param {Object} bufferDetails 缓冲详细信息(包含 chunkSize、duration 等)
         */
        handleBufferUpdate(bufferDetails) {
            if (this.type !== 'hls') return;
            if (!bufferDetails) {
                ConfigManager.log('debug', 'Buffer details is undefined, skipping update');
                return;
            }
            const isChunkSizeValid = typeof bufferDetails.chunkSize === 'number' && !isNaN(bufferDetails.chunkSize);
            const isDurationValid = typeof bufferDetails.duration === 'number' && !isNaN(bufferDetails.duration);
            const chunkSize = isChunkSizeValid ? Math.abs(bufferDetails.chunkSize) : 1;
            const chunkDuration = isDurationValid ? Math.abs(bufferDetails.duration) : 0.1;
            if (chunkDuration === 0) {
                ConfigManager.log('warn', 'chunkDuration 为0,跳过速度计算');
                return;
            }
            const speed = chunkSize / 1024 / chunkDuration;
            this.speedSamples.push({
                time: Date.now(),
                speed: speed
            });
            const now = Date.now();
            this.speedSamples = this.speedSamples.filter(sample => now - sample.time <= 30000);
            if (this.speedSamples.length > 100) {
                this.speedSamples = this.speedSamples.slice(-100);
            }
            if (this.speedSamples.length === 0) {
                ConfigManager.log('debug', 'No speed samples available, skipping buffer check');
                return;
            }
        }

        /**
         * 触发额外缓冲加载(通过调整播放器属性或HLS配置)
         */
        triggerBufferLoad() {
            if (this.type === 'hls') {
                // 优先通过 optimizerInstance.hls 获取 hls 实例
                const hlsInstance = (this.video.optimizerInstance && this.video.optimizerInstance.hls) ? this.video.optimizerInstance.hls : this.video.hls;
                if (hlsInstance) {
                    hlsInstance.config.maxBufferLength = this.targetBuffer;
                    ConfigManager.log('info', `优化:调整HLS maxBufferLength至 ${this.targetBuffer}`);
                } else {
                    ConfigManager.log('warn', 'HLS 实例未找到,无法调整缓冲长度');
                }
            } else {
                // 非HLS类型尝试手动触发加载(兼容性处理)
                this.video.load();
                ConfigManager.log('info', '优化:触发非HLS视频的手动缓冲加载');
            }
        }

        /**
         * 停止缓冲优化(清理资源)
         * @returns {void}
         */
        stopOptimization() {
            this.speedSamples = []; // 清空采样数据
            this.active = false; // 标记为非活跃状态
            ConfigManager.log('debug', 'Buffer optimizer stopped');
        }

        startNetworkSpeedMonitor() {
            // 每30秒测量一次网络速度
            this.networkMonitorInterval = setInterval(() => {
                this.measureNetworkSpeed();
            }, 30000);

            // 初始测量
            setTimeout(() => this.measureNetworkSpeed(), 5000);
        }

        measureNetworkSpeed() {
            const img = new Image();
            const startTime = Date.now();
            const testFileUrl = `https://via.placeholder.com/10?r=${Math.random()}`;

            img.onload = () => {
                const duration = (Date.now() - startTime) / 1000;
                const sizeKB = 0.01; // 10像素PNG大约是10字节
                const speedKBps = sizeKB / duration;

                if (speedKBps > 0) {
                    this.networkSpeedKBps = speedKBps;

                    // 平滑更新值
                    if (this.prevNetworkSpeed) {
                        this.networkSpeedKBps = (this.prevNetworkSpeed * 0.7 + speedKBps * 0.3);
                    }

                    this.prevNetworkSpeed = this.networkSpeedKBps;
                    ConfigManager.log('debug', `Network speed: ${this.networkSpeedKBps.toFixed(1)} KB/s`);
                }
            };

            img.onerror = () => {
                ConfigManager.log('debug', 'Network speed test failed');
            };

            img.src = testFileUrl;
        }

        optimize() {
            const now = Date.now();

            // 确保有足够的时间间隔
            if (now - this.lastOptimizeTime < 1900) return;
            this.lastOptimizeTime = now;

            if (!this.video.readyState || this.video.readyState < 1) return;

            const position = this.video.currentTime;
            const buffered = this.video.buffered;

            if (buffered.length === 0) {
                ConfigManager.log('debug', 'No buffered data available');
                this.preloadNextSegment(position);
                return;
            }

            const currentBufferEnd = buffered.end(buffered.length - 1);
            const bufferSize = currentBufferEnd - position;

            // 添加到历史记录
            this.bufferHistory.push({
                time: now,
                size: bufferSize,
                position
            });

            // 保留最近10条记录
            if (this.bufferHistory.length > 10) {
                this.bufferHistory.shift();
            }

            // 动态调整缓冲大小
            const targetSize = this.calculateTargetBufferSize(bufferSize);

            // 需要预加载更多数据
            if (bufferSize < targetSize) {
                ConfigManager.log('info', `Buffer too small (${bufferSize.toFixed(1)}s < ${targetSize.toFixed(1)}s)`);
                this.preloadNextSegment(position);
            } else {
                ConfigManager.log('debug', `Buffer ${bufferSize.toFixed(1)}s ≥ target ${targetSize.toFixed(1)}s`);
            }

            // 检查卡顿
            if (position === this.lastPosition && position > 0) {
                // 仅在缓冲区有足够大小时才标记为卡顿
                if (bufferSize > 1.0) {
                    ConfigManager.log('warn', 'Playback stalled despite sufficient buffer');
                    this.handleStall(position);
                }
            }
            this.lastPosition = position;
        }

        calculateTargetBufferSize(currentSize) {
            const minSize = ConfigManager.get('minBufferSize');
            const maxSize = ConfigManager.get('maxBufferSize');

            // 基本值范围限制
            let targetSize = Math.max(minSize, Math.min(currentSize, maxSize * 0.9));

            // 根据网络状况动态调整
            if (this.networkSpeedKBps > 0) {
                if (this.networkSpeedKBps < 100) { // 低速网络 < 100KB/s
                    targetSize = Math.max(targetSize, maxSize * 0.8);
                    ConfigManager.log('debug', 'Slow network detected, increasing buffer target');
                } else if (this.networkSpeedKBps > 500) { // 高速网络 > 500KB/s
                    targetSize = Math.max(minSize, targetSize * 0.7);
                    ConfigManager.log('debug', 'Fast network detected, reducing buffer target');
                }
            }

            // 根据历史趋势调整
            if (this.bufferHistory.length >= 3) {
                const recent = this.bufferHistory.slice(-3);
                const sizeSum = recent.reduce((sum, item) => sum + item.size, 0);
                const avgSize = sizeSum / recent.length;

                // 如果缓冲区在减少,增加目标值
                if (avgSize < targetSize * 0.9) {
                    targetSize *= 1.2;
                    ConfigManager.log('debug', 'Buffer decreasing, increasing target');
                } else if (avgSize > targetSize * 1.2) {
                    targetSize *= 0.9;
                    ConfigManager.log('debug', 'Buffer increasing, reducing target');
                }
            }

            return Math.min(maxSize, Math.max(minSize, targetSize));
        }

        // 增强的卡顿处理
        handleStall(position) {
            const now = Date.now();

            // 记录当前播放状态
            const playState = this.video.paused ? "paused" : "playing";

            // 检查卡顿位置是否在可用缓冲区内
            let inBufferedRange = false;

            if (this.video.buffered.length > 0) {
                const currentBufferStart = this.video.buffered.start(0);
                const currentBufferEnd = this.video.buffered.end(this.video.buffered.length - 1);
                inBufferedRange = (position >= currentBufferStart && position <= currentBufferEnd);
            }

            ConfigManager.log('warn',
                `优化:检测到卡顿,位置 ${position.toFixed(2)}s(状态: ${playState}, inBuffer: ${inBufferedRange})`);

            // 尝试跳过卡顿点(频率限制)
            if (!this.video.paused && !inBufferedRange && (now - this.lastSkipTime) > 5000) {
                const skipPosition = Math.min(
                    (this.video.duration || 1000000) - 0.5,
                    position + 0.5
                );

                ConfigManager.log('info', `优化:自动跳过卡顿点,跳转到 ${skipPosition.toFixed(2)}s`);
                this.video.currentTime = skipPosition;
                this.lastSkipTime = now;
            }

            // 总是尝试预加载当前位置数据
            this.preloadNextSegment(position);
        }

        preloadNextSegment(position) {
            // HLS特定处理
            if (this.type === 'hls' && window.Hls && this.hls) {
                try {
                    ConfigManager.log('info', 'Preloading next HLS segment');

                    // 检查当前播放列表
                    if (this.hls.levels && this.hls.currentLevel >= 0) {
                        // 获取下一个片段
                        const frag = this.hls.getNextFragment(position);
                        if (frag) {
                            this.hls.loadFragment(frag);
                            ConfigManager.log('debug', `Loading fragment ${frag.sn} from level ${frag.level}`);
                        }
                    } else {
                        // 简单重新加载
                        this.hls.startLoad(position);
                    }
                } catch (e) {
                    ConfigManager.log('warn', 'HLS preloading failed: ' + e.message);
                }
                return;
            }

            // 通用预加载处理
            const rangeSize = this.calculateTargetBufferSize(0);
            ConfigManager.log('info', `Preloading ${rangeSize.toFixed(1)}s of data`);

            // 模拟预加载行为
            if (this.video.networkState === HTMLMediaElement.NETWORK_IDLE) {
                this.video.dispatchEvent(new Event('progress'));
            }
        }

        ensureBuffer() {
            if (this.video.buffered.length === 0) {
                ConfigManager.log('info', 'Starting buffer preload');
                this.preloadNextSegment(this.video.currentTime);
            } else {
                const end = this.video.buffered.end(this.video.buffered.length - 1);
                const bufferSize = end - this.video.currentTime;
                ConfigManager.log('debug', `Current buffer: ${bufferSize.toFixed(1)}s`);
            }
        }

        cleanup() {
            this.stopOptimization();

            if (this.networkMonitorInterval) {
                clearInterval(this.networkMonitorInterval);
            }
        }
    }

    // 资源回收管理器
    class ResourceManager {
        static setup() {
            // 页面可见性回收
            document.addEventListener('visibilitychange', () => {
                if (document.hidden) {
                    this.downgradeMedia();
                }
            });

            // 确保DOM准备好
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', () => {
                    this.setupRemovedNodeTracking();
                });
            } else {
                this.setupRemovedNodeTracking();
            }
        }

        static setupRemovedNodeTracking() {
            // 确保document.body存在
            if (!document.body) {
                ConfigManager.log('warn', 'document.body not available for tracking');
                return;
            }

            const observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    if (!mutation.removedNodes) return;

                    for (let i = 0; i < mutation.removedNodes.length; i++) {
                        const node = mutation.removedNodes[i];
                        if (node.nodeName === 'VIDEO' && node.optimizerInstance) {
                            ConfigManager.log('info', 'Cleaning up removed video element');
                            node.optimizerInstance.cleanup();
                            delete node.optimizerInstance;
                        }
                    }
                });
            });

            try {
                observer.observe(document.body, { childList: true, subtree: true });
                ConfigManager.log('info', 'Node removal tracking enabled');
            } catch (e) {
                ConfigManager.log('error', `Node removal tracking failed: ${e.message}`);
            }
        }

        static downgradeMedia() {
            ConfigManager.log('info', 'Page hidden - downgrading media resources');

            // 查找所有活动优化器
            const optimizers = [];
            const videoElements = document.querySelectorAll('video');

            videoElements.forEach(video => {
                if (video.optimizerInstance) {
                    optimizers.push(video.optimizerInstance);
                }
            });

            // 释放资源
            optimizers.forEach(optimizer => {
                if (optimizer.bufferOptimizer) {
                    optimizer.bufferOptimizer.stopOptimization();
                    ConfigManager.log('info', '优化:页面隐藏,已停止缓冲优化');
                }
                // 减少视频质量(HLS)
                if (optimizer.hls && optimizer.hls.levels?.length > 1) {
                    optimizer.hls.nextLevel = Math.max(0, Math.floor(optimizer.hls.levels.length / 2));
                    ConfigManager.log('info', `优化:页面隐藏,自动降低HLS视频质量到 level ${optimizer.hls.nextLevel}`);
                }
            });
        }
    }

    // 内存保护
    class MemoryGuard {
        static setup() {
            if ('memory' in performance && performance.memory) {
                setInterval(() => this.checkMemoryStatus(), 30000);
                ConfigManager.log('info', 'Memory guard activated');
            } else {
                ConfigManager.log('info', 'Browser does not support memory monitoring');
            }
        }

        static checkMemoryStatus() {
            const mem = performance.memory;
            const usedMB = mem.usedJSHeapSize / (1024 * 1024);
            const threshold = 100; // 100MB 阈值

            if (usedMB > threshold * 1.5) {
                this.freeResources(0.5); // 严重情况释放50%
                ConfigManager.log('warn', `Critical memory usage (${usedMB.toFixed(1)}MB)`);
            } else if (usedMB > threshold) {
                this.freeResources(0.3); // 警告情况释放30%
                ConfigManager.log('info', `High memory usage (${usedMB.toFixed(1)}MB)`);
            }
        }

        static freeResources(percent) {
            ConfigManager.log('info', `Freeing ${percent*100}% of resources`);

            // 查找所有活动优化器
            const optimizers = [];
            const videoElements = document.querySelectorAll('video');

            videoElements.forEach(video => {
                if (video.optimizerInstance && video.optimizerInstance.bufferOptimizer) {
                    optimizers.push(video.optimizerInstance.bufferOptimizer);
                }
            });

            // 释放缓冲资源
            optimizers.forEach(optimizer => {
                optimizer.stopOptimization();
            });
        }
    }

    // 主控制器
    class Main {
        static init() {
            ConfigManager.log('info', 'Script initializing...');

            // 初始化系统
            ResourceManager.setup();
            MemoryGuard.setup();

            // 启动视频检测
            this.videoDetector = new VideoDetector();

            ConfigManager.log('info', 'Script ready');

            // 注册(不可用)全局清理钩子
            window.addEventListener('beforeunload', Main.cleanupAllOptimizers, { once: true });
            window.addEventListener('unload', Main.cleanupAllOptimizers, { once: true });
        }

        /**
         * 清理所有 video 元素上的优化器实例,防止内存泄漏
         */
        static cleanupAllOptimizers() {
            const videos = document.querySelectorAll('video');
            videos.forEach(video => {
                if (video.optimizerInstance && typeof video.optimizerInstance.cleanup === 'function') {
                    try {
                        video.optimizerInstance.cleanup();
                    } catch (e) {
                        ConfigManager.log('warn', '全局清理优化器时出错: ' + e.message);
                    }
                    delete video.optimizerInstance;
                }
            });
            ConfigManager.log('info', '已执行全局优化器清理');
        }
    }

    // 启动主控制器
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            Main.init();
        });
    } else {
        Main.init();
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址