Sleazy Fork is available in English.

Jable video control bar tweak

UI tweak for video control bar

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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