summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--web/css/player.css11
-rw-r--r--web/detach.html1
-rw-r--r--web/index.html2
-rw-r--r--web/js/app.js3
-rw-r--r--web/js/keyboard.js1
-rw-r--r--web/js/player.js27
6 files changed, 43 insertions, 2 deletions
diff --git a/web/css/player.css b/web/css/player.css
index 0f468f9..7a8b561 100644
--- a/web/css/player.css
+++ b/web/css/player.css
@@ -128,6 +128,17 @@
max-height: 100%;
}
+.player.crop-mode video {
+ object-fit: fill;
+}
+
+.crop-indicator {
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ padding: 0 0.25rem;
+ user-select: none;
+}
+
/* Cover art for audio playback */
.player .cover-art {
width: 100%;
diff --git a/web/detach.html b/web/detach.html
index b83ef68..8d6af63 100644
--- a/web/detach.html
+++ b/web/detach.html
@@ -37,6 +37,7 @@
<input id="volume-slider" type="range" min="0" max="1" step="0.05" value="1" class="volume-slider" aria-label="Volume">
</div>
<button id="btn-fullscreen" class="icon-btn" aria-label="Fullscreen">&#9978;</button>
+ <span id="crop-indicator" class="crop-indicator hidden" title="Crop mode active (c)">✂️</span>
</div>
</aside>
</body>
diff --git a/web/index.html b/web/index.html
index f3e37a1..4b90f92 100644
--- a/web/index.html
+++ b/web/index.html
@@ -90,6 +90,7 @@
<input id="volume-slider" type="range" min="0" max="1" step="0.05" value="1" class="volume-slider" aria-label="Volume">
</div>
<button id="btn-fullscreen" class="icon-btn" aria-label="Fullscreen (f)">⛶</button>
+ <span id="crop-indicator" class="crop-indicator hidden" title="Crop mode active (c)">✂️</span>
</div>
<button id="btn-restore-player" class="player-restore" type="button" aria-label="Restore player">
<span id="player-restore-title">Player minimized</span>
@@ -133,6 +134,7 @@
<div class="help-row"><span><kbd>S</kbd></span><span>Share selected media</span></div>
<div class="help-row"><span><kbd>+</kbd> <kbd>−</kbd></span><span>Zoom in / out (image viewer)</span></div>
<div class="help-row"><span><kbd>Shift+S</kbd></span><span>Toggle slideshow (images)</span></div>
+ <div class="help-row"><span><kbd>c</kbd></span><span>Toggle crop mode (fullscreen)</span></div>
<div class="help-row"><span><kbd>L</kbd></span><span>My Shares</span></div>
<div class="help-row"><span><kbd>?</kbd></span><span>Show / hide help</span></div>
</div>
diff --git a/web/js/app.js b/web/js/app.js
index 9c769c9..13cc824 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, 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, selectAndPlay, zoomIn as playerZoomIn, zoomOut as playerZoomOut, toggleSlideshow as playerToggleSlideshow, isImageMode as playerIsImageMode } from './player.js';
import { initSearch, focusSearch, trigger as triggerSearch, parseQuery } from './search.js';
import { initShuffle, toggle as toggleShuffle, isOn as isShuffle } from './shuffle.js';
import { initThemes } from './themes.js';
@@ -134,6 +134,7 @@ async function initApp() {
mediaInfo: () => toggleMediaInfo(),
fullscreen: () => toggleFullscreen(),
toggleMinimize: () => toggleMinimize(),
+ toggleCrop: () => toggleCrop(),
escape: () => { exitFullscreenIfNeeded(); closeLightbox(); const el = currentElement(); if (el) el.classList.remove('selected'); closeAllModals(); },
shuffle: () => { toggleShuffle(); loadMedia(); },
share: () => shareSelected(),
diff --git a/web/js/keyboard.js b/web/js/keyboard.js
index 951e573..dc3eb4e 100644
--- a/web/js/keyboard.js
+++ b/web/js/keyboard.js
@@ -214,6 +214,7 @@ export function initKeyboard(handlers) {
break;
case 'C': handlers.toggleMinimize?.(e); break;
case 'f': handlers.fullscreen?.(e); break;
+ case 'c': handlers.toggleCrop?.(e); break;
case 'Escape': handlers.escape?.(e); break;
case 'Backspace': handlers.backspace?.(e); break;
case 'r': handlers.shuffle?.(e); break;
diff --git a/web/js/player.js b/web/js/player.js
index 6da8954..fea4a19 100644
--- a/web/js/player.js
+++ b/web/js/player.js
@@ -21,6 +21,7 @@ let isPanning = false;
let panStart = { x: 0, y: 0 };
let slideshowTimer = null;
let slideshowPausedUntil = 0;
+let cropMode = false;
const els = () => ({
video: document.getElementById('media-video'),
@@ -47,6 +48,7 @@ const els = () => ({
volume: document.getElementById('volume-slider'),
timeElapsed: document.getElementById('time-elapsed'),
timeTotal: document.getElementById('time-total'),
+ cropIndicator: document.getElementById('crop-indicator'),
});
export function initPlayer(options = {}) {
@@ -134,6 +136,16 @@ export function initPlayer(options = {}) {
e.video.addEventListener('loadedmetadata', updateFloatingSize);
+ document.addEventListener('fullscreenchange', () => {
+ const p = e.player;
+ if (!p) return;
+ if (!document.fullscreenElement) {
+ p.classList.remove('crop-mode');
+ cropMode = false;
+ if (e.cropIndicator) e.cropIndicator.classList.add('hidden');
+ }
+ });
+
function setupVideoDebug(m) {
const events = ['loadstart','loadeddata','loadedmetadata','canplay','canplaythrough','playing','waiting','stalled','suspend','error','abort','emptied','ended'];
events.forEach(event => {
@@ -521,10 +533,23 @@ function updateMinimizedTitle() {
export function exitFullscreenIfNeeded() {
if (document.fullscreenElement) {
document.exitFullscreen().catch(() => {});
- els().player?.classList.remove('is-fullscreen');
+ const p = els().player;
+ if (p) p.classList.remove('is-fullscreen', 'crop-mode');
+ cropMode = false;
+ const ind = els().cropIndicator;
+ if (ind) ind.classList.add('hidden');
}
}
+export function toggleCrop() {
+ const e = els();
+ if (!e.player) return;
+ if (!document.fullscreenElement) return;
+ cropMode = !cropMode;
+ e.player.classList.toggle('crop-mode', cropMode);
+ if (e.cropIndicator) e.cropIndicator.classList.toggle('hidden', !cropMode);
+}
+
function startProgressTimer() {
stopProgressTimer();
if (!reportProgress) return;