// ==UserScript==
// @name Universal Control Video Player - RafPlayer (Fixed)
// @namespace http://tampermonkey.net/
// @version 4.2
// @description Advanced video controller for any website — brightness, playback speed, volume, on-screen shortcut help (H), and fullscreen (F).
// @author Rafin Saleh
// @license MIT
// @match *://*/*
// @grant none
// ==/UserScript==
/*
Modified for universal use across all websites,
and fullscreen (F) functionality has been re-enabled.
*/
(function () {
'use strict';
// --- Display Box ---
const display = document.createElement('div');
Object.assign(display.style, {
position: 'fixed',
bottom: '10px',
left: '50%',
transform: 'translateX(-50%)',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
color: 'white',
padding: '10px 14px',
borderRadius: '6px',
zIndex: '999999',
fontSize: '17px',
fontFamily: 'sans-serif',
display: 'none',
transition: 'opacity 0.3s ease'
});
document.body.appendChild(display);
function showDisplay(msg, duration = 1500) {
display.textContent = msg;
display.style.display = 'block';
display.style.opacity = '1';
clearTimeout(display.hideTimer);
display.hideTimer = setTimeout(() => {
display.style.opacity = '0';
setTimeout(() => (display.style.display = 'none'), 300);
}, duration);
}
// --- Help Overlay ---
const helpOverlay = document.createElement('div');
Object.assign(helpOverlay.style, {
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: 'rgba(0, 0, 0, 0.85)',
color: 'white',
padding: '20px',
borderRadius: '10px',
zIndex: '1000000',
fontSize: '15px',
fontFamily: 'monospace',
display: 'none',
whiteSpace: 'pre-line',
maxWidth: '90%',
lineHeight: '1.5em'
});
helpOverlay.textContent =
`🎥 UDV Universal Controller — Shortcuts (v2.0)
Brightness:
9 = Increase, 8 = Decrease
Playback Speed:
Enter = Toggle 1x / 1.75x
. / , = Fine Adjust (±0.25x), lowest speed is now 1x
/ = Toggle 2.25x / 10x
A = 3x Speed
Z = Show Current Speed
Volume:
I = Increase, K = Decrease (±10%)
; = Mute / Unmute
Seek & Frames:
← / → = Seek ±5s (DISABLED to allow native player controls)
[ / ] = Seek ±60s
- / = = Frame Back / Forward (when paused)
Misc:
F = Toggle Fullscreen
H = Show / Hide This Help
`;
document.body.appendChild(helpOverlay);
let helpVisible = false;
function toggleHelp() {
helpVisible = !helpVisible;
helpOverlay.style.display = helpVisible ? 'block' : 'none';
}
// --- States ---
let brightness = 1;
let speed = 1.75;
let volume = 1;
let muted = false;
// --- Universal Video Selection ---
// Selects ALL video elements on the page.
const videos = () => document.querySelectorAll('video');
// Selects the video that is likely the main one being controlled
function getActiveVideo() {
// Return the last video found, which is often the main or most recently loaded one.
const vids = videos();
return vids[vids.length - 1];
}
// --- Core Functions ---
function setSpeed(newSpeed) {
videos().forEach(v => (v.playbackRate = newSpeed));
}
function setVolume(newVolume) {
newVolume = Math.min(1, Math.max(0, newVolume));
videos().forEach(v => (v.volume = newVolume));
volume = newVolume;
showDisplay(`Volume: ${(volume * 100).toFixed(0)}%`);
}
function toggleMute() {
muted = !muted;
videos().forEach(v => (v.muted = muted));
showDisplay(muted ? 'Muted' : 'Unmuted');
}
function adjustBrightness(change) {
brightness = Math.min(2, Math.max(0.2, brightness + change));
// Apply filter to a higher level element like a container around the player for better effect on some sites
// For universal use, applying to document.documentElement is a common method.
document.documentElement.style.filter = `brightness(${brightness})`;
showDisplay(`Brightness: ${(brightness * 100).toFixed(0)}%`);
}
function seek(seconds) {
const v = getActiveVideo();
if (v) v.currentTime = Math.min(v.duration, Math.max(0, v.currentTime + seconds));
}
function frameStep(forward = true) {
const v = getActiveVideo();
if (v && v.paused) {
// Standard frame rate approximation (e.g., 30 FPS)
v.currentTime += forward ? 1 / 30 : -1 / 30;
showDisplay(`Frame ${forward ? '→' : '←'}`);
}
}
// --- Fullscreen (Re-enabled) ---
function toggleFullscreen() {
// Fullscreen function is RE-ENABLED by removing the "return;"
const v = getActiveVideo();
if (!v) return;
// Check if the document is currently in fullscreen mode
if (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement) {
// Exit fullscreen
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) { // Firefox
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) { // Chrome, Safari and Opera
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) { // IE/Edge
document.msExitFullscreen();
}
showDisplay('Exited Fullscreen');
} else {
// Attempt to enter fullscreen on the video element's container (e.g., the YouTube player) or the video itself
// On YouTube, the video is inside a 'ytd-player' or similar container.
const element = v.closest('#player-container') || v.parentElement || v;
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) { // Firefox
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) { // Chrome, Safari and Opera
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) { // IE/Edge
element.msRequestFullscreen();
}
showDisplay('Entered Fullscreen');
}
}
// --- Key Controls ---
window.addEventListener('keydown', e => {
const tagName = document.activeElement.tagName;
// 🚨 CRITICAL FIX: Only run controls if we are NOT typing in an input field OR interacting with the main YouTube player.
// YouTube player controls live inside an element with role="button" or similar, or have a specific class.
// We can generally check if the active element is a text input, or a player control element.
// A simple check is to avoid triggering controls when an input field is active.
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(tagName) || document.activeElement.isContentEditable) return;
// On YouTube, the active element when a user is interacting with the player controls
// can be a button, a control bar element, or the player itself.
// If the focused element is not the body, we assume the user is interacting with some UI element.
// For general use, sticking to the input check is best, but for YouTube-specific issues,
// we might want to be more careful. A common approach is to check for the player container's class.
// A quick and dirty fix for YouTube is to check if the target is *within* a known player element.
if (e.target.closest('#movie_player, .html5-video-player')) {
// If the user is pressing a key while focused on the YouTube player itself,
// we usually want YouTube's native controls to take precedence (e.g., space for play/pause).
// We only proceed for the controls we explicitly want to *override* or add.
}
// Ensure a video is present and active
if (!getActiveVideo() && e.key.toLowerCase() !== 'h') return;
// Prevent browser default actions for keys we'll use.
// Removed 'ArrowRight', 'ArrowLeft' to let native YouTube controls handle seeking.
if ([' ', 'ArrowUp', 'ArrowDown', 'f', 'h', 'Enter', '[', ']', '-', '='].includes(e.key)) {
e.preventDefault();
}
// Convert to lowercase for case-insensitive check
const key = e.key.toLowerCase();
switch (key) {
case ';': toggleMute(); break;
case '9': adjustBrightness(0.1); break;
case '8': adjustBrightness(-0.1); break;
case 'a': speed = 3; setSpeed(speed); showDisplay(`Speed: 3x`); break;
case 'z': showDisplay(`Speed: ${speed.toFixed(2)}x`); break;
case 'enter': speed = speed === 1.75 ? 1 : 1.75; setSpeed(speed); showDisplay(`Speed: ${speed.toFixed(2)}x`); break;
case '/': speed = speed === 10 ? 2.25 : 10; setSpeed(speed); showDisplay(`Speed: ${speed.toFixed(2)}x`); break;
case '[': seek(-60); break;
case ']': seek(60); break;
case '-': frameStep(false); break;
case '=': frameStep(true); break;
case ',':
// MODIFICATION: Set the minimum speed to 1x
speed = Math.max(1, speed - 0.25);
setSpeed(speed);
showDisplay(`Speed: ${speed.toFixed(2)}x`);
break;
case '.': speed += 0.25; setSpeed(speed); showDisplay(`Speed: ${speed.toFixed(2)}x`); break;
case 'i': setVolume(volume + 0.1); break;
case 'k': setVolume(volume - 0.1); break;
case 'f': toggleFullscreen(); break; // RE-ENABLED
case 'h': toggleHelp(); break;
// 'ArrowRight' and 'ArrowLeft' handlers for seeking are REMOVED to allow YouTube's native controls to work.
}
});
// --- Initialization ---
// Apply initial settings to any video elements that appear
const initializeVideo = (v) => {
if (!v.dataset.udvInit) {
v.dataset.udvInit = '1';
v.playbackRate = speed;
v.volume = volume;
showDisplay('Universal Controller Ready ✅');
}
};
// Initialize all existing videos
videos().forEach(initializeVideo);
// Observer to handle videos loaded dynamically (like on YouTube or other single-page apps)
const observer = new MutationObserver((mutationsList, observer) => {
videos().forEach(initializeVideo);
});
// Monitor the entire document body for changes
observer.observe(document.body, { childList: true, subtree: true });
})();