diff --git a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.html b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.html index ac4f28119..6322e5177 100644 --- a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.html +++ b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.html @@ -10,6 +10,7 @@ (pointerenter)="onPlayerInteraction()" (pointermove)="onPlayerInteraction()" (click)="onPlayerInteraction()" + (dblclick)="onPlayerDblClick($event)" (focusin)="onPlayerInteraction()" >
diff --git a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.ts b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.ts index 9733694da..47f9b35ba 100644 --- a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.ts +++ b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-player.component.ts @@ -194,12 +194,51 @@ export class EmbeddedMpvPlayerComponent implements OnDestroy { } }; private readonly onDocumentKeydown = (event: KeyboardEvent) => { - if (event.key !== 'Escape') { + if (event.key === 'Escape') { + this.closePopovers(); return; } - this.closePopovers(); + if (this.shouldIgnoreShortcut(event)) { + return; + } + + switch (event.key) { + case ' ': + case 'k': + case 'K': + event.preventDefault(); + void this.togglePaused(); + return; + case 'f': + case 'F': + event.preventDefault(); + void this.toggleFullscreen(); + return; + case 'ArrowLeft': + event.preventDefault(); + void this.seekBy(-5); + return; + case 'ArrowRight': + event.preventDefault(); + void this.seekBy(5); + return; + case 'ArrowUp': + event.preventDefault(); + this.adjustVolume(0.05); + return; + case 'ArrowDown': + event.preventDefault(); + this.adjustVolume(-0.05); + return; + case 'm': + case 'M': + event.preventDefault(); + this.toggleMute(); + return; + } }; + private mutedVolume = 0; private readonly onFullscreenChange = () => { const playerRoot = this.playerRoot()?.nativeElement; this.isFullscreen.set( @@ -520,9 +559,37 @@ export class EmbeddedMpvPlayerComponent implements OnDestroy { onVolumeInput(event: Event): void { const nextVolume = Number((event.target as HTMLInputElement).value); + this.applyVolume(nextVolume); + this.revealControls(false); + } + + onPlayerDblClick(event: MouseEvent): void { + const target = event.target as HTMLElement | null; + if (target?.closest('button, input, [role="slider"]')) { + return; + } + void this.toggleFullscreen(); + } + + private adjustVolume(delta: number): void { + const next = Math.max(0, Math.min(1, this.volume() + delta)); + this.applyVolume(next); + this.revealControls(); + } + + private toggleMute(): void { + if (this.volume() > 0) { + this.mutedVolume = this.volume(); + this.applyVolume(0); + } else { + this.applyVolume(this.mutedVolume || 0.5); + } + this.revealControls(); + } + + private applyVolume(nextVolume: number): void { this.volume.set(nextVolume); localStorage.setItem('volume', String(nextVolume)); - this.revealControls(false); const session = this.session(); if (!session?.id || !window.electron?.setEmbeddedMpvVolume) { @@ -539,6 +606,21 @@ export class EmbeddedMpvPlayerComponent implements OnDestroy { .catch(() => undefined); } + private shouldIgnoreShortcut(event: KeyboardEvent): boolean { + if (this.overlayVisibility.overlayActive()) { + return true; + } + const target = event.target as HTMLElement | null; + if (!target) { + return false; + } + const tag = target.tagName; + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') { + return true; + } + return target.isContentEditable; + } + async selectAudioTrack(trackId: number): Promise { const session = this.session(); if (!session?.id || !window.electron?.setEmbeddedMpvAudioTrack) {