// ==UserScript== // @name 黄金左右键 // @description 按住"→"键倍速播放,按住"←"键减速播放,松开恢复原来的倍速,轻松追剧,看视频更灵活,还能快进/跳过大部分网站的广告!~ 支持用户单独配置倍速和秒数,并可根据根域名启用或禁用脚本 // @icon https://image.suysker.xyz/i/2023/10/09/artworks-QOnSW1HR08BDMoe9-GJTeew-t500x500.webp // @namespace http://tampermonkey.net/ // @version 1.0.3 // @author Suysker // @match http://*/* // @match https://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @homepage https://github.com/Suysker/Golden-Left-Right // @supportURL https://github.com/Suysker/Golden-Left-Right // @downloadURL none // ==/UserScript== (function () { 'use strict'; const DEFAULT_RATE = 2; // 默认倍速 const DEFAULT_TIME = 5; // 默认秒数 const DEFAULT_RL_TIME = 180; // 左右同时按下秒数 const DOMAIN_BLOCK_LIST_KEY = "blockedDomains"; // 存储禁用的根域名列表的键名 let keyboardEventsRegistered = false; // 确保键盘事件只注册一次 let debug = false; // 控制日志的输出,正式环境关闭 let cachedVideos = []; // 缓存视频列表 const loadSetting = async (key, defaultValue) => { const value = await GM_getValue(key, defaultValue); return value !== undefined ? value : defaultValue; }; const saveSetting = async (key, value) => { await GM_setValue(key, value); }; const log = debug ? console.log.bind(console) : () => {}; const getRootDomain = () => { const parts = location.hostname.split('.'); return parts.slice(-2).join('.'); // 获取根域名 (例如 example.com) }; const isDomainBlocked = async () => { const blockedDomains = await loadSetting(DOMAIN_BLOCK_LIST_KEY, []); const currentDomain = getRootDomain(); return blockedDomains.includes(currentDomain); }; const toggleCurrentDomain = async () => { const blockedDomains = await loadSetting(DOMAIN_BLOCK_LIST_KEY, []); const currentDomain = getRootDomain(); const index = blockedDomains.indexOf(currentDomain); let isNowBlocked = false; if (index === -1) { blockedDomains.push(currentDomain); await saveSetting(DOMAIN_BLOCK_LIST_KEY, blockedDomains); alert(`已禁用黄金左右键脚本在此网站 (${currentDomain})`); isNowBlocked = true; } else { blockedDomains.splice(index, 1); await saveSetting(DOMAIN_BLOCK_LIST_KEY, blockedDomains); alert(`已启用黄金左右键脚本在此网站 (${currentDomain})`); isNowBlocked = false; } keyboardEventsRegistered = false; // 确保键盘事件重新注册 handleKeyboardEvents(!isNowBlocked); // 根据新状态立即启用/禁用键盘事件 }; const state = { playbackRate: DEFAULT_RATE, // 播放倍速 changeTime: DEFAULT_TIME, // 快进/回退秒数 pageVideo: null, lastPlayedVideo: null, // 记录上一个播放过的视频(通过 play 事件更新) originalPlaybackRate: 1, // 存储原来的播放速度 rightKeyDownCount: 0, // 追踪右键按下次数 leftKeyDownCount: 0 // 追踪左键按下次数 }; const isVideoVisible = (video) => { const rect = video.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); }; const isVideoPlaying = (video) => { return video && !video.paused && video.currentTime > 0; }; const addPlayEventListeners = (video) => { video.addEventListener('play', () => { state.lastPlayedVideo = video; // 仅在视频播放时更新 log('更新 lastPlayedVideo: 当前播放的视频', video); }); }; // 初始化视频监听器并缓存视频 const initVideoListeners = (videos) => { cachedVideos.push(...videos); // 缓存新视频 videos.forEach(addPlayEventListeners); // 为每个视频添加监听 }; // 获取当前页面上所有的视频并进行缓存 const cacheAllVideos = () => { const allVideos = Array.from(document.getElementsByTagName('video')); initVideoListeners(allVideos); }; const getOptimalPageVideo = async () => { // 检查 lastPlayedVideo 是否存在且可见,不检查是否正在播放 if (state.lastPlayedVideo && isVideoVisible(state.lastPlayedVideo)) { log('lastPlayedVideo 存在且可见'); return state.lastPlayedVideo; } // 如果 lastPlayedVideo 不存在或不可见,检查是否有其他视频正在播放 const allVideos = Array.from(document.getElementsByTagName('video')); const playingVideo = allVideos.find(isVideoPlaying); if (playingVideo) { log('找到其他正在播放的视频:', playingVideo); return playingVideo; } // 如果没有合适的视频,返回 null 并记录状态 log('未找到合适的视频'); return null; }; const checkPageVideo = async () => { state.pageVideo = await getOptimalPageVideo(); if (!state.pageVideo) { log('未找到符合条件的视频'); return false; } return true; }; const isInputFocused = () => { const activeElement = document.activeElement; const inputTypes = ['input', 'textarea', 'select', 'button']; const isContentEditable = activeElement && activeElement.isContentEditable; const isInputElement = activeElement && inputTypes.includes(activeElement.tagName.toLowerCase()); return isContentEditable || isInputElement; }; const handleKeyboardEvents = async (enable) => { if (enable && !keyboardEventsRegistered) { document.body.addEventListener('keydown', onRightKeyDown, { capture: true }); document.body.addEventListener('keydown', onLeftKeyDown, { capture: true }); document.body.parentElement.addEventListener('keyup', onRightKeyUp, { capture: true }); document.body.parentElement.addEventListener('keyup', onLeftKeyUp, { capture: true }); keyboardEventsRegistered = true; } else if (!enable && keyboardEventsRegistered) { document.body.removeEventListener('keydown', onRightKeyDown, { capture: true }); document.body.removeEventListener('keydown', onLeftKeyDown, { capture: true }); document.body.parentElement.removeEventListener('keyup', onRightKeyUp, { capture: true }); document.body.parentElement.removeEventListener('keyup', onLeftKeyUp, { capture: true }); keyboardEventsRegistered = false; } }; const checkBothKeysPressed = async () => { if (state.rightKeyDownCount == 1 && state.leftKeyDownCount == 1 && await checkPageVideo()) { state.pageVideo.currentTime += DEFAULT_RL_TIME; log(`同时按下左右键,快进 ${DEFAULT_RL_TIME} 秒`); return true; // 表示已处理 } return false; }; const onRightKeyDown = async (e) => { if (e.keyCode !== 39 || isInputFocused()) return; e.preventDefault(); e.stopPropagation(); state.rightKeyDownCount++; // 检查是否同时按下左右键 if (await checkBothKeysPressed()) return; if (state.rightKeyDownCount == 2 && await checkPageVideo() && isVideoPlaying(state.pageVideo)) { state.originalPlaybackRate = state.pageVideo.playbackRate; state.pageVideo.playbackRate = state.playbackRate; log('加速播放中, 倍速: ' + state.playbackRate); } }; const onRightKeyUp = async (e) => { if (e.keyCode !== 39 || isInputFocused()) return; e.preventDefault(); e.stopPropagation(); if (state.rightKeyDownCount == 1 && await checkPageVideo()) { state.pageVideo.currentTime += state.changeTime; log('前进 ' + state.changeTime + ' 秒'); } // 恢复原来的倍速 if (state.pageVideo && state.pageVideo.playbackRate !== state.originalPlaybackRate) { state.pageVideo.playbackRate = state.originalPlaybackRate; log('恢复原来的倍速: ' + state.originalPlaybackRate); } state.rightKeyDownCount = 0 }; const onLeftKeyDown = async (e) => { if (e.keyCode !== 37 || isInputFocused()) return; e.preventDefault(); e.stopPropagation(); state.leftKeyDownCount++; // 检查是否同时按下左右键 if (await checkBothKeysPressed()) return; if (state.leftKeyDownCount == 2 && await checkPageVideo() && isVideoPlaying(state.pageVideo)) { state.originalPlaybackRate = state.pageVideo.playbackRate; state.pageVideo.playbackRate = 1 / state.playbackRate; log('减速播放中, 倍速: ' + state.pageVideo.playbackRate); } }; const onLeftKeyUp = async (e) => { if (e.keyCode !== 37 || isInputFocused()) return; e.preventDefault(); e.stopPropagation(); if (state.leftKeyDownCount == 1 && await checkPageVideo()) { state.pageVideo.currentTime -= state.changeTime; log('回退 ' + state.changeTime + ' 秒'); } // 恢复原来的倍速 if (state.pageVideo && state.pageVideo.playbackRate !== state.originalPlaybackRate) { state.pageVideo.playbackRate = state.originalPlaybackRate; log('恢复原来的倍速: ' + state.originalPlaybackRate); } state.leftKeyDownCount = 0 }; const configurePlaybackRate = async () => { const newRate = prompt('请输入新的播放倍速 (当前: ' + state.playbackRate + ')', state.playbackRate); if (newRate !== null) { const parsedRate = parseFloat(newRate); if (!isNaN(parsedRate) && parsedRate > 0) { state.playbackRate = parsedRate; await saveSetting('playbackRate', state.playbackRate); log('播放倍速设置为: ' + state.playbackRate); } } }; const configureChangeTime = async () => { const newTime = prompt('请输入新的快进/回退秒数 (当前: ' + state.changeTime + ')', state.changeTime); if (newTime !== null) { const parsedTime = parseFloat(newTime); if (!isNaN(parsedTime) && parsedTime > 0) { state.changeTime = parsedTime; await saveSetting('changeTime', state.changeTime); log('快进/回退秒数设置为: ' + state.changeTime); } } }; const removeFromCache = (removedVideos) => { cachedVideos = cachedVideos.filter(video => !removedVideos.includes(video)); }; const init = async () => { const isBlocked = await isDomainBlocked(); handleKeyboardEvents(!isBlocked); GM_registerMenuCommand('启用/禁用黄金左右键', toggleCurrentDomain); GM_registerMenuCommand('设置播放倍速', configurePlaybackRate); GM_registerMenuCommand('设置快进/回退秒数', configureChangeTime); cacheAllVideos(); const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { const addedVideos = Array.from(mutation.addedNodes).filter(node => node.tagName === 'VIDEO'); if (addedVideos.length > 0) { initVideoListeners(addedVideos); // 仅初始化新增视频 } const removedVideos = Array.from(mutation.removedNodes).filter(node => node.tagName === 'VIDEO'); if (removedVideos.length > 0) { removeFromCache(removedVideos); // 移除销毁的视频元素 } }); }); observer.observe(document.body, { childList: true, subtree: true }); }; init(); })();