Commit Graph

2944 Commits

Author SHA1 Message Date
Arkadiusz Fal
eea13a6290 Stop macOS player shortcuts from swallowing typing in other windows
The player's keyDown monitor only checked that some window was key, so
plain-key shortcuts (m, space, f, arrows) fired while typing into the
Settings window's text fields. Guard on the window actually hosting the
player controls and pass events through while a text field is editing.
2026-06-01 20:32:10 +02:00
Arkadiusz Fal
05bbd449f8 Drive macOS player control bars from presets
Render the macOS control bar button row and top bar from the active
preset's sections via a new MacOSControlsSectionRenderer, replacing the
hardcoded QuickTime-style rows in MacOSControlBar. Add a keepOnTop
button type and macOS-specific button availability lists, and adapt the
player controls settings editors (preset editor, section editor, button
configuration) for macOS.

Also apply the preset font style to the control bar time labels, and
update the built-in macOS Default preset order (queue before transport,
fullscreen at the end), bumping builtInPresetsVersion to 7.
2026-05-31 16:55:39 +02:00
Arkadiusz Fal
fa6ad2235c Fix tvOS GL render pipeline defects behind A10X IOFence crash
Apple TV 4K 1st gen on tvOS 26 crashes to the home screen with a
"blocked by IOFence" gpuEvent at playback start (#949) and stutters
with a starved demuxer cache (#947). Defensive fixes in the shared
iOS/tvOS OpenGL path:

- Drain in-flight GPU work (glFinish) before destroying the
  renderbuffer, so a pending presentRenderbuffer fence can't hold the
  CAEAGLLayer IOSurface being torn down or reallocated
- Never leave the EAGLContext current on the main thread: framebuffer
  create/destroy and the background glFinish now unbind on exit, and
  setup no longer binds the context on main at all
- Disable retained backing on tvOS; it exists only for iOS PiP frame
  capture and adds per-frame IOSurface fence pressure
- Report swaps to mpv after each present so display-vdrop (the tvOS
  default video-sync) gets swap-timing feedback, matching macOS

Add verbose-gated diagnostics for remote triage: playback stats every
10s (cache fill/rate, dropped frames, hwdec, avsync) and a rate-limited
slow-frame warning in performRender.

Addresses #949 and #947
2026-05-31 11:27:09 +02:00
Arkadiusz Fal
5950dbb22a Honor Lock Controls on macOS player
The macOS controls stack (MacOSPlayerControlsView + MacOSControlBar)
never read playerState.isControlsLocked, so locking via the settings
sheet had no effect: buttons, the scrubber, the volume slider, and the
AppKit keyboard shortcuts all kept working.

When locked, dim and disable transport, volume, the progress bar,
queue, the more menu, PiP, the pin/floating toggle, and the title-tap
details button; ignore playback keyboard shortcuts (space/arrows/mute)
while keeping F/Esc for fullscreen. Settings and Close stay enabled so
the user can reopen Settings to unlock.
2026-05-30 22:22:18 +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
32f526e05b Add macOS player top bar with title, avatar, and close
Show a top row in the macOS player controls (like tvOS) with the channel
avatar, video title, author name, and a close button whenever controls are
visible.

- Offset the top row below the traffic lights in the floating window by
  measuring the window buttons against the top bar (no effect in fullscreen,
  the inline sheet, or the side panel where there is no overlap).
- Tapping the avatar/title/author toggles the floating details panel, mirroring
  the iOS title/author control.
- Hide the controls immediately when the details panel opens instead of waiting
  for the auto-hide timer.
2026-05-29 08:17:14 +02:00
Arkadiusz Fal
486024834d Add position-based colorful Home shortcut palettes
Colorful shortcut cards now color by grid position instead of shortcut
identity, so colors stay in the same slot when shortcuts are reordered.

Adds a Palette option (Classic default, plus Sunset, Meadow, Berry,
Grape) and a Custom palette with a swatch-list editor (ColorPicker + hex
fields) and a switchable comma-separated text field. Color is resolved
per position and wraps by palette length, wired via a new
homeShortcutColorfulColor environment value and a Color(hex:) extension.
2026-05-28 23:26:05 +02:00
Arkadiusz Fal
bcd4206c55 Move Home shortcut style picker to its own page with live preview
Replace the inline Plain/Accent/Colorful picker in Home customization with
a navigation row that opens a dedicated page. The new page keeps the style
selector and renders a non-interactive preview of every shortcut type so
the chosen style is visible before committing.

- Add an optional styleOverride to HomeShortcutCardView so the preview can
  reflect the not-yet-saved selection.
- On macOS, navigate via navigationDestination(isPresented:) instead of a
  List-embedded NavigationLink, which otherwise stays stuck in its selected
  state after popping back and can't be reopened.
2026-05-28 20:34:38 +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
6bc58f10b2 Open macOS inline player sheet at correct size when video is loaded
The inline-sheet content declared only a minWidth/minHeight floor, so
.presentationSizing(.fitted) opened the sheet at 640x360 and
SheetWindowResizer corrected it a runloop tick later — a visible
small-then-resize flash when re-opening the sheet while a video plays.

Feed the already-computed aspect-derived size as the frame's ideal size
so .fitted opens the sheet at the correct size immediately when the video
dimensions are known; the resizer's first pass then no-ops on its guard.
The floor (no max) is kept so content still tracks later animated resizes.
2026-05-27 23:03:40 +02:00
Arkadiusz Fal
07fd5e2744 Add "Edit Shortcuts" context menu to Home shortcuts
Long-press (or right-click) any Home shortcut to open the Home settings
sheet for reordering and toggling shortcuts.
2026-05-26 07:57:55 +02:00
Arkadiusz Fal
7a1af02418 Add card style option for Home shortcut cards
Adds a Style picker (Plain / Accent / Colorful) to the Customize Home
sheet, shown when the shortcuts layout is Cards. Defaults to Plain.

- Plain: current look (light accent tint + accent border)
- Accent: solid accent fill, white icon/text
- Colorful: solid fill with a fixed color per shortcut type

The filled styles use a Reminders-style layout (icon top-leading, count
top-trailing, title anchored to the bottom) with a subtle gradient sheen
over the fill color. Cards without a meaningful count (Open Link, Remote,
Subscriptions, instance content, media sources) hide the number; Remote
shows its status dot in the top-right slot instead.

Persisted via SettingsKey.homeShortcutCardStyle, mirroring the existing
homeShortcutLayout accessor.
2026-05-26 06:43:34 +02:00
Arkadiusz Fal
5f49f2d022 Fix macOS black video after re-parenting the shared player view
The player video surface is a single shared MPVOGLView that is re-parented
between the mini-bar container (main window) and the expanded-player sheet
(a separate sheet window). When presenting the sheet, the new container
attaches the shared view before the old mini-bar container tears down, so
the old container's viewWillMove(toSuperview: nil) was ripping the shared
view out of its new home via removeFromSuperview() — leaving it with no
window and a black surface. Symmetric, so it also blanked the mini-bar
preview after collapsing.

Only detach the shared view if it still belongs to the container
(superview === self). Also force a repaint on window reattach so a paused
frame redraws, and extend the onAppear resumeRendering to macOS.
2026-05-25 19:46:35 +02:00
Arkadiusz Fal
fe4e3c531a Use rounded font for macOS player time labels
Switch the current-time and duration labels in the macOS transport
control bar from a monospaced design to a rounded design with
.monospacedDigit(), keeping stable digit widths while matching the
softer Liquid Glass look.
2026-05-25 18:02:34 +02:00
Arkadiusz Fal
54488aea0f Keep macOS player transport buttons visible but disabled when unavailable
The play-next and Picture-in-Picture buttons were folded into the view
condition (hasNext / isPiPPossible), so they popped in and out of the
control bar and shifted the surrounding layout. Always render them (when
their callback is wired) and gate availability with .disabled() instead,
mirroring the existing play-previous button.
2026-05-24 21:05:29 +02:00
Arkadiusz Fal
9568149c21 Use native text Close button for sheet toolbars on macOS
Sheet dismiss buttons rendered as an iOS-style circular glass "X" on
macOS 26, which looks foreign on the desktop. Add a shared
sheetCloseToolbarItem helper that renders a native text "Close" button
on macOS while keeping the compact icon-only xmark on iOS/tvOS, and
replace the ~14 duplicated inline close buttons across sheets with it.
2026-05-24 12:34:53 +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
86f9ef56b3 Dismiss expanded player window when closing video from the mini bar
The capsule close button (and context-menu close) only stopped playback and
cleared the queue, leaving the separate macOS player window open on a black
frame. Route both through a shared closeVideo() that also sets
isPlayerExpanded = false, mirroring PlaybackCommands.closeVideo, so the
player window is dismissed.
2026-05-23 11:39:46 +02:00
Arkadiusz Fal
84c17382ce Redesign macOS now-playing bar as centered Liquid Glass capsule
- Replace the full-width material overlay bar with a compact, centered
  capsule (Apple Music style) via a macOS-specific macOSCapsuleLayout
- Extend glassBackground() to use real Liquid Glass on macOS 26 with a
  material fallback on macOS 15-25 (mirrors the PlayerOverlayButton pattern)
- Remove the duplicate close button (close now comes only from the
  configurable button set)
- Keep the capsule visible in the main window while the separate player
  window is open (window mode), still hidden in sheet mode
2026-05-22 23:43:51 +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
93f9a382b0 Refine macOS player controls: add queue, more, previous; drop seek/fullscreen buttons
Add Queue (list.bullet), More (VideoContextMenuView ellipsis), and Play
Previous buttons to the macOS control bar. Play Previous is shown always and
disabled when the queue has no previous item. Remove the skip-back/forward 10s
buttons and the fullscreen button from the bar (arrow-key seek and F-key
fullscreen shortcuts are retained).

Queue reuses the existing cross-platform queue sheet; More reuses the shared
VideoContextMenuView (already !os(tvOS)), styled borderless to match the flat
bar buttons.
2026-05-21 21:36:09 +02:00
Arkadiusz Fal
64171a9cfb Add explicit sizing to sheets on macOS
Give player, download, source, and login sheets minimum frame dimensions on macOS so they open at a usable size instead of collapsing.
2026-05-21 18:19:19 +02:00
Arkadiusz Fal
7cb068c800 Fix Add to Playlist sheet layout on macOS
The sheet collapsed to an empty strip because a List inside a sheet
has no intrinsic height on macOS. Add an explicit minimum frame like
other sheets, gate presentationDetents to iOS, and replace the
prominent xmark toolbar button with a native Close button on macOS.
2026-05-20 09:15:27 +02:00
Arkadiusz Fal
6dfa63c263 Remove Enable DASH setting; make DASH a last-resort format
Drop the dashEnabled toggle and its plumbing. Stream auto-selection now
keeps DASH as a candidate but ranks it strictly below progressive and
HLS formats, so DASH is only chosen when it is the only format available
(including live-stream fallback after HLS). The manual quality selector
hides DASH entirely on iOS/tvOS/macOS. Reconciles BackendSwitcher, which
previously treated HLS/DASH equally, to use the same DASH-last ranking.
2026-05-20 07:14:31 +02:00
Arkadiusz Fal
88fafc5ada Remove Test Connection from source editing
The Test Connection buttons only probed API/server reachability, which
was misleading: a green result said nothing about whether videos would
actually play. Remove both the remote-server and WebDAV bandwidth test
buttons and all code exclusive to them (testBandwidth, BandwidthTestResult,
and orphaned localization keys). Add-time connectivity validation for
SMB/WebDAV sources is retained.
2026-05-19 20:40:28 +02:00
Arkadiusz Fal
858011c507 Generalize first-launch import prompt to cover sources
The prompt fires for any legacy data now, so word it for accounts and
sources rather than accounts only.
2026-05-19 20:25:33 +02:00
Arkadiusz Fal
86fbdd23f1 Allow importing account-less legacy instances as sources
Legacy v1 instances without an account had no import path after the
switch to explicit account review. Surface them in a separate "Sources"
section of the legacy import view where they can be added with a single
tap (no sign-in) or removed, mirroring the accounts flow. Instances tied
to an account stay in the Accounts section and are not duplicated.

Detection now gates the entry points and first-launch prompt on any
legacy data (accounts or sources), not accounts alone.
2026-05-18 23:54:42 +02:00
Arkadiusz Fal
de9b06072d Show only the alert on legacy account import success
Successful import fired both a toast and a confirmation alert for the
same event. Drop the toast and keep the alert.
2026-05-18 20:22:35 +02:00
Arkadiusz Fal
c4bcfd1211 Remove dead legacy-import pipeline and unused LegacyAccount.password
The manual account-import flow made the old silent instance-import
pipeline unreachable. Drop the orphaned parsing, reachability checks,
batch import, result/error types, and the old import row view, plus the
httpClient dependency they were the only consumer of. Also remove the
LegacyAccount.password field, which was parsed but never read.
2026-05-17 19:29:21 +02:00
Arkadiusz Fal
bde80bfef1 Add manual legacy account import 2026-05-17 13:08:32 +02:00
Arkadiusz Fal
69cf616465 Bump build number to 262 2026-05-16 23:26:30 +02:00
Arkadiusz Fal
b171a6d8c0 Update Sparkle 2026-05-16 20:47:13 +02:00
Arkadiusz Fal
37d11b50a8 Render Add Source buttons inline on macOS where toolbar items aren't shown
Pre-macOS 26 doesn't reliably surface a detail-pane NavigationStack's
toolbar confirmation items in the window title bar, leaving the
"Add Source" actions invisible. Gate the toolbar placement behind
macOS 26+ and fall back to an inline form/header button on older
macOS across the add-source views and the sources list.
2026-05-15 07:55:30 +02:00
Arkadiusz Fal
bc81b03821 Fall back to software OpenGL renderer instead of crashing on GPU-less Macs
MPVOpenGLLayer called fatalError() when CGLChoosePixelFormat failed for
both the 10-bit and 8-bit (kCGLPFAAccelerated) formats, killing the app at
launch on machines without a usable GPU such as virtual machines.

Add a non-accelerated software pixel format as a last resort and make the
whole OpenGL init path failable: createPixelFormat/createContext return nil
(with an error log) instead of aborting, MPVOpenGLLayer.init becomes init?,
and MPVOGLView holds an optional layer, throwing openGLSetupFailed from
setup(with:) when OpenGL is unavailable. That error is already handled by
MPVBackend.setupMPVAsync, so the app now launches cleanly and only logs that
video playback is unavailable.
2026-05-15 07:03:24 +02:00
Arkadiusz Fal
c49ae6c6a6 Capture player weakly in outer Task to fix mismatched ownership warning 2026-05-14 19:38:13 +02:00
Arkadiusz Fal
c168fbae02 Add tvOS A/V sync diagnostics on its own settings page
Surface audio-delay (±10/±100 ms) and video-sync-mode controls behind an
"A/V Sync" navigation row in Advanced settings rather than inline, keeping the
Advanced page uncluttered. Audio delay applies live to the running MPV
instance; sync mode takes effect on next playback.
2026-05-14 09:43:25 +02:00
Arkadiusz Fal
579df26284 Persist SwiftData within a background task on suspension (0xdead10cc)
On backgrounding, the app flushed pending CloudKit changes in an unguarded Task
while SwiftData autosave could leave an open SQLite transaction. iOS terminates
apps that hold the database lock across suspension (0xdead10cc), which was the
largest crash group on build 261. On iOS, commit pending changes synchronously
to release the lock, and run the async flush inside a beginBackgroundTask
assertion that ends when the flush completes.

Fixes 0xdead10cc SIGKILLs seen in TestFlight build 261 (17 reports).
2026-05-13 22:05:04 +02:00
Arkadiusz Fal
ba33823048 Run CloudKit record mapping on the main actor to fix SwiftData race
CloudKitRecordMapper was a standalone actor, so 'await recordMapper.toCKRecord'
from the @MainActor sync engine hopped off the main actor and read live
SwiftData @Model properties (e.g. LocalPlaylistItem.authorName) from the wrong
executor. SwiftData models are bound to the main ModelContext and are not
thread-safe, so this raced the backing store and crashed with EXC_BAD_ACCESS.

Make the mapper @MainActor (it must touch main-isolated models regardless) and
drop the now-redundant awaits. Fixes the crash for all synced model types.

Fixes an EXC_BAD_ACCESS seen in TestFlight build 261.
2026-05-13 20:56:03 +02:00
Arkadiusz Fal
926c6ebc97 Set popover anchor for share sheets to fix iPad crash
UIActivityViewController is presented as a popover on iPad and requires an
anchor; without a sourceView/barButtonItem UIKit aborts in
-[UIPopoverPresentationController presentationTransitionWillBegin]. Set
sourceView/sourceRect on both the direct presentation in the swipe-actions
modifier and the ShareSheet representable.

Fixes a SIGABRT seen in TestFlight build 261.
2026-05-12 23:54:57 +02:00
Arkadiusz Fal
2ed0dc72be Fix CheckedContinuation double-resume in waitForConnectionReady
The 'resumed' guard flag was set inside a dispatched MainActor Task rather than
synchronously in the connection state handler. Two states arriving in quick
succession (e.g. .ready then .failed) could both pass the guard before either
Task ran, resuming the continuation twice and trapping. Claim the continuation
synchronously on the serial queue before dispatching the side effects.

Fixes a SIGTRAP seen in TestFlight build 261.
2026-05-12 22:32:13 +02:00
Arkadiusz Fal
ec5ac944c4 Fix download completion crash from stale array index across await
completeMultiFileDownload captured an index via firstIndex, then awaited a
detached file-size calculation. Another concurrently-completing download could
mutate activeDownloads during that suspension, leaving the index stale and
crashing in Array.remove(at:) with an out-of-bounds index. Remove the download
by identity instead.

Fixes a SIGTRAP seen in TestFlight build 261 (5 reports).
2026-05-11 22:50:16 +02:00
Arkadiusz Fal
f74659e903 Fall back to lower-quality thumbnails when higher-res variants 404
YouTube's CDN advertises maxres/sddefault thumbnails for every video but
only generates them for sufficiently high-res uploads, so older/low-res
videos return 404 for those qualities. The view picked the best quality
and showed a blank placeholder on failure with no fallback.

VideoThumbnailView now accepts an ordered fallback chain and advances to
the next candidate via NukeUI's onCompletion when a load fails, so a valid
thumbnail is always shown. DeArrowVideoThumbnail feeds it DeArrow branding
first, then the video's own thumbnails best-quality-first.
2026-05-11 18:56:06 +02:00
Arkadiusz Fal
a734bb47f7 Fix downloads never finishing due to duplicate AppEnvironment
SwiftUI evaluates a @State default-value autoclosure more than once,
keeping only the first instance but still running the side effects of
the discarded ones. Each AppEnvironment() built its own DownloadManager,
and each DownloadManager registered a background URLSession under the
same identifier. The download task started on the surviving instance's
session, but iOS delivered the completion delegate callback to the other
(leaked) instance, whose activeDownloads was empty - so the finished
file was dropped and the progress spinner spun forever at 0 KB.

- Make AppEnvironment a process-wide singleton (static let shared) and
  reference it from the App's @State, guaranteeing exactly one instance
  (and one background session, one DataManager/CloudKit stack).
- Make DownloadManager.setDownloadSettings idempotent: only create the
  background session when none exists, so a re-entrant call never
  invalidateAndCancels the live session and kills in-flight downloads.
  Cellular changes already route through refreshCellularAccessSetting().
2026-05-10 23:46:02 +02:00
Arkadiusz Fal
92d0b3215b Resume MPV rendering when expanded player view re-appears
On iPad, expanding the player after collapsing left the MPV display link
paused, showing only a static frame. MiniPlayerView pauses rendering when
its video preview hides during expand, and PlayerService.playerSheetDidAppear
is gated behind background playback so it can't reliably resume.

Call resumeRendering() from MPVVideoView.onAppear so the expanded view
always restarts the display link.
2026-05-10 17:23:56 +02:00
github-actions[bot]
c71332f69f Bump build number to 261 2.0.0-beta.261 2026-05-10 14:41:18 +00:00
Arkadiusz Fal
806be7d808 Bump build number to 260 2026-05-10 16:04:37 +02:00
Arkadiusz Fal
e4936873ca Update changelog 2026-05-10 15:50:24 +02:00
Arkadiusz Fal
a6b95e9dad Dismiss tvOS player panels when playback fails
Settings, queue, and details panels stayed open over the failure overlay
and error details sheet, obscuring them. Close any open right-side panel
on entry to the failed state.
2026-05-10 15:31:39 +02:00
Arkadiusz Fal
c52f035729 Increase tvOS queue row spacing for focus halo breathing room 2026-05-10 15:28:12 +02:00