您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为各类网站视频提供通用的倍速播放控制功能
// ==UserScript== // @name 通用视频倍速播放控制 // @namespace http://tampermonkey.net/ // @version 3.5 // @description 为各类网站视频提供通用的倍速播放控制功能 // @author ming2k // @match https://www.bilibili.com/video/* // @match https://www.youtube.com/* // @match https://www.iyf.tv/play/* // @grant none // @run-at document-idle // @license MIT // ==/UserScript== (function() { 'use strict'; // 配置信息 - 针对慢加载视频优化 const config = { // 默认初始播放速度 defaultSpeed: 1.0, // 变速步长 speedStep: 0.1, // 最小速度 minSpeed: 0.1, // 最大速度 maxSpeed: 4.0, // 提示显示时间(毫秒) notificationDuration: 1500, // 快捷键设置(与MPV兼容) hotkeys: { increaseSpeed: ']', // 增加速度 decreaseSpeed: '[', // 减少速度 resetSpeed: '{', // 重置速度 doubleSpeed: '}' // 2倍速 }, // 启用详细日志 debug: true, // 尝试间隔(毫秒) - 增加间隔以处理慢速加载 retryInterval: 2000, // 最大尝试次数 - 大幅增加以处理慢速加载 maxRetries: 40, // 延长初始等待时间(毫秒) initialWaitTime: 5000, // 长时间监控 - 持续时间(毫秒) longTermMonitoring: 300000, // 5分钟 // 长时间监控 - 检查间隔(毫秒) longTermCheckInterval: 8000 }; // 状态变量 let videoElement = null; let retryCount = 0; let notificationTimer = null; let notification = null; // 统一的存储键名 const storageKey = 'video_playback_speed'; let lastStoredSpeed = parseFloat(localStorage.getItem(storageKey)) || config.defaultSpeed; let isInitialized = false; let videoObserver = null; let speedApplied = false; let currentVideoUrl = location.href; // 记录当前视频URL let longTermMonitoringTimer = null; let videoFoundTime = null; let pendingSpeedPromise = null; let waitingForVideo = false; // 日志函数 function log(message, force = false) { if (config.debug || force) { console.log(`[视频倍速控制] ${message}`); } } // 添加样式 function addStyles() { const styles = ` .speed-notification { position: fixed; top: 70px; left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.8); color: white; padding: 10px 20px; border-radius: 4px; z-index: 9999999; font-size: 16px; font-weight: bold; opacity: 0; transition: opacity 0.3s ease; box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16); text-align: center; pointer-events: none; } .speed-notification.show { opacity: 1; } `; const styleElement = document.createElement('style'); styleElement.textContent = styles; document.head.appendChild(styleElement); log('样式已添加'); } // 创建通知元素 function createNotification() { notification = document.createElement('div'); notification.className = 'speed-notification'; document.body.appendChild(notification); log('通知元素已创建'); } // 显示通知 function showNotification(message) { if (!notification) { createNotification(); } notification.textContent = message; notification.classList.add('show'); // 清除之前的定时器 if (notificationTimer) { clearTimeout(notificationTimer); } // 设置新定时器隐藏通知 notificationTimer = setTimeout(() => { notification.classList.remove('show'); }, config.notificationDuration); } // 检查视频是否可用函数 - 更严格的可用性检查 function isVideoPlayable(video) { if (!video) return false; try { // 检查视频是否有效 if (video.readyState === 0) { log('视频元素存在但尚未初始化 (readyState=0)'); return false; } // 检查视频是否有效源或内容 const hasSource = video.src || video.querySelector('source') || video.readyState > 0; // 检查视频是否在文档中 const isInDocument = document.contains(video); // 检查视频是否有尺寸 const hasDimensions = (video.videoWidth > 0 && video.videoHeight > 0) || (video.offsetWidth > 0 && video.offsetHeight > 0); // 基本检查 if (!hasSource) { log('视频元素没有有效源'); return false; } if (!isInDocument) { log('视频元素不在文档中'); return false; } // 额外检查 - 可选 if (!hasDimensions && video.readyState <= 1) { log('视频元素没有尺寸且readyState低'); // 注意:某些网站可能隐藏了视频尺寸,所以这不是硬性条件 // 如果视频的readyState > 1,我们认为它是可用的,即使没有尺寸 if (video.readyState <= 1) { return false; } } return true; } catch (e) { log(`检查视频可用性时出错: ${e.message}`); return false; } } // 增强版查找视频元素 function findVideoElement() { // 检查现有的视频元素是否仍然有效 if (videoElement && isVideoPlayable(videoElement)) { return videoElement; } log('开始查找视频元素...'); // 尝试多种选择器 const selectors = [ 'video', // 标准视频标签 '.video-container video', // 从HTML结构分析得出的可能选择器 '.video-box video', // 另一个可能的选择器 'vg-player video', // Videogular 'aa-videoplayer video', // 自定义播放器 '*[id*="player"] video', // ID中包含player的元素内的视频 '*[class*="player"] video', // class中包含player的元素内的视频 // 增加更多选择器用于慢加载网站 '*[id*="video"] video', // ID中包含video的元素 '*[class*="video"] video', // class中包含video的元素 'iframe[src*="player"] video', // iframe中的播放器视频 'video[preload]', // 预加载的视频 '.container video', // 常见容器内的视频 'main video', // 主内容区域的视频 'article video', // 文章内的视频 'div > video', // div直接子元素的视频 'video[src]', // 有src属性的视频 'video > source', // 有source子元素的视频 ]; let foundVideos = []; // 收集所有视频元素 for (const selector of selectors) { const elements = document.querySelectorAll(selector); if (elements && elements.length > 0) { for (const video of elements) { // 获取实际视频元素(可能是source的父元素) const actualVideo = video.tagName.toLowerCase() === 'video' ? video : video.closest('video'); if (actualVideo && !foundVideos.includes(actualVideo) && isVideoPlayable(actualVideo)) { foundVideos.push(actualVideo); log(`找到可能的视频元素: ${selector}`); } } } } // 如果找到多个视频,尝试找出主视频 if (foundVideos.length > 1) { log(`找到${foundVideos.length}个视频元素,尝试确定主视频`); // 按照可能性排序: // 1. 可见的 // 2. 更大的 // 3. 位于视口中的 foundVideos.sort((a, b) => { const aVisible = isElementVisible(a); const bVisible = isElementVisible(b); // 可见性优先 if (aVisible && !bVisible) return -1; if (!aVisible && bVisible) return 1; // 然后是尺寸 const aSize = (a.offsetWidth || 0) * (a.offsetHeight || 0); const bSize = (b.offsetWidth || 0) * (b.offsetHeight || 0); if (aSize > bSize) return -1; if (aSize < bSize) return 1; // 最后是视口中的位置 const aInViewport = isInViewport(a); const bInViewport = isInViewport(b); if (aInViewport && !bInViewport) return -1; if (!aInViewport && bInViewport) return 1; return 0; }); log(`选择最可能的主视频元素`); return foundVideos[0]; } else if (foundVideos.length === 1) { return foundVideos[0]; } // 尝试搜索所有iframe const iframes = document.querySelectorAll('iframe'); for (let iframe of iframes) { try { if (iframe.contentDocument) { const iframeVideo = iframe.contentDocument.querySelector('video'); if (iframeVideo && isVideoPlayable(iframeVideo)) { log('在iframe中找到视频'); return iframeVideo; } } } catch (e) { // 跨域iframe无法访问内容 log('无法访问iframe内容: ' + e.message); } } // 深度搜索 function deepSearch(element, depth = 0) { if (!element || depth > 15) return null; // 增加搜索深度 if (element.tagName && element.tagName.toLowerCase() === 'video' && isVideoPlayable(element)) { return element; } // 搜索shadowDOM if (element.shadowRoot) { const shadowVideo = element.shadowRoot.querySelector('video'); if (shadowVideo && isVideoPlayable(shadowVideo)) return shadowVideo; // 深度搜索shadowDOM const allShadowElements = element.shadowRoot.querySelectorAll('*'); for (const shadowEl of allShadowElements) { const found = deepSearch(shadowEl, depth + 1); if (found) return found; } } for (let i = 0; i < element.children.length; i++) { const found = deepSearch(element.children[i], depth + 1); if (found) return found; } return null; } const deepSearchResult = deepSearch(document.body); if (deepSearchResult) { log('通过深度搜索找到视频'); return deepSearchResult; } // 最后尝试通过动态加载的iframe查找 // 某些网站可能延迟加载包含视频的iframe try { // 查找所有iframe,包括新加载的 const allIframes = document.querySelectorAll('iframe'); for (const iframe of allIframes) { try { // 尝试判断iframe是否是视频播放器 const isVideoFrame = iframe.src.includes('player') || iframe.src.includes('video') || iframe.src.includes('embed') || iframe.id.includes('player') || iframe.className.includes('player'); if (isVideoFrame) { log('找到可能包含视频的iframe,但无法直接访问内容'); // 不能直接返回iframe,但可以记录它存在 // 一些网站可能需要特殊处理 } } catch (e) { // 忽略跨域错误 } } } catch (e) { log(`检查iframe时出错: ${e.message}`); } log('未找到视频元素'); return null; } // 检查元素是否可见 function isElementVisible(element) { if (!element) return false; const style = window.getComputedStyle(element); return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && element.offsetWidth > 0 && element.offsetHeight > 0; } // 检查元素是否在视口中 function isInViewport(element) { if (!element) return false; const rect = element.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } // 获取最后使用的速度 function getLastSpeed() { try { const savedSpeed = parseFloat(localStorage.getItem(storageKey)); if (!isNaN(savedSpeed) && savedSpeed > 0) { log(`从localStorage加载速度: ${savedSpeed}x`); return savedSpeed; } } catch (e) { log(`无法读取保存的速度: ${e.message}`); } log(`未找到保存的速度,使用默认速度 ${config.defaultSpeed}x`); return config.defaultSpeed; } // 保存最后使用的速度 function saveLastSpeed(speed) { lastStoredSpeed = speed; try { localStorage.setItem(storageKey, speed); log(`已保存播放速度 ${speed}x 到localStorage`); } catch (e) { log(`无法保存速度: ${e.message}`); } } // 尝试多种方法设置播放速度 - 添加承诺等待功能 function setPlaybackSpeed(speed, showNotifFlag = true) { // 确保速度在范围内 speed = Math.max(config.minSpeed, Math.min(config.maxSpeed, speed)); speed = parseFloat(speed.toFixed(1)); // 保留一位小数 // 如果已经有等待设置的承诺,则取消它 if (pendingSpeedPromise) { clearTimeout(pendingSpeedPromise); pendingSpeedPromise = null; } // 查找视频元素 const video = findVideoElement(); if (video) { try { video.playbackRate = speed; log(`使用标准方式设置速度: ${speed}x`); if (showNotifFlag) { showNotification(`播放速度: ${speed}x`); } saveLastSpeed(speed); return true; } catch (e) { log(`标准方式设置速度失败: ${e.message}`); } } else { // 如果没找到视频但脚本已运行一段时间,设置一个延迟尝试 log(`未找到视频元素,设置延迟尝试设置速度: ${speed}x`); waitingForVideo = true; pendingSpeedPromise = setTimeout(() => { log(`执行延迟设置速度: ${speed}x`); setPlaybackSpeed(speed, showNotifFlag); pendingSpeedPromise = null; }, 5000); // 返回false表示当前未成功设置 return false; } // 尝试使用JavaScript注入 try { const script = document.createElement('script'); script.textContent = ` (function() { try { const videos = document.querySelectorAll('video'); let success = false; if (videos.length > 0) { videos.forEach(v => { try { // 检查视频是否有效 if (v && !v.ended && v.readyState > 0) { v.playbackRate = ${speed}; success = true; } } catch(e) { console.error('无法设置视频速度:', e); } }); } // 尝试从全局变量中找到播放器 const playerVars = ['player', 'videoPlayer', 'vgPlayer', 'mediaPlayer', 'ytPlayer', 'videojs']; for (const varName of playerVars) { if (window[varName]) { // 尝试不同的方法设置速度 try { if (typeof window[varName].setPlaybackRate === 'function') { window[varName].setPlaybackRate(${speed}); success = true; } else if (window[varName].player && typeof window[varName].player.setPlaybackRate === 'function') { window[varName].player.setPlaybackRate(${speed}); success = true; } else if (window[varName].getMedia && window[varName].getMedia()) { window[varName].getMedia().playbackRate = ${speed}; success = true; } else if (window[varName].video && window[varName].video.playbackRate !== undefined) { window[varName].video.playbackRate = ${speed}; success = true; } else if (window[varName].el && window[varName].el.playbackRate !== undefined) { window[varName].el.playbackRate = ${speed}; success = true; } } catch(e) { console.error('尝试通过全局变量设置速度失败:', e); } } } // 尝试在Angular组件中查找 if (window.ng && window.ng.probe) { const playerElements = document.querySelectorAll('vg-player, [class*="player"]'); playerElements.forEach(el => { try { const componentInstance = window.ng.probe(el); if (componentInstance && componentInstance.componentInstance) { const instance = componentInstance.componentInstance; if (instance.media && instance.media.playbackRate !== undefined) { instance.media.playbackRate = ${speed}; success = true; } } } catch(e) {} }); } // 尝试特定网站API // YouTube if (typeof document.querySelector('.html5-video-player') !== 'undefined') { const ytPlayer = document.querySelector('.html5-video-player'); if (ytPlayer && ytPlayer.getPlayer) { try { ytPlayer.getPlayer().setPlaybackRate(${speed}); success = true; } catch(e) {} } } // HTML5 video API - 再次尝试 document.querySelectorAll('video').forEach(v => { if (v && v.readyState > 0) { try { v.playbackRate = ${speed}; success = true; } catch(e) {} } }); window._speedChangeSuccess = success; } catch(e) { console.error('播放速度注入脚本错误:', e); window._speedChangeSuccess = false; } })(); `; document.body.appendChild(script); document.body.removeChild(script); // 检查是否成功 if (window._speedChangeSuccess) { log(`通过脚本注入设置速度: ${speed}x`); if (showNotifFlag) { showNotification(`播放速度: ${speed}x`); } saveLastSpeed(speed); return true; } } catch (e) { log(`脚本注入设置失败: ${e.message}`); } if (showNotifFlag) { log('无法设置播放速度,将在视频加载后重试', true); showNotification(`已保存速度设置,等待视频加载...`); // 保存要设置的速度,以便在视频加载后应用 saveLastSpeed(speed); } return false; } // 修改播放速度 function changeSpeed(delta) { const video = findVideoElement(); const currentSpeed = video ? video.playbackRate : lastStoredSpeed; const newSpeed = Math.round((currentSpeed + delta) * 10) / 10; // 保留一位小数 if (setPlaybackSpeed(newSpeed)) { lastStoredSpeed = newSpeed; // 确保当前调整的速度被记录为"最后一次速度" } else { // 如果设置失败但视频尚未加载,仍保存速度以便之后应用 lastStoredSpeed = newSpeed; saveLastSpeed(newSpeed); } } // 播放速度控制相关函数 function resetSpeed() { setPlaybackSpeed(config.defaultSpeed); } function doubleSpeed() { setPlaybackSpeed(2.0); } // 改进版:只处理特定按键的冲突而不是禁用所有键盘事件 function handleConflictingKeyBindings() { // 创建包含我们需要拦截的按键数组 const ourHotkeys = Object.values(config.hotkeys); log(`我们的热键: ${ourHotkeys.join(', ')}`); // 替代注入脚本,只阻止特定按键的事件 const script = document.createElement('script'); script.textContent = ` (function() { try { // 获取我们需要处理的快捷键 const keysToCatch = ${JSON.stringify(ourHotkeys)}; // 创建更精确的事件监听器,在捕获阶段先拦截 document.addEventListener('keydown', function(event) { // 只拦截我们关心的按键,且不在输入框中 if (keysToCatch.includes(event.key) && event.target.tagName !== 'INPUT' && event.target.tagName !== 'TEXTAREA') { // 标记这个事件已被我们的脚本处理 event._handledBySpeedController = true; } }, true); // 如果网站使用特定库,修改它们的行为而不是完全禁用 // 处理keyboardJS库 if (window.keyboardJS && typeof window.keyboardJS.bind === 'function') { const originalBind = window.keyboardJS.bind; window.keyboardJS.bind = function(key, pressHandler, releaseHandler) { // 如果绑定的是我们使用的键,则包装其处理程序 if (keysToCatch.includes(key)) { const wrappedPressHandler = function(e) { if (e._handledBySpeedController) return; if (pressHandler) pressHandler(e); }; const wrappedReleaseHandler = function(e) { if (e._handledBySpeedController) return; if (releaseHandler) releaseHandler(e); }; return originalBind.call(this, key, wrappedPressHandler, wrappedReleaseHandler); } // 其他键正常绑定 return originalBind.apply(this, arguments); }; console.log('已修改keyboardJS.bind以处理冲突键'); } // 处理Mousetrap库 if (window.Mousetrap && typeof window.Mousetrap.bind === 'function') { const originalBind = window.Mousetrap.prototype.bind; window.Mousetrap.prototype.bind = function(keys, callback, action) { // 检查单个键或键数组 const keysArray = Array.isArray(keys) ? keys : [keys]; const hasConflict = keysArray.some(k => keysToCatch.includes(k)); if (hasConflict) { // 包装回调函数 const wrappedCallback = function(e, combo) { if (e._handledBySpeedController) return; if (callback) return callback(e, combo); }; return originalBind.call(this, keys, wrappedCallback, action); } // 其他键正常绑定 return originalBind.apply(this, arguments); }; console.log('已修改Mousetrap.bind以处理冲突键'); } console.log('成功设置冲突按键处理'); } catch(e) { console.error('设置按键冲突处理失败:', e); } })(); `; document.body.appendChild(script); document.body.removeChild(script); log('已设置按键冲突处理'); } // 键盘事件处理,使用捕获阶段以确保我们的处理器先运行 function handleKeyPress(event) { // 忽略在输入框中的按键事件 if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') { return; } const key = event.key; // 检查是否匹配我们的快捷键 let handled = false; switch (key) { case config.hotkeys.increaseSpeed: changeSpeed(config.speedStep); handled = true; break; case config.hotkeys.decreaseSpeed: changeSpeed(-config.speedStep); handled = true; break; case config.hotkeys.resetSpeed: resetSpeed(); handled = true; break; case config.hotkeys.doubleSpeed: doubleSpeed(); handled = true; break; } if (handled) { // 如果处理了快捷键,阻止事件冒泡和默认行为 event.stopPropagation(); event.preventDefault(); } } // 处理URL变化的函数 - 增强检测 function handleUrlChange() { const newUrl = location.href; if (newUrl !== currentVideoUrl) { log(`检测到URL变化,从 ${currentVideoUrl} 到 ${newUrl}`); currentVideoUrl = newUrl; // URL变化时重置视频元素和状态 videoElement = null; speedApplied = false; // 重置视频记录时间 videoFoundTime = null; // 重新查找视频元素,使用渐进式延迟 const delays = [500, 1000, 2000, 3000, 5000, 8000, 13000]; delays.forEach((delay, index) => { setTimeout(() => { if (!videoElement) { log(`URL变更后第${index + 1}次查找视频 (延迟${delay}ms)`); videoElement = findVideoElement(); if (videoElement) { log('URL变化后找到视频元素'); applyInitialSpeed(); } } }, delay); }); } } // 设置MutationObserver监视DOM变化 function setupVideoObserver() { if (videoObserver) { videoObserver.disconnect(); } videoObserver = new MutationObserver((mutations) => { // 如果还没找到视频元素,继续尝试 if (!videoElement) { videoElement = findVideoElement(); if (videoElement) { log('MutationObserver找到视频元素'); speedApplied = false; applyInitialSpeed(); } } // 检查是否有新的视频加载(例如切换到新一集) for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (node.nodeName === 'VIDEO' || (node.nodeType === Node.ELEMENT_NODE && node.querySelector('video'))) { const newVideo = node.nodeName === 'VIDEO' ? node : node.querySelector('video'); if (newVideo && newVideo !== videoElement && isVideoPlayable(newVideo)) { log('检测到新的视频元素'); videoElement = newVideo; speedApplied = false; applyInitialSpeed(); } } } } } // 检查视频src变化,可能是同一个视频元素载入新内容 if (videoElement && !speedApplied) { applyInitialSpeed(); } }); videoObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src'] }); log('已设置视频元素观察器'); } // 改进版应用初始播放速度 - 减少自动干预但改进对慢加载视频的处理 function applyInitialSpeed() { // 如果已经应用过速度,不重复应用 if (speedApplied) { return; } if (videoElement) { // 记录找到视频的时间 if (!videoFoundTime) { videoFoundTime = Date.now(); } const speed = getLastSpeed(); log(`准备应用上次使用的速度: ${speed}x`); // 首次应用速度,增加延迟等待视频完全加载 setTimeout(() => { try { // 检查视频readyState是否足够应用速度 if (videoElement.readyState >= 2) { videoElement.playbackRate = speed; log(`成功应用播放速度: ${speed}x (readyState=${videoElement.readyState})`); showNotification(`应用播放速度: ${speed}x`); speedApplied = true; // 添加事件监听,但只在播放开始时检查一次,不持续监听 videoElement.addEventListener('play', function() { // 只在播放开始时检查一次 if (videoElement.playbackRate !== speed) { log(`检测到播放开始时速度不正确,重新应用: ${speed}x`); videoElement.playbackRate = speed; } }, { once: true }); // 监听loadeddata事件确保在数据加载后应用速度 videoElement.addEventListener('loadeddata', function() { if (videoElement.playbackRate !== speed) { log(`检测到视频数据加载后速度不正确,重新应用: ${speed}x`); videoElement.playbackRate = speed; } }, { once: true }); } else { // 视频尚未准备好,设置重试 log(`视频元素readyState不足 (${videoElement.readyState}),设置重试`); // 使用条件触发的事件监听器 const eventTypes = ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough']; // 为每个事件类型添加一次性监听器 eventTypes.forEach(eventType => { videoElement.addEventListener(eventType, function() { if (!speedApplied && videoElement.readyState >= 2) { log(`在 ${eventType} 事件后应用速度 ${speed}x`); try { videoElement.playbackRate = speed; showNotification(`应用播放速度: ${speed}x`); speedApplied = true; } catch (e) { log(`在 ${eventType} 事件后设置速度失败: ${e.message}`); } } }, { once: true }); }); // 添加备用延迟尝试,以防事件未触发 setTimeout(() => { if (!speedApplied) { log('使用备用延迟尝试应用速度'); try { videoElement.playbackRate = speed; log(`延迟备用设置成功 (readyState=${videoElement.readyState})`); showNotification(`应用播放速度: ${speed}x`); speedApplied = true; } catch (e) { log(`延迟备用设置失败: ${e.message}`); } } }, 4000); } } catch (e) { log(`应用播放速度失败: ${e.message}`); // 设置递增延迟的备用尝试 const retryDelays = [2000, 4000, 8000]; retryDelays.forEach((delay, index) => { setTimeout(() => { if (!speedApplied) { try { log(`第${index + 1}次重试应用速度 ${speed}x`); videoElement.playbackRate = speed; log(`重试成功`); showNotification(`应用播放速度: ${speed}x`); speedApplied = true; } catch (err) { log(`第${index + 1}次重试失败: ${err.message}`); } } }, delay); }); } }, 1000); // 给视频更多时间加载 } else if (waitingForVideo) { // 如果在等待视频但没有找到,不执行额外操作 log('等待视频元素出现...'); } else { // 启动长期监控以处理非常慢的视频加载 startLongTermMonitoring(); } } // 启动长期监控以处理特别慢的视频加载 function startLongTermMonitoring() { if (longTermMonitoringTimer) { clearInterval(longTermMonitoringTimer); } log('启动长期监控以等待视频加载'); let monitoringStartTime = Date.now(); longTermMonitoringTimer = setInterval(() => { const elapsedTime = Date.now() - monitoringStartTime; // 如果监控时间过长,停止监控 if (elapsedTime > config.longTermMonitoring) { log('长期监控超时,停止监控'); clearInterval(longTermMonitoringTimer); return; } // 如果已经找到视频并应用了速度,停止监控 if (videoElement && speedApplied) { log('视频已找到并应用了速度,停止长期监控'); clearInterval(longTermMonitoringTimer); return; } // 如果找到了视频但还没应用速度 if (videoElement && !speedApplied) { log('发现视频尚未应用速度,尝试应用'); applyInitialSpeed(); return; } // 尝试再次查找视频 log(`长期监控:第${Math.floor(elapsedTime / config.longTermCheckInterval)}次尝试查找视频`); videoElement = findVideoElement(); if (videoElement) { log('长期监控找到视频元素'); applyInitialSpeed(); } }, config.longTermCheckInterval); } // 主初始化函数 function initialize() { log('初始化中...'); // 加载保存的速度 lastStoredSpeed = getLastSpeed(); log(`初始化时加载速度: ${lastStoredSpeed}x`); // 添加样式 addStyles(); // 创建通知元素 createNotification(); // 处理冲突按键而不是禁用所有键盘事件 handleConflictingKeyBindings(); // 监听键盘事件 - 使用捕获阶段以确保我们的处理器先运行 document.addEventListener('keydown', handleKeyPress, true); // 监听URL变化 if (window.history && window.history.pushState) { // 监听popstate事件 window.addEventListener('popstate', handleUrlChange); // 重写history方法以捕获SPA页面变化 const originalPushState = window.history.pushState; window.history.pushState = function() { originalPushState.apply(this, arguments); handleUrlChange(); }; const originalReplaceState = window.history.replaceState; window.history.replaceState = function() { originalReplaceState.apply(this, arguments); handleUrlChange(); }; } // 定期检查URL变化(用于不触发history事件的变化) setInterval(handleUrlChange, 4000); // 设置视频观察器 setupVideoObserver(); // 延迟查找视频以处理慢加载 log(`等待${config.initialWaitTime / 1000}秒后开始查找视频...`); setTimeout(() => { // 立即尝试查找视频 videoElement = findVideoElement(); if (videoElement) { log('初始化时找到视频元素'); applyInitialSpeed(); return; } // 如果没有立即找到,定期尝试 log('未立即找到视频,启动定期查找'); let findVideoInterval = setInterval(() => { retryCount++; videoElement = findVideoElement(); if (videoElement) { log(`在第${retryCount}次尝试时找到视频元素`); clearInterval(findVideoInterval); applyInitialSpeed(); return; } if (retryCount >= config.maxRetries) { log('达到最大尝试次数,仍未找到视频元素'); clearInterval(findVideoInterval); // 最后尝试一次并启动长期监控 setTimeout(() => { videoElement = findVideoElement(); if (videoElement) { log('最后一次常规尝试找到视频元素'); applyInitialSpeed(); } else { log('常规查找未找到视频,切换到长期监控'); startLongTermMonitoring(); } }, 10000); } }, config.retryInterval); }, config.initialWaitTime); // 显示通知脚本已加载 showNotification('视频倍速控制已加载 [慢视频优化版]'); } // 等待页面加载完成并初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => setTimeout(initialize, 1000)); } else { setTimeout(initialize, 1000); // 给页面更多时间加载JS } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址