summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-06 08:56:12 +0300
committerPaul Buetow <paul@buetow.org>2026-05-06 08:56:12 +0300
commit1d68090f09b791c060dee7222dd2963a74c8e19b (patch)
tree7730bb20d9d03aa798c8817aac54914f8e1d5ba3
parente2958122bfb7304a43b7815d80e0f42c761aa68c (diff)
Add q hotkey to stop playback and close player
-rw-r--r--web/index.html1
-rw-r--r--web/js/app.js3
-rw-r--r--web/js/keyboard.js4
-rw-r--r--web/js/player.js35
4 files changed, 42 insertions, 1 deletions
diff --git a/web/index.html b/web/index.html
index 55cefc9..f44da8e 100644
--- a/web/index.html
+++ b/web/index.html
@@ -141,6 +141,7 @@
<div class="help-row"><span><kbd>D</kbd></span><span>Download selected media</span></div>
<div class="help-row"><span><kbd>T</kbd></span><span>Regenerate thumbnail</span></div>
<div class="help-row"><span><kbd>Esc</kbd></span><span>Exit fullscreen / close</span></div>
+ <div class="help-row"><span><kbd>q</kbd></span><span>Stop playback and close player</span></div>
<div class="help-row"><span><kbd>Backspace</kbd></span><span>Go up one folder</span></div>
<div class="help-row"><span><kbd>r</kbd></span><span>Toggle shuffle</span></div>
<div class="help-row"><span><kbd>s</kbd></span><span>Toggle sets sidebar</span></div>
diff --git a/web/js/app.js b/web/js/app.js
index 2e3caa7..102072b 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, shiftCropPosition, cycleCropPosition, currentMediaId, currentMediaInfo, hasLoadedMedia, isPlaybackActive, seekRelative, seekPercent, selectAndPlay, zoomIn as playerZoomIn, zoomOut as playerZoomOut, toggleSlideshow as playerToggleSlideshow, isImageMode as playerIsImageMode } from './player.js';
+import { initPlayer, togglePlay, toggleFullscreen, toggleMinimize, toggleDetach, exitFullscreenIfNeeded, toggleCrop, shiftCropPosition, cycleCropPosition, currentMediaId, currentMediaInfo, hasLoadedMedia, isPlaybackActive, seekRelative, seekPercent, selectAndPlay, stopAndClose, 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';
@@ -150,6 +150,7 @@ async function initApp() {
notes: openNotesForSelected,
tags: openTagsForSelected,
toggleDetach: () => toggleDetach(),
+ stopAndClose: () => stopAndClose(),
download: downloadSelected,
help: toggleHelp,
backspace: () => { navigateBack(); },
diff --git a/web/js/keyboard.js b/web/js/keyboard.js
index 1a933c6..53ee3d0 100644
--- a/web/js/keyboard.js
+++ b/web/js/keyboard.js
@@ -275,6 +275,10 @@ export function initKeyboard(handlers) {
case 'u': handlers.upload?.(e); break;
case 'L': handlers.sharesToggle?.(e); break;
case 'T': handlers.regenThumbnail?.(e); break;
+ case 'q':
+ e.preventDefault();
+ handlers.stopAndClose?.(e);
+ break;
case '?':
e.preventDefault();
handlers.help?.(e);
diff --git a/web/js/player.js b/web/js/player.js
index 343bc1b..ad44e14 100644
--- a/web/js/player.js
+++ b/web/js/player.js
@@ -315,6 +315,41 @@ export function seekRelative(seconds) {
return true;
}
+export function stopAndClose() {
+ const m = currentMediaElement();
+ if (m) m.pause();
+ if (currentMedia?.type === 'image') {
+ stopSlideshow();
+ resetImageZoom();
+ }
+ exitFullscreenIfNeeded();
+ const e = els();
+ e.player?.classList.remove('open', 'has-image', 'minimized');
+ e.video?.classList.add('hidden');
+ e.audio?.classList.add('hidden');
+ e.image?.classList.add('hidden');
+ e.coverArt?.classList.add('hidden');
+ e.bigPlay?.classList.add('hidden');
+ e.track?.classList.add('hidden');
+ e.timeElapsed?.classList.add('hidden');
+ e.timeTotal?.classList.add('hidden');
+ e.btnPlay?.classList.add('hidden');
+ e.btnZoomIn?.classList.add('hidden');
+ e.btnZoomOut?.classList.add('hidden');
+ e.btnSlideshow?.classList.add('hidden');
+ currentMedia = null;
+ currentMediaIndex = -1;
+ isPlaying = false;
+ stopProgressTimer();
+ updateUI(false);
+ highlightPlayingCard();
+ if (e.fill) e.fill.style.width = '0%';
+ if (e.thumb) e.thumb.style.left = '0%';
+ if (e.timeElapsed) e.timeElapsed.textContent = '0:00';
+ if (e.timeTotal) e.timeTotal.textContent = '0:00';
+ if (e.buffered) e.buffered.style.background = 'transparent';
+}
+
export function selectAndPlay(media, index, resumeFrom = 0) {
currentMedia = media;
currentMediaIndex = index ?? -1;