Jable video control bar tweak

UI tweak for video control bar

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Jable video control bar tweak
// @namespace    http://tampermonkey.net
// @version      7.0.2
// @description  UI tweak for video control bar
// @author       php
// @match        *://jable.tv/videos/*
// @grant        none
// @run-at       document-end
// @license       MIT
// ==/UserScript==

(function() {
    'use strict';

    let hideTimeout = null;

    // Add CSS for Custom Skip Buttons AND Vertical Volume Bar
    const style = document.createElement('style');
    style.innerHTML = `
        /* Force player to stay visible during interactions */
        .plyr.keep-showing .plyr__controls {
            opacity: 1 !important;
            visibility: visible !important;
            pointer-events: auto !important;
            transform: translateY(0) !important;
        }

        /* NEW: Skip buttons styles moved from JS to CSS for cleaner code */
        .custom-skip-btn {
            width: 32px !important;
            height: 32px !important;
            min-width: 32px !important;
            padding: 0 !important;
            flex-shrink: 0 !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
        }

        /* Strip residual padding/flex from the container so it tightly wraps the button */
        .plyr__controls__item.plyr__volume {
            position: relative !important;
            overflow: visible !important;
            flex: 0 0 auto !important;
            min-width: auto !important;
            width: auto !important;
            padding: 0 !important;
            margin: 0 !important;
            display: flex !important;
            justify-content: center !important;
        }

        /* Take the volume range out of normal document flow */
        .plyr__controls__item.plyr__volume input[data-plyr="volume"] {
            position: absolute !important;
            bottom: 84px !important;
            left: calc(50% - 60px) !important;

            width: 120px !important;
            height: 32px !important;
            margin: 0 !important;

            transform: rotate(-90deg) !important;
            transform-origin: center center !important;


            opacity: 0;
            visibility: hidden;
            pointer-events: none;
            transition: opacity 0.25s ease, visibility 0.25s ease !important;
            z-index: 100 !important;

            flex: none !important;
            max-width: none !important;
        }

        /* Show state applied via JavaScript */
        .plyr__controls__item.plyr__volume.show-volume input[data-plyr="volume"] {
            opacity: 1 !important;
            visibility: visible !important;
            pointer-events: auto !important;
        }
    `;
    document.head.appendChild(style);

    function injectUI() {
        const controlBar = document.querySelector('.plyr__controls');
        const video = document.querySelector('video');
        const plyrContainer = document.querySelector('.plyr');

        // --- Feature 1: Skip Buttons ---
        if (controlBar && video && !document.querySelector('.custom-skip-btn')) {
            const createBtn = (seconds, label) => {
                const btn = document.createElement('button');
                btn.type = 'button';
                btn.className = 'plyr__controls__item plyr__control custom-skip-btn';
                btn.innerHTML = label;

                const handleClick = (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    video.currentTime += seconds;
                    if (plyrContainer) {
                        if (hideTimeout) clearTimeout(hideTimeout);
                        plyrContainer.classList.add('keep-showing');
                        hideTimeout = setTimeout(() => plyrContainer.classList.remove('keep-showing'), 5000);
                    }
                };

                btn.addEventListener('click', handleClick);
                btn.addEventListener('touchstart', handleClick, { passive: false });

                return btn;
            };

            const rwBtn = createBtn(-10, '<svg aria-hidden="true" focusable="false"><use xlink:href="#plyr-rewind"></use></svg><span class="plyr__sr-only">Rewind 10s</span>');
            const ffBtn = createBtn(10, '<svg aria-hidden="true" focusable="false"><use xlink:href="#plyr-fast-forward"></use></svg><span class="plyr__sr-only">Forward 10s</span>');
            const playBtn = controlBar.querySelector('button[data-plyr="play"]');

            if (playBtn) {
                playBtn.before(rwBtn);
                playBtn.after(ffBtn);
            }
        }

        // --- Feature 2: Vertical Volume Bar Logic ---
        const volumeContainer = document.querySelector('.plyr__volume');

        if (volumeContainer && !volumeContainer.classList.contains('custom-vol-enabled')) {
            volumeContainer.classList.add('custom-vol-enabled');
            const volumeBtn = volumeContainer.querySelector('button[data-plyr="mute"]');
            const isHoverable = () => window.matchMedia('(hover: hover)').matches;

            // 1. Desktop: Hover Interactions
            volumeContainer.addEventListener('mouseenter', () => isHoverable() && volumeContainer.classList.add('show-volume'));
            volumeContainer.addEventListener('mouseleave', () => isHoverable() && volumeContainer.classList.remove('show-volume'));

            // 2. Mobile: Click/Touch Interactions
            if (volumeBtn) {
                volumeBtn.addEventListener('click', () => {
                    if (!isHoverable()) volumeContainer.classList.add('show-volume');
                });
            }

            // 3. Mobile: Close when clicking elsewhere
            document.addEventListener('click', (e) => {
                if (volumeContainer.classList.contains('show-volume')) {
                    // Close if clicked completely outside the control bar OR clicked another interactive element in the control bar
                    if (!controlBar.contains(e.target) || (!volumeContainer.contains(e.target) && e.target.closest('button, input'))) {
                        volumeContainer.classList.remove('show-volume');
                    }
                }
            });
        }
    }

    const observer = new MutationObserver(injectUI);
    observer.observe(document.body, { childList: true, subtree: true });

    injectUI();
})();