UI tweak for video control bar
// ==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();
})();