您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
高效视频流媒体性能优化脚本
当前为
// ==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或关注我们的公众号极客氢云获取最新地址