Commit Graph

30 Commits

Author SHA1 Message Date
Arkadiusz Fal
7f4555dc2e Keep the macOS corner player layout from being squeezed
The separate player window could be resized below what the corner layout
needs. Free resize now floors at the corner layout's minimum (its width
threshold, the narrowest side column plus a video column wide enough for
the control bar, and 600pt tall), and an undersized window grows to it
when the corner layout takes over. The video column minimum is 560pt on
macOS so the control bar capsule fits.

The control bar's fixed 650pt frame also widened the controls stack past
a narrower video column, clipping the top bar's avatar and close button;
the frame is now capped to the container width.
2026-10-13 23:18:47 +02:00
Arkadiusz Fal
4770bbe3b9 Remove dead code found by Periphery on all platforms
Delete declarations that Periphery reports unused on every platform that
compiles them (iOS, macOS, tvOS), including nine views used only by their
own previews. Platform-gated code still used on another platform, device-
only MPV render paths, migration models, CloudKit record names, App
Intents metadata and unused parameters are kept.
2026-10-08 23:57:56 +02:00
Arkadiusz Fal
1071fc56b3 Stop inline macOS playback when its window closes 2026-09-29 23:26:00 +02:00
Arkadiusz Fal
60222fa5f3 Fix inline macOS player aspect resizing 2026-09-29 23:08:16 +02:00
Arkadiusz Fal
8d340048ea Hide the corner layout panels when the macOS player enters native fullscreen
The fullscreen button in the corner layout zoomed the whole video-plus-
panels arrangement onto the screen and left the panels there. Now the
button hides the panels first (the video grows over them inside the
current window), waits for that animation, then runs the native zoom, so
the fullscreen space shows the video alone. On exit the window returns to
its size first and the panels slide back in, unless the user re-showed
them while fullscreen. System-initiated transitions (green button, Window
menu, Mission Control) hide the panels as the zoom starts.

Hiding the panels re-arms the separate window's aspect lock; an entering
flag parks it during the transition and the restore on exit skips the
parked replay, so the window keeps its corner size instead of auto-
fitting to the video. The preset's remembered panel visibility is left
untouched by the fullscreen session.
2026-09-28 23:05:05 +02:00
Arkadiusz Fal
96eeadb479 Stop the macOS player window oscillating when a drag crosses the corner layout threshold
The corner layout resolves from the window width. Dragging the window
narrower than the threshold dropped the layout mid-drag, which re-locked
the aspect ratio and auto-fitted the window back to the video size. The
fitted width was above the threshold again, so the layout re-entered
corner mode and unlocked, and the cycle repeated on every layout pass for
the rest of the drag (visible as the window abruptly jumping in size).

Aspect lock/unlock requests made during a live resize are now parked and
applied in windowDidEndLiveResize, since changing contentAspectRatio or
contentResizeIncrements under the cursor makes AppKit's resize handler
switch modes and snap the frame. Auto-fit never runs during a live resize,
and the corner-layout change handler skips it when the change was caused
by the user's drag: the drag owns the size.

The free-resize size floor in windowWillResize now clamps width and height
independently. Preserving the proposed ratio (correct only under an aspect
lock) widened the window while dragging just the bottom edge past the
minimum height.
2026-09-28 23:00:40 +02:00
Arkadiusz Fal
99692a3554 Fix macOS corner layout window resize and re-lock the aspect ratio when panels hide
Entering the corner layout cleared the window's aspect-ratio lock by setting
contentAspectRatio to zero. That doesn't remove the constraint: AppKit keeps
its aspect-ratio resize handler active with a degenerate 0:0 ratio, so every
interactive resize resolved to the current size and the window couldn't be
resized at all. Reset contentResizeIncrements to 1x1 instead, the supported
way back to free resizing (aspect ratio and resize increments are mutually
exclusive).

Free resize now also requires the corner panels to be visible. With the
panels hidden only the video remains, so the window follows the ratio lock
(and auto-fit, when on) exactly like the stacked layout. The new
AppEnvironment.isCornerPanelLayoutActive gate covers the layout-change
handler, window creation, and the fullscreen-exit replay.
2026-09-28 20:19:10 +02:00
Arkadiusz Fal
3e5b387532 Apply panel default visibility to portrait panel without position jump
The preset's When Player Opens setting only drove the landscape/floating
panel state, so on iPhone in portrait the details panel reappeared on
every player expansion regardless of the setting. Mirror the resolved
visibility into the portrait panel state, and seed it into the sheet's
initial state at all creation sites so the first frame already renders
the correct layout - applying it in onAppear animated a visible
top-to-center video jump during expansion. Portrait panel toggles now
also persist the last visibility so Remember Last works there too.
2026-09-22 23:53:32 +02:00
Arkadiusz Fal
a6d989c7af Add corner player layout on iPad and macOS
Preset-selected two-region layout: video in the top-left, a resizable
side column, and an optional region below the video. Pages are assigned
to regions per preset (Location picker in the page editor); the layout
falls back to the stacked widescreen layout below 900pt width, so Split
View and narrow windows degrade automatically.

- CornerPlayerLayout hosts the shared playerAreaForWidescreen builder,
  so the MPV hosting path is identical to the stacked layout
- Layout resolver in ExpandedPlayerSheet picks corner per layout pass;
  NavigationCoordinator.isCornerLayoutActive feeds the macOS window
  manager, which drops the aspect-ratio lock and auto-fit while corner
  is active (and restores them on exit, incl. fullscreen replay and
  window re-mounts)
- Side column width persisted via new cornerPanelColumnWidth setting
  (280-560pt, video column kept >= 480pt)
- Settings: layout mode picker (iPad/macOS) with automatic-fallback
  footer, per-page region picker when corner is on, region shown in
  the pages list
2026-09-21 23:52:09 +02:00
Arkadiusz Fal
76a709a921 Keep macOS player fullscreen when queue advances to different-aspect video
Aspect-driven window sizing (contentAspectRatio + setFrame) ran even while
the separate player window was in native fullscreen, making AppKit fit and
center the content inside the fullscreen space with black bars around it.

Defer the aspect lock and auto-resize while fullscreen by parking the ratio,
then replay it in windowDidExitFullScreen so the window is ratio-locked (and
auto-fitted when that setting is on) once back in windowed mode.
2026-08-03 23:34:06 +02:00
Arkadiusz Fal
bc7b00dfb3 Restore last player window frame when auto-resize is off
With auto-resize disabled the separate player window opened at a
computed 16:9 default and was never corrected, so non-16:9 videos sat
letterboxed until a manual resize. Persist the window frame via
NSWindow frame autosave and restore it on open when auto-resize is
off, so the window reopens exactly as the user last left it.
2026-07-18 16:21:12 +02:00
Arkadiusz Fal
66f7602d08 Exit fullscreen before dismissing macOS player window
Ordering out a window in native fullscreen skips the exit transition and
strands its black fullscreen space on screen. Route all dismissal paths
(stop/collapse, PiP hide, window close) through exitFullScreenIfNeeded,
which leaves fullscreen first and defers hiding until the transition
completes, with a timeout fallback if AppKit refuses the exit.
2026-07-12 21:01:15 +02:00
Arkadiusz Fal
ecb6a9dd7a Replace macOS inline player sheet with full-window overlay
When "Play in a separate window" is disabled, the expanded player was a
native sheet attached to the main window. AppKit refuses fullscreen with
an attached sheet, so pressing F swapped the sheet for an in-window
overlay before fullscreening — a visible two-step transition.

Present the inline player as the full-window overlay from the start, so
fullscreen is just the window's native toggle. This removes the whole
sheet-swap machinery (toggleSheetFullScreen with its detach timing,
SheetWindowResizer, sheet sizing helpers).

- beginInlineOverlay/endInlineOverlay hide/restore the main window
  toolbar and mirror the window's fullscreen state into the renamed
  isMacInlinePlayerFullScreen flag via NSWindow notifications, so exits
  through Esc/green button/Mission Control stay in sync
- Collapsing exits fullscreen only if it was entered for the video
- Esc in the windowed overlay collapses to the mini bar via a new
  onCollapse callback (replacing the sheet's default Esc-dismiss)
- ContentView registers its hosting window (MainContentWindowReader) so
  the overlay targets the right window even when the setting is toggled
  while the Settings window has focus
2026-07-03 21:57:55 +02:00
Arkadiusz Fal
9bcb8e35a6 Add native fullscreen for macOS sheet-mode player via in-window overlay
The fullscreen button did nothing in sheet mode: AppKit ignores
toggleFullScreen on a window attached as a sheet, and the old fallback
targeted the key window — the sheet itself. Two other strategies were
ruled out first: fullscreening the sheet's parent is silently refused
while any sheet is attached, and resizing the sheet's backing window to
screen size NaN-asserts inside AppKit's sheet positioning even without
animation.

Working approach: swap the sheet for a plain overlay in the main window
and native-fullscreen the now sheet-free main window.

- isMacPlayerFullScreenOverlay on NavigationCoordinator dismisses the
  sheet (presentation binding excludes it; the setter ignores the
  dismissal write-back) and mounts ExpandedPlayerSheet as a black-backed
  overlay in ContentView
- ExpandedPlayerWindowManager fullscreens the parent after the sheet
  detaches, verifies AppKit accepted it (rolls back if refused), hides
  the window toolbar (NSWindow keeps it as a strip above the overlay
  otherwise) and restores it on exit
- a didExitFullScreen observer restores the sheet on any exit path
  (button, Esc, green button, Window menu); closing the player while
  fullscreen exits the main window's fullscreen too
- scheduleSharedViewAdoptionRetry on entry re-homes the shared render
  view once the dismissing sheet's window loses visibility — its steal
  is declined mid-dismissal and nothing else retried, leaving the
  overlay black until the next video load
2026-06-25 21:55:47 +02:00
Arkadiusz Fal
59cc7a8249 Enforce minimum size for macOS player window
The separate macOS player window (and the inline sheet, which locks to the
same window via applyAspectRatioConstraint) could be dragged arbitrarily
small. Two AppKit behaviors combined to defeat the intended minimum:

- Setting contentAspectRatio makes AppKit's aspect-ratio resize handler take
  over live drags and stop enforcing minSize/contentMinSize.
- NSHostingController resets minSize/contentMinSize to zero at runtime, even
  with sizingOptions = [].

Enforce the floor in windowWillResize(_:to:) from constants instead, growing
any under-sized proposal back up to a 360pt minimum height (aspect-scaled
width, with a floor for narrow videos) while preserving the proposed ratio.
2026-06-25 20:12:49 +02:00
Arkadiusz Fal
7808d4c730 Fix black video in sheet-mode player and mini capsule hand-off delays
Sheet mode has no container lifecycle hook on either transition, so the
shared render view was stuck until the render watchdog (~10s of black):

- Presenting the sheet: the capsule parks the view while the sheet's
  window exists but is not yet visible - viewDidMoveToWindow has already
  fired and findTransferTarget rejects non-visible windows, so nothing
  picked the parked view up.
- Dismissing the sheet: the dismissed sheet's hierarchy stays alive
  holding the view inside a now-invisible window, with no unmount or
  park at all.

Add scheduleSharedViewAdoptionRetry() to MPVContainerNSView: for ~1s it
re-checks every 50ms whether the shared view needs re-homing (parked,
or owned by a container whose window lost visibility) and runs the same
recovery the watchdog uses. Deduplicated so overlapping triggers don't
stack loops. Triggered after parking with no transfer target and from
hide()'s no-window branch, which every sheet-mode collapse funnels
through.

The retry ticks pass quiet: true to recoverSharedPlayerViewIfNeeded so
teardown after closing a video (when no container legitimately exists)
no longer spams warnings, and the loop stops once the shared view is
gone.
2026-06-23 21:24:13 +02:00
Arkadiusz Fal
945804c109 Hand off render view to mini capsule immediately on player collapse
The capsule's claim on the shared render view is declined during the
player window's fade-out (the window is still visible, so the steal
guard refuses), and nothing retried after orderOut - the capsule stayed
black until the render watchdog recovered the view ~10 seconds later.

Call recoverSharedPlayerViewIfNeeded() in hide()'s cleanup right after
ordering the window out, so the view moves to the capsule as soon as
the collapse finishes. The PiP path is untouched - there the view must
stay in the hidden window.
2026-06-22 20:19:25 +02:00
Arkadiusz Fal
bf00c48e6c Fix macOS player window black video when mini capsule steals render view
Closing the player window via its close button left isPlayerCollapsing
stuck true (windowShouldClose has no animation completion to clear it),
so the mini capsule preview kept mounting its render container and
grabbed the shared render view on the next expand - the video rendered
into the 44x26 thumbnail while the player window stayed black.

- Clear isPlayerCollapsing on the next runloop in windowShouldClose
- Give containers in the tracked player window priority to claim the
  shared view (even from a visible owner), and refuse steals from the
  visible player window by outside containers
- Prefer the largest container in the player window during recovery so
  thumbnail-sized surfaces are never picked
- Add a watchdog misplaced-view check in MPVBackend that re-attaches
  the render view when it lives outside the visible player window
- Include container bounds and tracked-window flag in attach/decline
  logs
2026-06-22 19:55:32 +02:00
Arkadiusz Fal
b82241564d Fix macOS permanent black video after player collapse/expand cycles
In separate-window mode, hide() keeps the ordered-out window's SwiftUI
hierarchy alive (nilling contentViewController crashes AVKit), and its
updateNSView unconditionally re-attached the shared MPVOGLView -
stealing it into a window CoreAnimation never composites. The layer's
silent skip-render fallback then consumed every frame flag, so video
stayed black (climbing vo drops) until app restart.

- Decline stealing the shared view from a visible container into a
  non-visible one; owner-initiated transfers bypass the guard
- Only transfer to containers in visible windows or the window still
  tracked by ExpandedPlayerWindowManager; park the view otherwise and
  reclaim it when a container gains a window
- Log skip-render frame consumption (rate-limited) and track render
  health counters (draws/skips/dropped draws)
- Add a watchdog to the playback stats task that detects a stalled
  video output and re-attaches the view to a visible container
- Don't mount player layouts during zero-size layout passes
2026-06-21 21:55:37 +02:00
Arkadiusz Fal
0d05daae9d Fix black screen after restoring PiP when player window was closed on macOS
The expanded player window hidden for PiP is reused on restore, so its
MPVContainerNSView kept a stale weak reference to the shared render view
that the mini player preview had taken during PiP. On restore the mini
preview released the view as an orphan and the reused container skipped
re-attaching it because its guard only compared view identity.

- Skip re-attach in setPlayerView only when the view is still actually
  our subview, matching the iOS container's stolen-view handling
- Transfer the shared render view to another living container on unmount
  instead of orphaning it, since SwiftUI is not guaranteed to re-run
  updateNSView on a hidden window's content after restore
- Force a repaint when restoring the window hidden for PiP; forced draws
  requested while ordered out are dropped and the layer is pull-based
2026-06-08 22:55:31 +02:00
Arkadiusz Fal
0fa4df161b Fix fullscreen not working when player window is pinned on macOS
A pinned window carries .fullScreenAuxiliary, which AppKit refuses to
make a primary fullscreen window, so toggleFullScreen was a silent
no-op. Drop the floating config before entering fullscreen and restore
it in windowDidExitFullScreen, keeping the pin setting intact.
2026-06-08 22:33:01 +02:00
Arkadiusz Fal
e94ff861c7 Fix stale queue after closing macOS player window with Cmd+W
Closing the separate player window via Cmd+W (or the traffic-light
button) stopped the player but left the queue populated, so the next
video tap showed the Play Now/Play Next/Add to Queue prompt. Clear the
queue in windowShouldClose to match the close button behavior.
2026-06-04 18:27:29 +02:00
Arkadiusz Fal
31151df169 Fix macOS player window blink when playing a new video
The separate player window's show() restore branch reset alphaValue to 0
and faded back in even when the window was already on screen, so every
Play Now while playing made the window flash. Only run the fade when the
window was actually hidden (PiP restore); otherwise just bring it forward.
2026-06-03 22:27:38 +02:00
Arkadiusz Fal
2f2e436fe2 Split macOS player mode into window toggle + floating control
Replace the three-way macOS Player Mode picker (Separate Window /
Floating Window / Inline) with a single 'Play in a separate window'
toggle in Playback settings, and move the always-on-top choice to a
pin button in the player's top bar (window mode only).

Floating was really a transient window property (NSWindow.level), not
a peer of the window/inline structural choice — so it belongs on a live
control, not in Settings. The pin state persists across sessions.

- Replace MacPlayerMode enum with macPlayerSeparateWindow /
  macPlayerFloating bool settings
- Branch runtime presentation and window level off the two bools
- Add pin.fill/pin toggle to MacOSPlayerControlsView top bar
- Update localization; drop obsolete playerMode strings and enum tests
2026-05-30 12:42:18 +02:00
Arkadiusz Fal
64ca1c10d9 Snap macOS player window to final size on open
The expanded player window opened at a seeded 16:9 size and was then
resized with an animated setFrame once the real aspect ratio / video
change landed. Because the video and controls are laid out from the
window's live size, they visibly grew/repositioned as the window
animated.

Force the first resize after each open to snap (no animation) so the
player appears at its final fixed layout, and seed the initial window
size and aspect constraint from the real video aspect ratio when it is
already known (e.g. expanding from the mini bar) to avoid even a jump.
Later resizes (switching to a different-aspect video while the window
stays open) still animate.
2026-05-29 18:36:39 +02:00
Arkadiusz Fal
ef213307dc Lock macOS inline player sheet resize to the video aspect ratio
In floating/window mode, interactive resize is aspect-locked via the
window's contentAspectRatio, so no black bars appear. The inline sheet
had no such lock, allowing free resize with pan bars on the sides.

Make applyAspectRatioConstraint static so the sheet path can reuse the
exact same lock the standalone window uses, and apply it to the sheet's
backing NSWindow from SheetWindowResizer whenever the video ratio is
known. Like the standalone window, the resize lock always follows the
real video ratio (independent of the playerSheetAutoResize size setting).
2026-05-27 23:21:07 +02:00
Arkadiusz Fal
c245a0e2a0 Resize macOS player sheet to match video aspect ratio
In sheet (inline) mode the expanded player kept a fixed size and never
reacted to the video's aspect ratio, unlike window/floating mode. Drive
the sheet's backing NSWindow size from the video aspect ratio, reusing
the same sizing math as the standalone window.

- Extract shared sizing into ExpandedPlayerWindowManager.fittedPlayerSize
  and add fittedSheetSize for the sheet path.
- Add SheetWindowResizer (NSViewRepresentable) + .sheetWindowSize modifier
  that resizes the hosting sheet window, since .presentationSizing(.fitted)
  only fits once at presentation.
- Let the sheet content fill the window and paint the window background
  black so animated resizes stay gap-free with no white bars.
2026-05-23 13:17:28 +02:00
Arkadiusz Fal
a480b73e7c Open macOS player window immediately with loading feedback
On macOS the expanded player window built ExpandedPlayerSheet directly in
a fresh NSHostingController on every open. AppKit doesn't composite the
window until that heavy view finishes its first layout pass, so the click
felt like it waited for data to load before anything appeared.

Host a lightweight two-phase root (black + spinner) that composites
immediately, then defer building ExpandedPlayerSheet by one runloop so the
window shows at once and fades in with visible loading — matching iOS.

Because assigning contentViewController resizes the window to the hosting
controller's fitting size (tiny for the placeholder), force the content
size back to the seeded 16:9 initialSize so the window opens full-sized
during the loading phase too.
2026-05-22 23:13:08 +02:00
Arkadiusz Fal
9b85ae2b13 Lock macOS player window resize to video aspect ratio 2026-05-08 18:32:43 +02:00
Arkadiusz Fal
100df744d9 Yattee v2 rewrite 2026-04-18 20:37:24 +02:00