Sync with native YouTube volume & avoid duplicate seeking
当前为
// ==UserScript==
// @name YouTube Arrow Key Video Control (Improved Sync)
// @name:ru Улучшенное управление YouTube через стрелки
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Sync with native YouTube volume & avoid duplicate seeking
// @description:ru Правильное управление видео YouTube через стрелки на клавиатуре.
// @author Boss of this gym
// @match *://www.youtube.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const VOLUME_STEP = 10; // 10%
const SEEK_STEP = 5; // seconds
function getVideoElement() {
return document.querySelector('video');
}
function getVolumePercent(video) {
return Math.round(video.volume * 100);
}
function setVolumeFromPercent(video, percent) {
const clamped = Math.min(Math.max(percent, 0), 100);
video.volume = clamped / 100;
showOverlay(`🔊 ${clamped}%`);
}
function seekVideo(video, delta) {
const newTime = Math.min(Math.max(video.currentTime + delta, 0), video.duration);
video.currentTime = newTime;
showOverlay(`${delta > 0 ? '⏩' : '⏪'} ${Math.abs(delta)}s`);
}
function isInputElementFocused() {
const active = document.activeElement;
return active && (['INPUT', 'TEXTAREA'].includes(active.tagName) || active.isContentEditable);
}
function createOverlay() {
const overlay = document.createElement('div');
overlay.id = 'yt-ctrl-overlay';
overlay.style.cssText = `
position: fixed;
top: 20%;
left: 50%;
transform: translateX(-50%);
padding: 12px 24px;
background: rgba(0, 0, 0, 0.7);
color: #fff;
font-size: 20px;
border-radius: 8px;
z-index: 9999;
display: none;
`;
document.body.appendChild(overlay);
return overlay;
}
const overlay = createOverlay();
let overlayTimeout = null;
function showOverlay(text) {
overlay.textContent = text;
overlay.style.display = 'block';
clearTimeout(overlayTimeout);
overlayTimeout = setTimeout(() => {
overlay.style.display = 'none';
}, 800);
}
window.addEventListener('keydown', function(event) {
if (isInputElementFocused() || event.altKey) return;
const video = getVideoElement();
if (!video) return;
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
event.preventDefault();
event.stopImmediatePropagation();
if (document.activeElement !== video) {
video.setAttribute('tabindex', '-1');
video.focus();
}
switch (event.key) {
case 'ArrowUp': {
const vol = Math.floor(video.volume * 100);
setVolumeFromPercent(video, vol + VOLUME_STEP);
break;
}
case 'ArrowDown': {
const vol = Math.floor(video.volume * 100);
setVolumeFromPercent(video, vol - VOLUME_STEP);
break;
}
case 'ArrowRight':
seekVideo(video, SEEK_STEP);
break;
case 'ArrowLeft':
seekVideo(video, -SEEK_STEP);
break;
}
}
}, true); // Use capture to intercept before YouTube
})();