|
// ==UserScript== |
|
// @name Advanced Video Controller |
|
// @namespace http://tampermonkey.net/ |
|
// @version 1.0 |
|
// @description Control video playback with keyboard shortcuts (works with iframes) |
|
// @match *://*/* |
|
// @grant GM_setClipboard |
|
// @run-at document-end |
|
// ==/UserScript== |
|
|
|
(function() { |
|
'use strict'; |
|
|
|
const SPEED_STEP = 0.25; |
|
const VOLUME_STEP = 0.1; |
|
const SEEK_STEP = 10; |
|
|
|
function getAllVideos() { |
|
const videos = Array.from(document.querySelectorAll('video')); |
|
|
|
const iframes = document.querySelectorAll('iframe'); |
|
iframes.forEach(iframe => { |
|
try { |
|
const iframeVideos = iframe.contentDocument?.querySelectorAll('video'); |
|
if (iframeVideos) { |
|
videos.push(...Array.from(iframeVideos)); |
|
} |
|
} catch (e) {} |
|
}); |
|
|
|
return videos; |
|
} |
|
|
|
function getActiveVideo() { |
|
const videos = getAllVideos(); |
|
const playing = videos.find(v => !v.paused); |
|
return playing || videos[0]; |
|
} |
|
|
|
function showNotification(message) { |
|
const existing = document.getElementById('video-ctrl-notification'); |
|
if (existing) existing.remove(); |
|
|
|
const notification = document.createElement('div'); |
|
notification.id = 'video-ctrl-notification'; |
|
notification.textContent = message; |
|
notification.style.cssText = ` |
|
position: fixed; |
|
top: 20px; |
|
right: 20px; |
|
background: rgba(0, 0, 0, 0.8); |
|
color: white; |
|
padding: 12px 20px; |
|
border-radius: 6px; |
|
font-family: monospace; |
|
font-size: 14px; |
|
z-index: 999999; |
|
pointer-events: none; |
|
`; |
|
document.body.appendChild(notification); |
|
setTimeout(() => notification.remove(), 1500); |
|
} |
|
|
|
async function captureFrame(video) { |
|
const canvas = document.createElement('canvas'); |
|
canvas.width = video.videoWidth; |
|
canvas.height = video.videoHeight; |
|
const ctx = canvas.getContext('2d'); |
|
ctx.drawImage(video, 0, 0); |
|
|
|
try { |
|
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); |
|
const item = new ClipboardItem({'image/png': blob}); |
|
await navigator.clipboard.write([item]); |
|
showNotification('Frame copied to clipboard'); |
|
} catch (e) { |
|
canvas.toBlob(blob => { |
|
const url = URL.createObjectURL(blob); |
|
GM_setClipboard(url); |
|
showNotification('Frame URL copied (fallback)'); |
|
}); |
|
} |
|
} |
|
|
|
document.addEventListener('keydown', (e) => { |
|
if (!e.shiftKey) return; |
|
|
|
const activeElement = document.activeElement; |
|
const isInputActive = activeElement && ( |
|
activeElement.tagName === 'INPUT' || |
|
activeElement.tagName === 'TEXTAREA' || |
|
activeElement.isContentEditable |
|
); |
|
|
|
if (isInputActive) return; |
|
|
|
const video = getActiveVideo(); |
|
if (!video) return; |
|
|
|
let handled = true; |
|
|
|
switch(e.key.toLowerCase()) { |
|
case 's': |
|
video.playbackRate = Math.min(video.playbackRate + SPEED_STEP, 16); |
|
showNotification(`Speed: ${video.playbackRate.toFixed(2)}x`); |
|
break; |
|
|
|
case 'a': |
|
video.playbackRate = Math.max(video.playbackRate - SPEED_STEP, 0.25); |
|
showNotification(`Speed: ${video.playbackRate.toFixed(2)}x`); |
|
break; |
|
|
|
case 'w': |
|
video.volume = Math.min(video.volume + VOLUME_STEP, 1); |
|
showNotification(`Volume: ${Math.round(video.volume * 100)}%`); |
|
break; |
|
|
|
case 'd': |
|
video.muted = false; |
|
showNotification('Unmuted'); |
|
break; |
|
|
|
case 'z': |
|
video.currentTime = Math.min(video.currentTime + SEEK_STEP, video.duration); |
|
showNotification(`+${SEEK_STEP}s`); |
|
break; |
|
|
|
case 'x': |
|
video.currentTime = Math.min(video.currentTime - SEEK_STEP, video.duration); |
|
showNotification(`-${SEEK_STEP}s`); |
|
break; |
|
|
|
case 'q': |
|
captureFrame(video); |
|
break; |
|
|
|
case 'r': |
|
video.playbackRate = 1.0; |
|
showNotification('Speed: 1.0x (reset)'); |
|
break; |
|
|
|
case 'm': |
|
video.muted = !video.muted; |
|
showNotification(video.muted ? 'Muted' : 'Unmuted'); |
|
break; |
|
|
|
case 'f': |
|
if (video.requestFullscreen) { |
|
video.requestFullscreen(); |
|
} else if (document.fullscreenElement) { |
|
document.exitFullscreen(); |
|
} |
|
break; |
|
|
|
case 'p': |
|
if (video.paused) { |
|
video.play(); |
|
showNotification('Playing'); |
|
} else { |
|
video.pause(); |
|
showNotification('Paused'); |
|
} |
|
break; |
|
|
|
case 'c': |
|
showNotification(`${SEEK_STEP}s`); |
|
break; |
|
|
|
default: |
|
handled = false; |
|
} |
|
|
|
if (handled) { |
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
} |
|
}, true); |
|
|
|
})(); |