diff --git a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-format.utils.ts b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-format.utils.ts index 0cfe5d023..d583bdcf3 100644 --- a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-format.utils.ts +++ b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-format.utils.ts @@ -7,6 +7,16 @@ export const HIDDEN_BOUNDS: EmbeddedMpvBounds = Object.freeze({ height: 1, }) as EmbeddedMpvBounds; +/** + * Vertical pixels to subtract from the MPV view's height when a control + * popover (volume, audio, subtitle, speed, aspect) is open above the + * controls strip. The native NSView paints over the WebContents, so we + * shrink it from the bottom to expose the popover region in DOM. Sized to + * cover the tallest popover (audio/subtitle list capped at ~240 px plus + * title + padding); video keeps playing in the upper region. + */ +export const MENU_OPEN_BOTTOM_CUTOUT_PX = 300; + export const SPEED_PRESETS: ReadonlyArray<{ value: number; label: string }> = [ { value: 0.5, label: '0.5×' }, { value: 0.75, label: '0.75×' }, 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 41db2bd0b..b04b2f501 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 @@ -29,10 +29,13 @@ import { } from './embedded-mpv-ui-state'; import { ASPECT_PRESETS, + HIDDEN_BOUNDS, + MENU_OPEN_BOTTOM_CUTOUT_PX, SPEED_PRESETS, aspectLabel, audioTrackLabel, formatTime, + measureBounds, persistVolume, readStoredVolume, speedLabel, @@ -226,9 +229,22 @@ export class EmbeddedMpvPlayerComponent implements OnDestroy { ); } - this.controller.setOverlayActiveProvider(() => - this.overlayVisibility.overlayActive() - ); + this.controller.setBoundsProvider((host) => { + if (this.overlayVisibility.overlayActive()) { + return HIDDEN_BOUNDS; + } + const rect = measureBounds(host); + if (this.menus.anyOpen()) { + return { + ...rect, + height: Math.max( + 1, + rect.height - MENU_OPEN_BOTTOM_CUTOUT_PX + ), + }; + } + return rect; + }); this.shortcuts.attach({ isAvailable: () => !this.overlayVisibility.overlayActive(), @@ -265,6 +281,7 @@ export class EmbeddedMpvPlayerComponent implements OnDestroy { effect(() => { this.overlayVisibility.overlayActive(); + this.menus.anyOpen(); this.controller.triggerBoundsSync(); }); diff --git a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-session-controller.ts b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-session-controller.ts index 8362a0700..0804bb200 100644 --- a/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-session-controller.ts +++ b/libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-session-controller.ts @@ -7,11 +7,14 @@ import { untracked, } from '@angular/core'; import { + EmbeddedMpvBounds, EmbeddedMpvSession, EmbeddedMpvSupport, ResolvedPortalPlayback, } from 'shared-interfaces'; -import { HIDDEN_BOUNDS, measureBounds } from './embedded-mpv-format.utils'; +import { measureBounds } from './embedded-mpv-format.utils'; + +export type EmbeddedMpvBoundsProvider = (host: HTMLElement) => EmbeddedMpvBounds; const STALLED_TIMEOUT_MS = 30_000; @@ -26,7 +29,8 @@ export class EmbeddedMpvSessionController { private readonly destroyRef = inject(DestroyRef); private readonly unsubscribeSessionUpdate?: () => void; - private overlayActiveProvider: () => boolean = () => false; + private boundsProvider: EmbeddedMpvBoundsProvider = (host) => + measureBounds(host); private activeBoundsSync: (() => void) | null = null; private boundsAnimationFrame: number | null = null; private stalledTimer: number | null = null; @@ -65,8 +69,8 @@ export class EmbeddedMpvSessionController { }); } - setOverlayActiveProvider(provider: () => boolean): void { - this.overlayActiveProvider = provider; + setBoundsProvider(provider: EmbeddedMpvBoundsProvider): void { + this.boundsProvider = provider; } triggerBoundsSync(): void { @@ -98,9 +102,7 @@ export class EmbeddedMpvSessionController { if (!activeSessionId) { return; } - const bounds = this.overlayActiveProvider() - ? HIDDEN_BOUNDS - : measureBounds(host); + const bounds = this.boundsProvider(host); void window.electron ?.setEmbeddedMpvBounds(activeSessionId, bounds) .catch(() => undefined);