您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add custom keyboard shortcuts for YouTube navigation with UI feedback and improved controls
当前为
// ==UserScript== // @name ytkb // @namespace http://violentmonkey.net/ // @version 0.3 // @description Add custom keyboard shortcuts for YouTube navigation with UI feedback and improved controls // @match https://www.youtube.com/* // @grant none // @license MIT // ==/UserScript== (() => { const SEEK_TIME = 5; // Seconds to seek forward/backward const VOLUME_CHANGE = 5; // Percentage to change volume const MAX_SPEED = 3.0; // Maximum playback speed const MIN_SPEED = 0.1; // Minimum playback speed const state = { currentTime: 0, volume: 0, muted: false, playbackRate: 1.0, duration: 0, }; // Helper functions const formatTime = (seconds) => { const date = new Date(seconds * 1000); const parts = [ date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), ]; return parts .map(part => part.toString().padStart(2, '0')) .filter((part, index) => part !== '00' || index > 0) .join(':'); }; const getVideo = () => document.querySelector('video'); // UI element creation and management const createUIElement = () => { const uiElement = document.createElement('div'); uiElement.id = 'ytkb-ui'; uiElement.style.cssText = ` position: absolute; top: 20px; left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.7); color: white; padding: 10px; border-radius: 5px; font-size: 16px; z-index: 9999; display: none; text-shadow: 1px 1px 2px rgba(0,0,0,0.7); font-family: monospace; `; const playerContainer = document.getElementById('movie_player') || document.body; playerContainer.appendChild(uiElement); return uiElement; }; const createCurrentStateElement = () => { const currentStateElement = document.createElement('div'); currentStateElement.id = 'ytkb-current-state'; currentStateElement.style.cssText = ` padding: 8px 12px; background-color: rgba(255, 255, 255, 0.05); backdrop-filter: blur(10px); margin-top: 12px; color: white; border-radius: 5px; font-size: 14px; font-family: monospace; width: fit-content; `; return currentStateElement; }; let uiElement; let stateUpdateInterval; const showUIFeedback = (message) => { if (!uiElement) { uiElement = createUIElement(); } uiElement.textContent = message; uiElement.style.display = 'block'; setTimeout(() => { uiElement.style.display = 'none'; }, 1500); }; // Video state management const updateVideoState = () => { const video = getVideo(); if (!video) return; Object.assign(state, { currentTime: video.currentTime, volume: video.volume, muted: video.muted, playbackRate: video.playbackRate, duration: video.duration, }); const stateText = `${formatTime(state.currentTime)} / ${formatTime(state.duration)} | Volume: ${Math.round(state.volume * 100)}% | Muted: ${state.muted ? 'On' : 'Off'} | Speed: ${state.playbackRate.toFixed(2)}x`; document.getElementById("ytkb-current-state").textContent = stateText; }; const startVideoStateUpdate = () => { if (!stateUpdateInterval) { stateUpdateInterval = setInterval(updateVideoState, 1000); } }; const stopVideoStateUpdate = () => { clearInterval(stateUpdateInterval); stateUpdateInterval = null; }; // Keyboard event handling const handleKeydown = (e) => { if (e.target.tagName.toLowerCase() === 'input' || e.target.tagName.toLowerCase() === 'textarea') { return; } const video = getVideo(); if (!video) return; let handled = true; let feedbackMessage = ''; const ctrl = e.ctrlKey; switch (e.key.toLowerCase()) { case 'h': case 'arrowleft': { // Rewind const seekBackward = ctrl ? SEEK_TIME * 2 : SEEK_TIME; video.currentTime = Math.max(0, video.currentTime - seekBackward); feedbackMessage = `Rewound ${seekBackward}s`; state.currentTime = video.currentTime; break; } case 'j': case 'arrowdown': { // Volume down const volumeDownChange = ctrl ? VOLUME_CHANGE * 2 : VOLUME_CHANGE; video.volume = Math.max(0, video.volume - (volumeDownChange / 100)); feedbackMessage = `Volume: ${Math.round(video.volume * 100)}%`; state.volume = video.volume; break; } case 'k': case 'arrowup': { // Volume up const volumeUpChange = ctrl ? VOLUME_CHANGE * 2 : VOLUME_CHANGE; video.volume = Math.min(1, video.volume + (volumeUpChange / 100)); feedbackMessage = `Volume: ${Math.round(video.volume * 100)}%`; state.volume = video.volume; break; } case 'l': case 'arrowright': { // Forward const seekForward = ctrl ? SEEK_TIME * 2 : SEEK_TIME; video.currentTime = Math.min(video.duration, video.currentTime + seekForward); feedbackMessage = `Forward ${seekForward}s`; state.currentTime = video.currentTime; break; } case 'm': // Mute video.muted = !video.muted; feedbackMessage = `Muted: ${video.muted ? 'On' : 'Off'}`; state.muted = video.muted; break; case ',': // Decrease speed video.playbackRate = Math.max(MIN_SPEED, video.playbackRate - 0.25); feedbackMessage = `Speed: ${video.playbackRate.toFixed(2)}x`; state.playbackRate = video.playbackRate; break; case '.': // Increase speed video.playbackRate = Math.min(MAX_SPEED, video.playbackRate + 0.25); feedbackMessage = `Speed: ${video.playbackRate.toFixed(2)}x`; state.playbackRate = video.playbackRate; break; case 'z': // Reset speed video.playbackRate = 1.0; feedbackMessage = `Speed: ${video.playbackRate.toFixed(2)}x`; state.playbackRate = video.playbackRate; break; case 'i': // Toggle Picture-in-Picture if (document.pictureInPictureElement) { document.exitPictureInPicture(); feedbackMessage = 'Picture-in-Picture: Off'; } else if (document.pictureInPictureEnabled) { video.requestPictureInPicture(); feedbackMessage = 'Picture-in-Picture: On'; } break; case 'f': // Toggle fullscreen document.querySelector(".ytp-fullscreen-button").dispatchEvent(new MouseEvent('click')); break; // case ' ': // Play/Pause // if (video.paused) { // video.play(); // feedbackMessage = 'Playing'; // } else { // video.pause(); // feedbackMessage = 'Paused'; // } // break; default: handled = false; } if (handled) { e.preventDefault(); e.stopPropagation(); showUIFeedback(feedbackMessage); updateVideoState(); } }; // Event listeners document.addEventListener('keydown', handleKeydown, { capture: true, }); window.addEventListener('load', startVideoStateUpdate); window.addEventListener('unload', stopVideoStateUpdate); let stateElementCreated = false; const interval = setInterval(() => { if (stateElementCreated) { clearInterval(interval); return; } const topRow = document.getElementById("below"); if (topRow) { topRow.insertBefore(createCurrentStateElement(), topRow.firstChild); stateElementCreated = true; } }, 500); })();