Commit Graph

2818 Commits

Author SHA1 Message Date
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
Arkadiusz Fal
aa5e78a244 Silence Sendable warnings in TVRemoteHoldSeekOverlay
Annotate makeCoordinator/makeUIView/updateUIView as @MainActor so reading
the @MainActor-typed onTick closure stays within MainActor isolation and
no longer triggers a Sendable conversion warning.
2026-05-10 15:28:12 +02:00
Arkadiusz Fal
dac81e1ee8 Convert tvOS settings and queue overlays to half-screen panels
The Settings (quality/audio/subtitles) and Queue panels now slide in
from the right and occupy the right half of the screen, matching the
info/comments details panel introduced in 92cc8b79f. Video stays
visible on the left so the user retains visual context while browsing.

Both panels supply their own ultraThinMaterial backdrop and use a
custom title bar (replacing NavigationStack's auto-title on tvOS) so
the title styling and symmetric padding match across panels and
across pushed destination screens. The Menu button now pops the
quality panel's pushed Video/Audio/Subtitles detail screens before
dismissing the panel itself.

Removes the background Button from the focus tree while either panel
is open so D-pad left/right inside a row no longer escapes focus
into the player and triggers a seek. Initial focus is steered into
the first row programmatically since tvOS doesn't auto-focus inline
overlays the way it does for fullScreenCover.

Doubles the queue thumbnail size on tvOS (160x90) for readability at
the half-screen panel width.
2026-05-10 15:28:12 +02:00
Arkadiusz Fal
6e5714dd86 Fix tvOS MPV startup playback stability 2026-05-10 15:28:12 +02:00
Arkadiusz Fal
82d2830208 Refresh track list when advancing to next queued video
Player settings on tvOS showed the previous video's tracks after queue
advance because availableStreams was never cleared on a video change and
playQueuedVideo only seeded a single pre-resolved stream. Reset the list
on a new video, then fetch the full streams in the background for global
videos, and repoint state.currentStream/currentAudioStream to the
refreshed entries so the picker checkmarks land on the playing tracks.
2026-05-10 15:28:11 +02:00
Arkadiusz Fal
6a343311ea Add tvOS display frame rate and dynamic range matching
Lets the Apple TV switch its HDMI output to match the playing video's
frame rate and dynamic range via AVDisplayManager.preferredDisplayCriteria,
driven from MPV's container-fps and video-params/gamma. Two opt-in toggles
(default off) live under Playback → Display on tvOS; both are no-ops on
other platforms. Anchor an AVKit class symbol so the linker keeps AVKit
linked — Swift only autolinks AVFoundation here, and without AVKit the
UIWindow.avDisplayManager category isn't loaded at runtime.
2026-05-10 15:28:11 +02:00
Arkadiusz Fal
100e762d4b Suppress stale player error after switching videos mid-retry
If the MPV backend was retrying a failed load and the user switched to
another video before retries exhausted, the eventual error was published
to the player UI even though that video was no longer active. Guard the
catch block with a loadingVideoID check so stale errors are dropped.
2026-05-10 15:28:11 +02:00
Arkadiusz Fal
4935fbdb83 Remove tvOS close button from MPV debug stats overlay
Menu remote already dismisses the overlay via TVPlayerView's exit
command handler, so the in-overlay close button is redundant.
2026-05-10 15:28:11 +02:00
Arkadiusz Fal
c778ca5d06 Fix flaky integration tests and UI test runner robustness
- Skip Invidious integration tests gracefully on .noConnection so a
  transient instance outage no longer fails CI
- Point integration tests at i01.v.yattee.stream (the previous test
  instance was decommissioned)
- Force UTF-8 on AXe CLI output in the UI test wrapper; ASCII-tagged
  bytes were crashing JSON.parse in describe_ui
- Add iOS 26.4 visual baselines for app-launch-home and settings-main
2026-05-10 15:28:11 +02:00
Arkadiusz Fal
d0297a5e89 Fix tvOS MPV Options focus and Add/Edit sheet layout
Wrap pushed view in TVSidebarDetailContainer, list custom options
first so focus engages on a row, mark default options focusable so the
list scrolls. Replace toolbar-based Add/Edit sheets with padded VStacks
and inline confirm buttons. Cache customMPVOptions in @Observable
backing storage so writes refresh the list immediately.
2026-05-09 18:11:59 +02:00
Arkadiusz Fal
06ae5ac053 Round iOS player seek bar and show scrubber only while dragging
Clip the progress bar with a Capsule for neater edges, and reveal the
scrubber handle only during a drag with a spring zoom animation.
2026-05-09 16:54:45 +02:00
Arkadiusz Fal
d49591eaf4 Skip local-folder watches from iCloud sync 2026-05-09 15:53:54 +02:00
Arkadiusz Fal
c64f13a0e6 Show cached channel header on tvOS while channel loads
Render subscriber count, Subscribe button, and (for subscribed
channels) description in the tvOS loading state instead of just
avatar + name + spinner. Seed the in-memory author cache when
navigating to a channel from a video so the first-time channel
view has a name and avatar to display immediately.
2026-05-09 15:00:53 +02:00
Arkadiusz Fal
1f0f3a8cf0 Resume and seek when reopening currently-loaded video
When the same video was already loaded (typically paused), opening it
again via the URL scheme, a deep link, or a remote-control loadVideo
command did nothing — the player just stayed paused. Now the same-video
early-return path resumes playback if paused and seeks to the supplied
startTime, so timestamps from URLs and remotes are honoured even when
the video is already loaded.

URLRouter gains a parseTimestamp helper that reads t/time/start query
params in plain-seconds and YouTube-style (1h2m3s) forms, and the deep
link handler now forwards that timestamp through to openVideo.
2026-05-09 15:00:08 +02:00
Arkadiusz Fal
aabf5313fa Expose Background Playback toggle on tvOS, default off
Surfaces the existing iOS/macOS Background Playback setting in the tvOS
Playback settings, defaulting to off so audio stops when the user leaves
the app via the TV button. Pauses playback on .background/.inactive when
the toggle is off, regardless of audio route — the user's setting wins
over AirPlay/HomePod handoff. Also auto-shows the tvOS player controls
when returning to foreground so the paused state is immediately visible
and actionable.
2026-05-09 14:50:38 +02:00
Arkadiusz Fal
8a3f76bb1d Constrain tvOS details panel to right half of screen
Full-screen glass overlay was excessive on 4K displays and fully hid
the video. Wrap the panel in a GeometryReader sized to half the parent
width, slide it in from the trailing edge, and tighten internal
horizontal/top padding now that the content lives in a narrower column.
2026-05-09 14:35:00 +02:00
Arkadiusz Fal
42621b8193 Suppress tvOS Now Playing while AirPlay/HomePod route is active
On tvOS, registering MPRemoteCommandCenter handlers makes the system
classify the app as a long-form video media app. When audio is routed
to AirPlay 2 endpoints (HomePods), the system then enforces a ~2s
look-ahead buffer in the AVAudioSession → AirPlay 2 pipe for
multi-speaker sync. The result is a 2-3 second audio drain on pause
and refill on resume.

The buffer lives downstream of mpv, so no mpv command (ao-reload,
seek-flush, audio-add) can flush it; AVAudioSession setCategory
overrides (mode/policy) and setActive(false)/setActive(true) cycles
are also ignored once the app is media-classified.

Workaround: detect the active audio route via routeChangeNotification
on tvOS. While AirPlay/HomePod is the output, suppress
MPNowPlayingInfoCenter publication and disable every MPRemoteCommand
so tvOS un-classifies us. When the route returns to local outputs,
republish the cached Now Playing info and reconfigure remote commands.
A latch defers all media integration until the audio session has been
activated at least once, so no commands are registered before the
route can be evaluated.

Trade-off: while playing to HomePods, the Control Center widget and
external Siri Remote play/pause are not available — but pause/resume
is responsive.
2026-05-09 14:18:17 +02:00
Arkadiusz Fal
9287f5906d Make watched checkmark prominent on tvOS thumbnails
Bump glyph size, force white-on-black palette, and add a drop shadow so
the indicator stays readable on unfocused thumbnails from couch distance.
2026-05-09 11:35:46 +02:00
Arkadiusz Fal
9e13bffa8c Live-seek tvOS scrubber and auto-commit on idle
Throttle SELECT-based scrubbing to seek the underlying frame ~every
150ms instead of waiting 1s after pan-end, so the visible frame keeps
up with the scrub handle. Hide the redundant storyboard panel during
live scrub (the frame itself is now the preview) but keep the chapter
capsule visible. Storyboard panel still shown for D-pad arrow-seek
where the frame doesn't move until commit.

Auto-commit scrub mode after 3s of inactivity, matching
AVPlayerViewController behavior — playback resumes via the existing
scrub-pause wiring instead of staying paused indefinitely.
2026-05-09 11:24:46 +02:00
Arkadiusz Fal
80838db9cc Pause tvOS playback on seek bar scrub mode entry 2026-05-09 11:11:39 +02:00
Arkadiusz Fal
6173f63221 Prevent tvOS focus shadow from clipping between Home sections 2026-05-09 11:05:30 +02:00
Arkadiusz Fal
b6c3f0e71b Keep tvOS player controls visible on pause via on-screen button
Route the on-screen play/pause button through handlePlayPause() so it
follows the same visibility and auto-hide timer logic as the Siri Remote
hardware button: timer stops when paused (controls stay pinned) and
restarts on resume.
2026-05-09 11:00:04 +02:00
Arkadiusz Fal
7c1549ed35 Show watch progress bar on thumbnails in playlist, channel, and search views
These views rendered video thumbnails without passing watchProgress, so the
progress bar was silently missing. Apply the existing pattern from
SubscriptionsView: maintain a watchEntriesMap and forward watchProgress(for:)
to VideoRowView/VideoCardView at each call site.
2026-05-08 20:58:13 +02:00
Arkadiusz Fal
5ab9e3d5bf Surface mpv error details on stream load failure
Subscribe to mpv log messages and capture END_FILE error code/string so
load failures bubble up specific causes (HTTP 404/403, DNS failure,
demuxer errors) instead of a generic 10s timeout.
2026-05-08 20:43:27 +02:00
Arkadiusz Fal
f80ba26277 Enforce minimum 2 grid columns on tvOS 2026-05-08 20:04:29 +02:00
Arkadiusz Fal
5b9cd8c521 Dismiss tvOS sidebar detail pages when sidebar selection changes
tvOS's sidebarAdaptable TabView leaves the previously-pushed detail view
visible after the user picks another sidebar item, until they manually
press Menu. Broadcast a notification on tab change so any pushed
TVSidebarDetailContainer dismisses itself, and reset each tab's
NavigationPath. Also drop a redundant inner NavigationStack in the tvOS
SettingsView so subpages register on the tab's outer stack.
2026-05-08 19:35:36 +02:00