add new v0ck feature

This commit is contained in:
2026-07-14 19:12:15 +02:00
parent 4a491f404c
commit 965573a80f
6 changed files with 132 additions and 1 deletions

View File

@@ -527,9 +527,14 @@ class v0ck {
document.addEventListener('fullscreenchange', toggleFullScreenClasses); document.addEventListener('fullscreenchange', toggleFullScreenClasses);
toggleFullScreenClasses(); // Check initial state (important for transitions) toggleFullScreenClasses(); // Check initial state (important for transitions)
let keyHoldTimer = null;
let isHoldingKey = false;
let heldKey = null;
let reverseInterval = null;
function handleKeyDown(e) { function handleKeyDown(e) {
if (!document.body.contains(video)) { if (!document.body.contains(video)) {
document.removeEventListener('keydown', handleKeyDown); cleanupKeyboardListeners();
return; return;
} }
@@ -556,8 +561,129 @@ class v0ck {
handleVolumeButton(newVol); handleVolumeButton(newVol);
showHUD(newVol); showHUD(newVol);
} }
if (e.key === "." || e.key === ",") {
if (e.ctrlKey || e.metaKey || e.altKey) return;
e.preventDefault();
if (!Number.isFinite(video.currentTime)) return;
const duration = Number.isFinite(video.duration) ? video.duration : video.currentTime;
const direction = e.key === "." ? 1 : -1;
if (heldKey && heldKey !== e.key) return;
const isCurrentHold = heldKey === e.key;
if (video.paused || isCurrentHold) {
if (!e.repeat) {
heldKey = e.key;
isHoldingKey = false;
if (keyHoldTimer) clearTimeout(keyHoldTimer);
keyHoldTimer = setTimeout(() => {
isHoldingKey = true;
if (heldKey === ".") {
video.play();
} else if (heldKey === ",") {
if (reverseInterval) clearInterval(reverseInterval);
reverseInterval = setInterval(() => {
if (video.currentTime > 0) {
video.currentTime = Math.max(0, video.currentTime - 0.04);
} else {
clearInterval(reverseInterval);
reverseInterval = null;
}
}, 40);
}
}, 200);
} else {
if (keyHoldTimer && !isHoldingKey) {
clearTimeout(keyHoldTimer);
keyHoldTimer = null;
isHoldingKey = true;
if (heldKey === ".") {
video.play();
} else if (heldKey === ",") {
if (reverseInterval) clearInterval(reverseInterval);
reverseInterval = setInterval(() => {
if (video.currentTime > 0) {
video.currentTime = Math.max(0, video.currentTime - 0.04);
} else {
clearInterval(reverseInterval);
reverseInterval = null;
}
}, 40);
}
}
}
} else {
video.currentTime = Math.max(0, Math.min(duration, video.currentTime + direction * 5));
}
showControlsAndReset();
}
} }
function handleKeyUp(e) {
if (!document.body.contains(video)) {
cleanupKeyboardListeners();
return;
}
if (e.key === "." || e.key === ",") {
if (heldKey === e.key) {
if (keyHoldTimer) {
clearTimeout(keyHoldTimer);
keyHoldTimer = null;
}
if (isHoldingKey) {
if (heldKey === ".") {
video.pause();
} else if (heldKey === ",") {
if (reverseInterval) {
clearInterval(reverseInterval);
reverseInterval = null;
}
}
} else {
if (Number.isFinite(video.currentTime)) {
const duration = Number.isFinite(video.duration) ? video.duration : video.currentTime;
const direction = heldKey === "." ? 1 : -1;
const frameTime = 1 / 30;
video.currentTime = Math.max(0, Math.min(duration, video.currentTime + direction * frameTime));
}
}
heldKey = null;
isHoldingKey = false;
showControlsAndReset();
}
}
}
function handleBlur() {
if (!document.body.contains(video)) {
cleanupKeyboardListeners();
return;
}
if (keyHoldTimer) clearTimeout(keyHoldTimer);
if (reverseInterval) clearInterval(reverseInterval);
keyHoldTimer = null;
reverseInterval = null;
if (isHoldingKey) {
if (heldKey === ".") {
video.pause();
}
}
heldKey = null;
isHoldingKey = false;
}
function cleanupKeyboardListeners() {
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('keyup', handleKeyUp);
window.removeEventListener('blur', handleBlur);
if (keyHoldTimer) clearTimeout(keyHoldTimer);
if (reverseInterval) clearInterval(reverseInterval);
}
document.addEventListener('keydown', handleKeyDown); document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
window.addEventListener('blur', handleBlur);
video.volume = _volume = volumeSlider.value = +(localStorage.getItem('volume') ?? defaultVolume); video.volume = _volume = volumeSlider.value = +(localStorage.getItem('volume') ?? defaultVolume);
handleVolumeButton(video.volume); handleVolumeButton(video.volume);

View File

@@ -290,6 +290,7 @@
"next_prev": "Weiter/Zurück", "next_prev": "Weiter/Zurück",
"volume": "Lautstärke", "volume": "Lautstärke",
"scroll_nav": "Weiter/Zurück", "scroll_nav": "Weiter/Zurück",
"frame_skip": "Einzelbildsteuerung (pausiert) / 5s Sprung",
"show_help": "Hilfe anzeigen" "show_help": "Hilfe anzeigen"
}, },
"notifications": { "notifications": {

View File

@@ -290,6 +290,7 @@
"next_prev": "next/prev", "next_prev": "next/prev",
"volume": "volume", "volume": "volume",
"scroll_nav": "next/prev", "scroll_nav": "next/prev",
"frame_skip": "frame step (paused) / 5s skip",
"show_help": "show help" "show_help": "show help"
}, },
"notifications": { "notifications": {

View File

@@ -290,6 +290,7 @@
"next_prev": "volgende/vorige", "next_prev": "volgende/vorige",
"volume": "volume", "volume": "volume",
"scroll_nav": "next/prev", "scroll_nav": "next/prev",
"frame_skip": "frame-stap (gepauzeerd) / 5s overslaan",
"show_help": "help tonen" "show_help": "help tonen"
}, },
"notifications": { "notifications": {

View File

@@ -288,6 +288,7 @@
"next_prev": "nächster/vorheriger", "next_prev": "nächster/vorheriger",
"volume": "Lautstärke", "volume": "Lautstärke",
"scroll_nav": "nächster/vorheriger", "scroll_nav": "nächster/vorheriger",
"frame_skip": "Einzelbildrücken (pausiert) / 5s Hüpfen",
"show_help": "Hilfe anzeigen" "show_help": "Hilfe anzeigen"
}, },
"notifications": { "notifications": {

View File

@@ -319,6 +319,7 @@
<div class="shortcut-item"><span><kbd></kbd> <kbd></kbd></span><span>{{ t('shortcuts.next_prev') }}</span></div> <div class="shortcut-item"><span><kbd></kbd> <kbd></kbd></span><span>{{ t('shortcuts.next_prev') }}</span></div>
<div class="shortcut-item"><span><kbd></kbd> <kbd></kbd></span><span>{{ t('shortcuts.volume') }}</span></div> <div class="shortcut-item"><span><kbd></kbd> <kbd></kbd></span><span>{{ t('shortcuts.volume') }}</span></div>
<div class="shortcut-item"><span><kbd>Scroll</kbd></span><span>{{ t('shortcuts.scroll_nav') }}</span></div> <div class="shortcut-item"><span><kbd>Scroll</kbd></span><span>{{ t('shortcuts.scroll_nav') }}</span></div>
<div class="shortcut-item"><span><kbd>,</kbd> <kbd>.</kbd></span><span>{{ t('shortcuts.frame_skip') }}</span></div>
<div class="shortcut-item"><span><kbd>?</kbd></span><span>{{ t('shortcuts.show_help') }}</span></div> <div class="shortcut-item"><span><kbd>?</kbd></span><span>{{ t('shortcuts.show_help') }}</span></div>
</div> </div>
</div> </div>