// ==UserScript== // @name 视频临时倍速+B站字幕开关记忆 // @namespace http://tampermonkey.net/ // @version 2.4 // @description 视频播放增强:1. 长按左键临时加速 2. B站字幕开关记忆 3. 支持更多视频播放器 // @author Alonewinds // @match *://*/* // @exclude *://*/iframe/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @run-at document-start // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/521942/%E8%A7%86%E9%A2%91%E4%B8%B4%E6%97%B6%E5%80%8D%E9%80%9F%2BB%E7%AB%99%E5%AD%97%E5%B9%95%E5%BC%80%E5%85%B3%E8%AE%B0%E5%BF%86.user.js // @updateURL https://update.greasyfork.icu/scripts/521942/%E8%A7%86%E9%A2%91%E4%B8%B4%E6%97%B6%E5%80%8D%E9%80%9F%2BB%E7%AB%99%E5%AD%97%E5%B9%95%E5%BC%80%E5%85%B3%E8%AE%B0%E5%BF%86.meta.js // ==/UserScript== (function() { 'use strict'; if (window.location.hostname.includes('bilibili.com') && window.self !== window.top && window.location.hostname !== 'player.bilibili.com') { return; } // 默认配置 const config = { speedRate: GM_getValue('speedRate', 2.0), minPressTime: 200, selectors: { 'www.bilibili.com': '.bpx-player-video-area', 'www.youtube.com': '.html5-video-player', // 添加 YouTube 选择器 'default': '.video-controls, .progress-bar, [role="slider"]' }, // 调试模式开关 debug: false }; // 状态变量 let pressStartTime = 0; let originalSpeed = 1.0; let isPressed = false; let activeVideo = null; let isLongPress = false; let preventNextClick = false; // B站字幕相关变量 let currentVideoId = ''; let want_open = false; let subtitleCheckTimer = null; let debounceTimer = null; let lastSubtitleState = null; let lastSubtitleCheckTime = 0; // 添加动画帧ID跟踪 let animationFrameId = null; let urlObserver = null; // 调试日志函数 function debugLog(...args) { if (config.debug) { console.log(...args); } } // 添加开始检测函数 function startPressDetection() { if (!animationFrameId) { function checkPress() { handlePressDetection(); animationFrameId = requestAnimationFrame(checkPress); } checkPress(); } } // 添加停止检测函数 function stopPressDetection() { if (animationFrameId) { cancelAnimationFrame(animationFrameId); animationFrameId = null; } } GM_registerMenuCommand('设置倍速值', () => { if (window.self !== window.top && window.location.hostname !== 'player.bilibili.com') return; const newSpeed = prompt('请输入新的倍速值(建议范围:1.1-4):', config.speedRate); if (newSpeed && !isNaN(newSpeed)) { config.speedRate = parseFloat(newSpeed); GM_setValue('speedRate', config.speedRate); const indicator = document.querySelector('.speed-indicator'); if (indicator) { indicator.innerHTML = `当前加速 ${config.speedRate}x ▶▶`; } } }); // ================ 倍速控制功能 ================ function findVideoElement(element) { if (!element) return null; if (element instanceof HTMLVideoElement) { return element; } const domain = window.location.hostname; // B站和YouTube使用区域限制 if (domain === 'www.bilibili.com') { const playerArea = document.querySelector('.bpx-player-video-area'); if (!playerArea?.contains(element)) return null; } else if (domain === 'www.youtube.com') { const ytPlayer = element.closest('.html5-video-player'); if (!ytPlayer?.contains(element)) return null; const video = ytPlayer.querySelector('video'); if (video) return video; } const controlSelector = config.selectors.default; if (element.closest(controlSelector)) { return null; } const container = element.closest('*:has(video)'); const video = container?.querySelector('video'); return video && window.getComputedStyle(video).display !== 'none' ? video : null; } function setYouTubeSpeed(video, speed) { if (window.location.hostname === 'www.youtube.com') { const player = video.closest('.html5-video-player'); if (player) { try { // 清理之前的监控器 if (player._speedInterval) { clearInterval(player._speedInterval); player._speedInterval = null; } // 设置速度 video.playbackRate = speed; if (speed !== 1.0) { // 只在加速时监控 // 增加检查间隔到 100ms player._speedInterval = setInterval(() => { if (video.playbackRate !== speed) { video.playbackRate = speed; } }, 100); // 添加超时清理 setTimeout(() => { if (player._speedInterval) { clearInterval(player._speedInterval); player._speedInterval = null; } }, 5000); // 5秒后自动清理 } video.dispatchEvent(new Event('ratechange')); } catch (e) { console.error('设置 YouTube 播放速度失败:', e); } } } else { video.playbackRate = speed; } } // ================ B站字幕功能 ================ function getVideoId() { const match = location.pathname.match(/\/video\/(.*?)\//); return match ? match[1] : ''; } const browserMode = (function() { const mode_data = navigator.userAgent; if (mode_data.includes('Firefox')) { return 'Firefox'; } else if (mode_data.includes('Chrome')) { return 'Chrome'; } else return 'Chrome'; })(); function isAiSubtitle() { let sub = document.querySelector('.bpx-player-ctrl-subtitle-major-inner > .bpx-state-active'); if (sub && sub.innerText.includes("自动")) return true; return false; } function isSubtitleOpen() { const now = Date.now(); if (lastSubtitleState !== null && now - lastSubtitleCheckTime < 500) { return lastSubtitleState; } let max_length = 3; if (browserMode === 'Firefox') max_length = 2; let sub = document.querySelectorAll('svg[preserveAspectRatio="xMidYMid meet"] > defs > filter'); lastSubtitleCheckTime = now; lastSubtitleState = (sub.length !== max_length); return lastSubtitleState; } function isRememberOpen() { return GM_getValue('subtitleOpen', false); } // 开启字幕 - 添加防重复执行 function openSubtitle() { // 清除之前的定时器 if (subtitleCheckTimer) { clearTimeout(subtitleCheckTimer); subtitleCheckTimer = null; } let sub = document.querySelector('[aria-label="字幕"] [class="bpx-common-svg-icon"]'); if (!sub) { sub = document.querySelector('.bpx-player-ctrl-btn.bpx-player-ctrl-subtitle .bpx-player-ctrl-btn-icon'); } if (!sub) { // 如果没找到字幕按钮,延迟重试 subtitleCheckTimer = setTimeout(openSubtitle, 2000); return; } debugLog('尝试开启字幕', isRememberOpen()); const currentState = isSubtitleOpen(); const desiredState = isRememberOpen(); if (currentState !== desiredState && !want_open) { want_open = true; setTimeout(() => { if (sub) sub.click(); want_open = false; debugLog('已点击字幕按钮'); }, 300); } rememberSwitch(); } // 记忆开关状态回调函数 - 添加防抖 function rememberSwitchCallback(e) { if (!e.isTrusted) return; if (debounceTimer) { clearTimeout(debounceTimer); } debounceTimer = setTimeout(() => { const isOpen = isSubtitleOpen(); GM_setValue('subtitleOpen', isOpen); debugLog('储存字幕开关状态', isOpen); debounceTimer = null; }, 300); } // 记忆开关状态 - 优化事件监听 function rememberSwitch() { let sub = document.querySelector('div[aria-label="字幕"]'); if (!sub) { sub = document.querySelector('.bpx-player-ctrl-btn.bpx-player-ctrl-subtitle'); } if (sub && !sub._hasSubtitleListener) { // 使用属性标记已添加监听器,避免重复添加 sub._hasSubtitleListener = true; sub.addEventListener('click', rememberSwitchCallback); debugLog('已添加字幕按钮点击监听'); } } function handleMouseDown(e) { if (e.button !== 0) return; const domain = window.location.hostname; let video = null; let playerArea = null; // B站和YouTube使用严格区域限制 if (domain === 'www.bilibili.com' || domain === 'www.youtube.com') { const selector = config.selectors[domain]; playerArea = document.querySelector(selector); if (!playerArea?.contains(e.target)) return; video = findVideoElement(e.target); } else { video = findVideoElement(e.target); if (video) { playerArea = video.closest('*:has(video)') || video.parentElement; } } if (!video) return; if (video.paused) { hideSpeedIndicator(); return; } pressStartTime = Date.now(); activeVideo = video; originalSpeed = video.playbackRate; isPressed = true; isLongPress = false; preventNextClick = false; // 开始检测 startPressDetection(); } function handleMouseUp(e) { if (!isPressed || !activeVideo) return; const pressDuration = Date.now() - pressStartTime; if (pressDuration >= config.minPressTime) { preventNextClick = true; setYouTubeSpeed(activeVideo, originalSpeed); hideSpeedIndicator(); } isPressed = false; isLongPress = false; activeVideo = null; // 停止检测 stopPressDetection(); } function handlePressDetection() { if (!isPressed || !activeVideo) return; const pressDuration = Date.now() - pressStartTime; if (pressDuration >= config.minPressTime) { // 获取最新的速度值 const currentSpeedRate = GM_getValue('speedRate', config.speedRate); if (activeVideo.playbackRate !== currentSpeedRate) { setYouTubeSpeed(activeVideo, currentSpeedRate); } if (!isLongPress) { isLongPress = true; const playerArea = activeVideo.closest('*:has(video)') || activeVideo.parentElement; let indicator = document.querySelector('.speed-indicator'); if (!indicator) { indicator = document.createElement('div'); indicator.className = 'speed-indicator'; playerArea.appendChild(indicator); } indicator.innerHTML = `当前加速 ${currentSpeedRate}x ▶▶`; indicator.style.display = 'block'; } } } function handleClick(e) { if (preventNextClick) { e.stopPropagation(); preventNextClick = false; return; } } // 优化视频加载处理 - 合并事件监听 function onVideoLoad() { if (window.location.hostname !== 'www.bilibili.com') return; const video = document.querySelector('video'); if (!video) { setTimeout(onVideoLoad, 1000); // 增加延迟,减少检查频率 return; } const newVideoId = getVideoId(); if (newVideoId !== currentVideoId) { currentVideoId = newVideoId; // 重置字幕状态缓存 lastSubtitleState = null; lastSubtitleCheckTime = 0; // 视频ID变化时,初始化字幕功能 setTimeout(openSubtitle, 1500); } if (!video._hasEvents) { video._hasEvents = true; const handleVideoEvent = () => { // 清除之前的定时器 if (subtitleCheckTimer) { clearTimeout(subtitleCheckTimer); } // 延迟检查字幕,避免频繁调用 subtitleCheckTimer = setTimeout(openSubtitle, 1500); }; // 视频加载和播放时,检查字幕状态 video.addEventListener('loadeddata', handleVideoEvent); video.addEventListener('play', handleVideoEvent); } } // ================ 初始化 ================ function initializeEvents() { addSpeedIndicatorStyle(); document.addEventListener('mousedown', handleMouseDown, true); document.addEventListener('mouseup', handleMouseUp, true); document.addEventListener('click', handleClick, true); document.addEventListener('mouseleave', handleMouseUp, true); document.addEventListener('fullscreenchange', hideSpeedIndicator); document.addEventListener('webkitfullscreenchange', hideSpeedIndicator); document.addEventListener('mozfullscreenchange', hideSpeedIndicator); document.addEventListener('MSFullscreenChange', hideSpeedIndicator); document.addEventListener('pause', (e) => { if (e.target instanceof HTMLVideoElement) { hideSpeedIndicator(); } }, true); if (window.location.hostname === 'www.bilibili.com') { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', onVideoLoad); } else { onVideoLoad(); } // 优化URL变化监听 - 添加节流 let lastUrl = location.href; let urlChangeTimer = null; // 清理之前的观察器 if (urlObserver) { urlObserver.disconnect(); urlObserver = null; } urlObserver = new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { // 清除之前的定时器 if (urlChangeTimer) { clearTimeout(urlChangeTimer); } // 延迟处理URL变化,避免频繁触发 urlChangeTimer = setTimeout(() => { lastUrl = url; onVideoLoad(); urlChangeTimer = null; }, 500); } }); urlObserver.observe(document, {subtree: true, childList: true}); // 初始化字幕功能 - 优化检测逻辑 let initAttempts = 0; let initTimer = setInterval(() => { let k = document.querySelector('div[aria-label="宽屏"]'); initAttempts++; if (k || initAttempts > 20) { clearInterval(initTimer); if (k) openSubtitle(); } }, 200); // 增加间隔,减少检查频率 } } function addSpeedIndicatorStyle() { const style = document.createElement('style'); style.textContent = ` .speed-indicator { position: absolute; top: 15%; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.7); color: white; padding: 5px 10px; border-radius: 4px; z-index: 999999; display: none; font-size: 14px; } .speed-arrow { color: #00a1d6; margin-left: 2px; }`; document.head.appendChild(style); } function hideSpeedIndicator() { const indicator = document.querySelector('.speed-indicator'); if (indicator) { indicator.style.display = 'none'; } } // 清理函数 - 在页面卸载时清理资源 function cleanup() { if (animationFrameId) { cancelAnimationFrame(animationFrameId); } if (subtitleCheckTimer) { clearTimeout(subtitleCheckTimer); } if (debounceTimer) { clearTimeout(debounceTimer); } if (urlObserver) { urlObserver.disconnect(); } } // 注册页面卸载事件 window.addEventListener('unload', cleanup); initializeEvents(); })();