您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
支持方向键和自定义快捷键的视频控制面板,包含全屏模式下的迷你控制面板
// ==UserScript== // @name 小鹅通增强版视频控制面板(修复版) // @namespace http://tampermonkey.net/ // @version 2.1 // @description 支持方向键和自定义快捷键的视频控制面板,包含全屏模式下的迷你控制面板 // @author Claude // @match *://*.xiaoeknow.com/* // @match *://*.xe-live.com/* // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function () { 'use strict'; // 元素ID常量 const PANEL_ID = 'xe-video-control-panel'; const TIME_DISPLAY_ID = 'xe-time-display'; const SPEED_INDICATOR_ID = 'xe-speed-indicator'; const MINI_BUTTON_ID = 'xe-mini-control-button'; // 配置参数(支持本地存储) let config = { seekTime: GM_getValue('seekTime', 5), // 步进时间 speedUp: GM_getValue('speedUp', 1.5), // 加速倍率 longPressTime: 300, // 长按判定时间 seekBackward: GM_getValue('seekBackward', 'arrowleft'), // 默认后退键 seekForward: GM_getValue('seekForward', 'arrowright') // 默认前进键 }; // 状态管理 let state = { keyPressTime: {}, // 记录按键按下时间 keyStates: {}, // 记录按键状态 originalPlaybackRate: 1, // 原始播放速度 timeDisplay: null, // 时间显示元素 speedIndicator: null, // 速度显示元素 isControlPanelMinimized: false, // 控制面板是否最小化 isFullscreen: false, // 是否全屏 miniControlButton: null, // 迷你控制按钮 currentKeyCapturing: null, // 当前正在捕获的快捷键类型 monitorTimer: null, // 监控定时器 recoveryAttempts: 0, // 恢复尝试次数 }; const MAX_RECOVERY_ATTEMPTS = 5; // 最大恢复尝试次数 // 工具函数:移除指定ID的元素 function removeElementById(id) { const element = document.getElementById(id); if (element) { element.remove(); } } // 工具函数:检查面板是否已存在 function isPanelExists() { return !!document.getElementById(PANEL_ID); } // 创建视觉反馈指示器 function createIndicators() { // 先清理已存在的指示器 removeElementById(TIME_DISPLAY_ID); removeElementById(SPEED_INDICATOR_ID); // 创建时间显示器 state.timeDisplay = document.createElement('div'); state.timeDisplay.id = TIME_DISPLAY_ID; state.timeDisplay.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.7); color: white; padding: 12px 24px; border-radius: 24px; font-size: 16px; font-weight: bold; pointer-events: none; opacity: 0; transition: opacity 0.3s; z-index: 999999; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); `; document.body.appendChild(state.timeDisplay); // 创建速度显示器 state.speedIndicator = document.createElement('div'); state.speedIndicator.id = SPEED_INDICATOR_ID; state.speedIndicator.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: rgba(33, 150, 243, 0.9); color: white; padding: 10px 20px; border-radius: 20px; font-size: 14px; font-weight: bold; pointer-events: none; opacity: 0; transition: opacity 0.3s; z-index: 999999; box-shadow: 0 2px 8px rgba(0,0,0,0.2); `; document.body.appendChild(state.speedIndicator); } // 显示提示信息 function showIndicator(element, text, duration = 800) { if (!element) return; element.textContent = text; element.style.opacity = '1'; // 使用防抖处理 clearTimeout(element.fadeTimeout); element.fadeTimeout = setTimeout(() => { element.style.opacity = '0'; }, duration); } // 执行视频步进 function performSeek(direction) { const video = document.querySelector('video'); if (!video) { console.log('未找到视频元素,尝试恢复...'); attemptRecovery(); return; } try { const newTime = direction === 'forward' ? Math.min(video.duration, video.currentTime + config.seekTime) : Math.max(0, video.currentTime - config.seekTime); video.currentTime = newTime; showIndicator(state.timeDisplay, direction === 'forward' ? `⏩ ${config.seekTime}秒` : `⏪ ${config.seekTime}秒` ); } catch (error) { console.error('步进操作失败:', error); attemptRecovery(); } } // 控制播放速度 function controlPlaybackRate(shouldSpeedUp) { const video = document.querySelector('video'); if (!video) { attemptRecovery(); return; } try { if (shouldSpeedUp && video.playbackRate !== config.speedUp) { state.originalPlaybackRate = video.playbackRate; video.playbackRate = config.speedUp; showIndicator(state.speedIndicator, `⚡ ${config.speedUp}x`, 1000); } else if (!shouldSpeedUp && video.playbackRate !== state.originalPlaybackRate) { video.playbackRate = state.originalPlaybackRate; showIndicator(state.speedIndicator, `► ${state.originalPlaybackRate}x`, 1000); } } catch (error) { console.error('播放速度控制失败:', error); attemptRecovery(); } } // 创建迷你控制按钮 function createMiniControlButton() { // 先清理已存在的迷你按钮 removeElementById(MINI_BUTTON_ID); const button = document.createElement('div'); button.innerHTML = ` <div id="${MINI_BUTTON_ID}" style=" position: fixed; top: 20px; right: 20px; background: rgba(33, 33, 33, 0.7); color: white; padding: 8px; border-radius: 50%; font-size: 16px; cursor: pointer; z-index: 999999; opacity: 0; transition: opacity 0.3s; "⚙️</div> `; document.body.appendChild(button); const miniButton = button.querySelector(`#${MINI_BUTTON_ID}`); miniButton.addEventListener('mouseenter', () => { miniButton.style.opacity = '1'; }); miniButton.addEventListener('mouseleave', () => { if (!state.isControlPanelMinimized) { miniButton.style.opacity = '0'; } }); miniButton.addEventListener('click', () => { const panel = document.getElementById(PANEL_ID); if (panel) { panel.style.display = 'block'; state.isControlPanelMinimized = false; } }); return miniButton; } // 创建快捷键配置界面 function createKeyConfigPanel() { const keyConfig = document.createElement('div'); keyConfig.innerHTML = ` <div style="margin-top: 12px; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 12px;"> <div style="margin-bottom: 8px;">快捷键设置</div> <div style="display: grid; grid-template-columns: auto 1fr; gap: 8px; align-items: center;"> <span>后退键:</span> <button id="backward-key" class="hotkey-button" style=" padding: 4px 8px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; color: white; cursor: pointer; ">${config.seekBackward === 'arrowleft' ? '←' : config.seekBackward.toUpperCase()}</button> <span>前进键:</span> <button id="forward-key" class="hotkey-button" style=" padding: 4px 8px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; color: white; cursor: pointer; ">${config.seekForward === 'arrowright' ? '→' : config.seekForward.toUpperCase()}</button> </div> <div style="margin-top: 8px; font-size: 11px; color: #aaa;"> 点击按钮后按下新的按键来更改快捷键 </div> </div> `; return keyConfig; } // 设置快捷键配置功能 function setupHotkeyConfiguration() { const backwardBtn = document.getElementById('backward-key'); const forwardBtn = document.getElementById('forward-key'); if (!backwardBtn || !forwardBtn) return; function startKeyCapture(button, keyType) { button.textContent = '请按键...'; state.currentKeyCapturing = keyType; button.style.background = 'rgba(33, 150, 243, 0.3)'; } function stopKeyCapture(button, key) { button.textContent = key === 'arrowleft' ? '←' : key === 'arrowright' ? '→' : key.toUpperCase(); state.currentKeyCapturing = null; button.style.background = 'rgba(255,255,255,0.1)'; } backwardBtn.addEventListener('click', () => { startKeyCapture(backwardBtn, 'backward'); }); forwardBtn.addEventListener('click', () => { startKeyCapture(forwardBtn, 'forward'); }); // 只添加一次全局按键监听 if (!window.hotkeyListenerAdded) { document.addEventListener('keydown', (e) => { if (state.currentKeyCapturing) { e.preventDefault(); const key = e.key.toLowerCase(); if (state.currentKeyCapturing === 'backward') { if (key !== config.seekForward) { config.seekBackward = key; GM_setValue('seekBackward', key); stopKeyCapture(backwardBtn, key); state.keyStates = {}; state.keyPressTime = {}; } } else { if (key !== config.seekBackward) { config.seekForward = key; GM_setValue('seekForward', key); stopKeyCapture(forwardBtn, key); state.keyStates = {}; state.keyPressTime = {}; } } } }); window.hotkeyListenerAdded = true; } } // 创建主控制面板 function createControlPanel() { // 检查是否已存在面板 if (isPanelExists()) { return document.getElementById(PANEL_ID); } const panel = document.createElement('div'); panel.innerHTML = ` <div id="${PANEL_ID}" style=" position: fixed; top: 100px; right: 20px; background: rgba(33, 33, 33, 0.9); border-radius: 8px; padding: 12px; color: white; font-size: 12px; z-index: 9999999; min-width: 200px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); transition: transform 0.3s; "> <div id="panel-header" style=" display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; cursor: move; user-select: none; "> <span style="font-weight: bold;">视频控制面板</span> <div style="display: flex; gap: 8px;"> <button id="minimize-panel" style=" background: none; border: none; color: white; cursor: pointer; padding: 0 4px; ">_</button> <button id="close-panel" style=" background: none; border: none; color: white; cursor: pointer; padding: 0 4px; ">×</button> </div> </div> <div id="panel-content"> <div style="margin-bottom: 12px;"> <div style="margin-bottom: 4px;">步进时间 (秒)</div> <div style="display: flex; align-items: center; gap: 8px;"> <input type="range" id="seek-time" min="1" max="30" step="1" value="${config.seekTime}" style="flex: 1;" > <span id="seek-time-value">${config.seekTime}s</span> </div> </div> <div style="margin-bottom: 12px;"> <div style="margin-bottom: 4px;">长按加速倍率</div> <div style="display: flex; align-items: center; gap: 8px;"> <input type="range" id="speed-up" min="1" max="3" step="0.25" value="${config.speedUp}" style="flex: 1;" > <span id="speed-up-value">${config.speedUp}x</span> </div> </div> </div> </div> `; document.body.appendChild(panel); // 添加快捷键配置界面 const panelContent = panel.querySelector('#panel-content'); panelContent.appendChild(createKeyConfigPanel()); // 设置控制面板的基本功能 setupControlPanel(panel); // 设置快捷键配置功能 setupHotkeyConfiguration(); return panel; } // 设置控制面板的基本功能 function setupControlPanel(panel) { const controlPanel = panel.querySelector(`#${PANEL_ID}`); const minimizeBtn = panel.querySelector('#minimize-panel'); const closeBtn = panel.querySelector('#close-panel'); const seekTimeSlider = panel.querySelector('#seek-time'); const speedUpSlider = panel.querySelector('#speed-up'); const seekTimeValue = panel.querySelector('#seek-time-value'); const speedUpValue = panel.querySelector('#speed-up-value'); const panelHeader = panel.querySelector('#panel-header'); // 拖动功能实现 let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; panelHeader.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); function dragStart(e) { initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; if (e.target === panelHeader) { isDragging = true; } } function drag(e) { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; controlPanel.style.transform = `translate(${currentX}px, ${currentY}px)`; } } function dragEnd() { initialX = currentX; initialY = currentY; isDragging = false; } // 最小化功能 minimizeBtn.addEventListener('click', () => { state.isControlPanelMinimized = !state.isControlPanelMinimized; panel.querySelector('#panel-content').style.display = state.isControlPanelMinimized ? 'none' : 'block'; minimizeBtn.textContent = state.isControlPanelMinimized ? '□' : '_'; controlPanel.style.minWidth = state.isControlPanelMinimized ? 'auto' : '200px'; // 如果在全屏模式下,显示/隐藏迷你按钮 if (state.miniControlButton) { state.miniControlButton.style.opacity = state.isControlPanelMinimized ? '1' : '0'; } }); // 关闭功能 closeBtn.addEventListener('click', () => { controlPanel.style.display = 'none'; if (state.miniControlButton) { state.miniControlButton.style.opacity = '1'; } }); // 滑块控制 seekTimeSlider.addEventListener('input', function () { config.seekTime = parseInt(this.value); seekTimeValue.textContent = `${config.seekTime}s`; GM_setValue('seekTime', config.seekTime); }); speedUpSlider.addEventListener('input', function () { config.speedUp = parseFloat(this.value); speedUpValue.textContent = `${config.speedUp}x`; GM_setValue('speedUp', config.speedUp); }); } // 视频状态监控 function startVideoMonitoring() { let lastPlaybackTime = 0; function checkVideoState() { const video = document.querySelector('video'); if (!video) { console.log('视频元素丢失,尝试恢复...'); attemptRecovery(); return; } // 检查视频是否卡住 if (!video.paused && lastPlaybackTime === video.currentTime) { console.log('视频可能卡住了,尝试恢复...'); attemptRecovery(); } lastPlaybackTime = video.currentTime; } // 清理已存在的监控 if (state.monitorTimer) { clearInterval(state.monitorTimer); } // 启动新的监控 state.monitorTimer = setInterval(checkVideoState, 1000); return state.monitorTimer; } // 恢复机制 function attemptRecovery() { if (state.recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) { console.log('达到最大恢复尝试次数'); return; } state.recoveryAttempts++; console.log(`尝试恢复控制 (${state.recoveryAttempts}/${MAX_RECOVERY_ATTEMPTS})`); // 清理现有状态 cleanup(); // 延迟重新初始化 setTimeout(() => { initializeControls(); if (document.querySelector('video')) { state.recoveryAttempts = 0; // 如果成功找到视频元素,重置计数器 } }, 1000 * state.recoveryAttempts); } // 清理函数 function cleanup() { if (state.monitorTimer) { clearInterval(state.monitorTimer); state.monitorTimer = null; } state.keyStates = {}; state.keyPressTime = {}; } // 监听全屏变化 function setupFullscreenTracking() { document.addEventListener('fullscreenchange', handleFullscreenChange); document.addEventListener('webkitfullscreenchange', handleFullscreenChange); document.addEventListener('mozfullscreenchange', handleFullscreenChange); document.addEventListener('MSFullscreenChange', handleFullscreenChange); } // 处理全屏变化 function handleFullscreenChange() { state.isFullscreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; if (state.isFullscreen) { if (!state.miniControlButton) { state.miniControlButton = createMiniControlButton(); } state.miniControlButton.style.display = 'block'; } else { if (state.miniControlButton) { state.miniControlButton.style.display = 'none'; } } } // 处理按键按下事件 function handleKeyDown(e) { const key = e.key.toLowerCase(); if (key !== config.seekBackward && key !== config.seekForward) return; if (state.currentKeyCapturing) return; e.preventDefault(); e.stopPropagation(); if (!state.keyStates[key]) { state.keyStates[key] = true; state.keyPressTime[key] = Date.now(); } } // 处理按键释放事件 function handleKeyUp(e) { const key = e.key.toLowerCase(); if (key !== config.seekBackward && key !== config.seekForward) return; if (state.currentKeyCapturing) return; e.preventDefault(); e.stopPropagation(); const pressDuration = Date.now() - state.keyPressTime[key]; state.keyStates[key] = false; // 如果不是长按,执行步进 if (pressDuration < config.longPressTime) { performSeek(key === config.seekForward ? 'forward' : 'backward'); } // 检查是否需要恢复正常速度 if (!state.keyStates[config.seekBackward] && !state.keyStates[config.seekForward]) { controlPlaybackRate(false); } } // 检查长按状态 function checkLongPress() { Object.keys(state.keyStates).forEach(key => { if (state.keyStates[key] && (Date.now() - state.keyPressTime[key] >= config.longPressTime)) { controlPlaybackRate(true); } }); } // 初始化所有控制功能 function initializeControls() { // 检查是否已经初始化 if (isPanelExists()) { console.log('控制面板已存在,跳过初始化'); return; } createIndicators(); createControlPanel(); setupFullscreenTracking(); // 初始化按键状态 state.keyStates[config.seekBackward] = false; state.keyStates[config.seekForward] = false; // 添加键盘事件监听 document.removeEventListener('keydown', handleKeyDown, true); document.removeEventListener('keyup', handleKeyUp, true); document.addEventListener('keydown', handleKeyDown, true); document.addEventListener('keyup', handleKeyUp, true); // 启动视频监控 startVideoMonitoring(); // 启动长按检测 setInterval(checkLongPress, 100); // 监听进度条拖动 const video = document.querySelector('video'); if (video) { video.addEventListener('seeking', () => { console.log('检测到进度条拖动,重新初始化控制...'); setTimeout(attemptRecovery, 100); }); video.addEventListener('error', (e) => { console.error('视频错误:', e); attemptRecovery(); }); } console.log('视频控制已初始化:\n- 左右方向键:后退/前进\n- 长按方向键:加速播放\n- 支持全屏模式下的迷你控制面板'); } // 等待视频元素加载 function waitForVideo() { if (isPanelExists()) { console.log('控制面板已存在,无需重新等待'); return; } const observer = new MutationObserver((mutations, observer) => { if (document.querySelector('video')) { initializeControls(); observer.disconnect(); } }); observer.observe(document.documentElement, { childList: true, subtree: true }); } // 页面可见性变化监听 document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { console.log('页面重新可见,检查控制状态...'); if (!isPanelExists()) { attemptRecovery(); } } }); // 网络状态变化监听 window.addEventListener('online', () => { console.log('网络恢复,检查控制状态...'); if (!isPanelExists()) { attemptRecovery(); } }); // 初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', waitForVideo); } else { waitForVideo(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址