YouTube Arrow Key Video Control (Improved Sync)

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
})();