summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-06 08:46:31 +0300
committerPaul Buetow <paul@buetow.org>2026-05-06 08:46:31 +0300
commit728ef661402c57e44e1035c311341cf8c5f11922 (patch)
treea651e2284de9cfc78ad305acbda87294990c2642 /web
parent30ad6a0416c2a1fec58ddc6f4af727af9518f73c (diff)
Add PgUp/PgDn 10% seek in fullscreen mode
Diffstat (limited to 'web')
-rw-r--r--web/index.html1
-rw-r--r--web/js/app.js7
-rw-r--r--web/js/detach.js13
-rw-r--r--web/js/keyboard.js10
-rw-r--r--web/js/player.js16
5 files changed, 46 insertions, 1 deletions
diff --git a/web/index.html b/web/index.html
index d35a390..6e85ec6 100644
--- a/web/index.html
+++ b/web/index.html
@@ -135,6 +135,7 @@
<div class="help-row"><span><kbd>i</kbd></span><span>Show / hide media info</span></div>
<div class="help-row"><span><kbd>n</kbd></span><span>Open notes</span></div>
<div class="help-row"><span><kbd>f</kbd></span><span>Toggle fullscreen</span></div>
+ <div class="help-row"><span><kbd>PgUp</kbd> <kbd>PgDn</kbd></span><span>Seek +10% / −10% (fullscreen)</span></div>
<div class="help-row"><span><kbd>C</kbd></span><span>Minimize player</span></div>
<div class="help-row"><span><kbd>d</kbd></span><span>Detach / attach player</span></div>
<div class="help-row"><span><kbd>D</kbd></span><span>Download selected media</span></div>
diff --git a/web/js/app.js b/web/js/app.js
index 78dd16d..99ae0f3 100644
--- a/web/js/app.js
+++ b/web/js/app.js
@@ -1,7 +1,7 @@
import { API } from './api.js';
import { initKeyboard } from './keyboard.js';
import { initSelection, clearSelection, select, selectByElement, next, prev, currentIndex, currentElement, navUp, navDown, navLeft, navRight } from './selection.js';
-import { initPlayer, togglePlay, toggleFullscreen, toggleMinimize, toggleDetach, exitFullscreenIfNeeded, toggleCrop, currentMediaId, currentMediaInfo, hasLoadedMedia, isPlaybackActive, seekRelative, selectAndPlay, zoomIn as playerZoomIn, zoomOut as playerZoomOut, toggleSlideshow as playerToggleSlideshow, isImageMode as playerIsImageMode } from './player.js';
+import { initPlayer, togglePlay, toggleFullscreen, toggleMinimize, toggleDetach, exitFullscreenIfNeeded, toggleCrop, currentMediaId, currentMediaInfo, hasLoadedMedia, isPlaybackActive, seekRelative, seekPercent, selectAndPlay, zoomIn as playerZoomIn, zoomOut as playerZoomOut, toggleSlideshow as playerToggleSlideshow, isImageMode as playerIsImageMode } from './player.js';
import { initSearch, focusSearch, trigger as triggerSearch, parseQuery, showSearchHelp } from './search.js';
import { initShuffle, toggle as toggleShuffle, isOn as isShuffle } from './shuffle.js';
import { initThemes } from './themes.js';
@@ -104,6 +104,11 @@ async function initApp() {
navRight: () => navRight(),
seekBackward: (e) => seekByKeyboard(-1, e.repeat),
seekForward: (e) => seekByKeyboard(1, e.repeat),
+ seekPercent: (e, percent) => {
+ if (!hasLoadedMedia()) return false;
+ if (!document.fullscreenElement) return false;
+ return seekPercent(percent);
+ },
nextSet: () => setSetByDelta(1),
prevSet: () => setSetByDelta(-1),
isSidebarOpen: () => document.getElementById('sidebar')?.classList.contains('open'),
diff --git a/web/js/detach.js b/web/js/detach.js
index 3b48122..b1dfd87 100644
--- a/web/js/detach.js
+++ b/web/js/detach.js
@@ -145,6 +145,8 @@ function handleCommand(payload) {
el.play().catch(() => {});
} else if (action === 'seek-relative') {
seekRelative(Number(payload.seconds || 0));
+ } else if (action === 'seek-percent') {
+ seekPercent(Number(payload.percent || 0));
}
}
@@ -164,6 +166,17 @@ function seekRelative(seconds) {
sendState();
}
+function seekPercent(percent) {
+ const el = mediaElement();
+ if (!currentMedia || !el || !isFinite(percent)) return;
+ const dur = (el.duration && isFinite(el.duration) && el.duration > 0) ? el.duration : (currentMedia?.duration || 0);
+ if (!dur) return;
+ const upper = el.duration && isFinite(el.duration) ? el.duration : Infinity;
+ el.currentTime = Math.max(0, Math.min(upper, (el.currentTime || 0) + dur * percent));
+ lastKnownPosition = el.currentTime || 0;
+ sendState();
+}
+
function toggleFullscreen() {
const player = document.getElementById('player');
if (!player) return;
diff --git a/web/js/keyboard.js b/web/js/keyboard.js
index b7769b7..76c3a9d 100644
--- a/web/js/keyboard.js
+++ b/web/js/keyboard.js
@@ -206,6 +206,16 @@ export function initKeyboard(handlers) {
}
handlers.navRight?.(e);
break;
+ case 'PageUp':
+ if (handlers.seekPercent?.(e, -0.10)) {
+ e.preventDefault();
+ }
+ break;
+ case 'PageDown':
+ if (handlers.seekPercent?.(e, 0.10)) {
+ e.preventDefault();
+ }
+ break;
case 'Enter': handlers.enter?.(e); break;
case 'n':
handlers.notes?.(e);
diff --git a/web/js/player.js b/web/js/player.js
index e179856..f9a96da 100644
--- a/web/js/player.js
+++ b/web/js/player.js
@@ -275,6 +275,22 @@ export function hasLoadedMedia() {
return !!currentMedia;
}
+export function seekPercent(deltaPercent) {
+ const dp = Number(deltaPercent);
+ if (!currentMedia || !isFinite(dp)) return false;
+ if (isDetached()) {
+ postToDetach({ type: 'detach-command', action: 'seek-percent', percent: dp });
+ return true;
+ }
+ const m = currentMediaElement();
+ if (!m) return false;
+ const dur = effectiveDuration();
+ if (!dur) return false;
+ const upper = m.duration && isFinite(m.duration) ? m.duration : dur;
+ m.currentTime = Math.max(0, Math.min(upper, (m.currentTime || 0) + dur * dp));
+ return true;
+}
+
export function seekRelative(seconds) {
const amount = Number(seconds);
if (!currentMedia || !isFinite(amount)) return false;