Commit Graph

2778 Commits

Author SHA1 Message Date
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
Arkadiusz Fal
10bd7d09af Use black icons on focused tvOS player control buttons for legibility 2026-05-08 19:03:35 +02:00
Arkadiusz Fal
765d322ee1 Use default foreground color for tvOS home section titles 2026-05-08 18:54:14 +02:00
Arkadiusz Fal
c8e716be94 Match tvOS play button background to prev/next transport buttons 2026-05-08 18:52:23 +02:00
Arkadiusz Fal
9b85ae2b13 Lock macOS player window resize to video aspect ratio 2026-05-08 18:32:43 +02:00
Arkadiusz Fal
b7b7c5ac62 Fix local folder playback after app container UUID changes
After iOS reinstall/restore the app container UUID rotates, which left both
the persisted source.url and the security-scoped bookmark pointing at a
no-longer-current path. Files derived a stale absolute path that got appended
onto the resolved bookmark, producing doubled URLs that MPV could not load.

- Resolve the base URL by picking whichever of the bookmark or source.url
  actually exists on disk.
- Compute MediaFile relative paths against the resolved root so they survive
  later container changes.
- Hold the security-scoped resource access for the source's lifetime via a
  shared resolver, so MPV can open files long after the directory enumeration
  that resolved the bookmark has returned.
- Normalize legacy absolute paths embedded in old recents/history video IDs
  so they re-resolve under the current container.
2026-05-08 18:23:16 +02:00
Arkadiusz Fal
5d88ed9743 Skip Sparkle strip in Debug to keep incremental builds working
Removing the embedded framework after every build broke Xcode's
incremental copier on the next build (it would try to copy individual
files into a destination directory tree that no longer existed). The
strip is only meaningful for App Store-bound Release builds, so skip
it in Debug — Sparkle in a local debug bundle is harmless.
2026-05-08 07:42:26 +02:00
Arkadiusz Fal
df0f144ced Make Strip Sparkle build phase resilient to rm -rf races
The post-embed strip script was intermittently failing with "Directory
not empty" when rm -rf raced against codesign/Spotlight/Xcode touching
the freshly-embedded framework. Atomically rename the framework out of
the embed path first, then rm -rf with a short retry loop, so a clean
build is no longer needed to recover.
2026-05-08 07:40:32 +02:00
Arkadiusz Fal
b163864628 Add interactive swipe-to-dismiss for iOS toasts
Toast cards now follow the finger upward and dismiss on either a
sufficient drag or a fast flick (via predicted-end translation). The
auto-dismiss timer pauses while the user is dragging and re-arms if
they release without dismissing.
2026-05-08 03:04:32 +02:00
Arkadiusz Fal
4f763373c1 Fix tvOS pickers 2026-05-08 02:18:25 +02:00
Arkadiusz Fal
c2758b0d0c Use light glass background for tvOS player control buttons 2026-05-07 18:36:47 +02:00
Arkadiusz Fal
c8bb13e229 Show playback failure overlay on tvOS
Previously a failed video left the user staring at a black screen / thumbnail
with no indication anything went wrong — playbackState went to .failed but
TVPlayerView never read it. Add a focusable glass overlay (Details / Retry /
Play Next or Close) gated on isFailed and a parallel one for retry-exhausted
state, with the regular controls and background tap-target disabled while
either is visible so focus stays inside the overlay. Hide the Copy/Share
toolbar items in ErrorDetailsSheet on tvOS where they aren't useful.
2026-05-07 18:29:48 +02:00
Arkadiusz Fal
158d518e3a Trim comments and hoist settings read in stream filtering
Drop comments restating what the code shows; hoist allowSoftwareDecodedFormats
out of the recommendedVideoStreams filter closure so the bridge property is
read once per render instead of once per stream.
2026-05-07 18:03:34 +02:00
Arkadiusz Fal
16477641ab Add Allow Software-Decoded Formats playback setting
Lets the auto stream selector pick formats whose codec isn't hardware
decoded on the current device. Defaults off; when on, 4K VP9/AV1 can be
auto-selected on Apple TV models without those decoders. Software-decoded
streams also move into the Recommended section so the selection stays
visible without enabling advanced stream details.
2026-05-07 18:00:14 +02:00
Arkadiusz Fal
823faee012 Add Show Sidebar toggle to tvOS Subscriptions view
Mirrors the existing iOS/macOS option using the shared
subscriptionsShowSidebar AppStorage key. When the sidebar is hidden
the View Options button moves above the feed so it remains reachable.
2026-05-07 06:57:06 +02:00
Arkadiusz Fal
6673d478c2 Hide feed channel filter strip on tvOS
The horizontal channel chip strip looks awkward on tvOS and the focus
interaction is clunky. Drop it from the tvOS branch of InstanceBrowseView;
other platforms keep it.
2026-05-07 06:32:14 +02:00
Arkadiusz Fal
51108738aa Add press-and-hold continuous seek on tvOS d-pad
The Siri Remote's left/right d-pad only delivered a single discrete
seek per click — holding the button did nothing. A window-level custom
UIGestureRecognizer now tracks the actual press duration and drives a
repeating seek tick (10s → 20s → 30s acceleration) until release,
routing through the existing accumulating-seek paths so the scrubber
preview, debounced commit, and on-screen feedback all keep working.
2026-05-07 06:18:40 +02:00
Arkadiusz Fal
cc109043b3 Unstick more tvOS focus dead-ends in channel views
Settings → Notifications → Manage Channels: wrap the tvOS NavigationLink
destination in TVSidebarDetailContainer(showsDismissButton: true) so the
no-subscriptions, error, and loading states all have a focusable Done.

Channels sidebar tab: lift the tvOS search field + View Options button
out of the loaded-channels branch and render it above every state. The
empty state previously had zero focusable elements, leaving the right
pane blank when swiping in from the sidebar.
2026-05-06 23:01:02 +02:00
Arkadiusz Fal
39beb45cff Make tvOS detail dismiss button opt-in and unstick more views
TVSidebarDetailContainer now exposes a showsDismissButton flag instead of
always attaching a Done toolbar item. The button is only enabled where a
view can end up with no focusable element on its own — Device
Capabilities (informational rows) and the Import Playlists/Subscriptions
flows.

Wrap Contributors, Translators, Acknowledgements, and Device Capabilities
destinations in TVSidebarDetailContainer for the consistent sidebar look,
and make the Translators/Acknowledgements rows focusable on tvOS by
wrapping them in Buttons so the Menu remote button can pop the stack.
2026-05-06 22:41:46 +02:00
Arkadiusz Fal
5c7429abf3 Fix tvOS soft-lock in import views when no rows are focusable
When all playlists/subscriptions were imported, every row collapsed to a
non-focusable checkmark and the Add All toolbar item disappeared, leaving
the view with no focusable element. The Menu button then closed the app
instead of popping the navigation stack.

Wrap the import destinations in TVSidebarDetailContainer for visual
consistency and add a Done toolbar item (cancellationAction) that is
always present on tvOS, reachable from any list row via swipe-up.
2026-05-06 22:17:08 +02:00
Arkadiusz Fal
38242edf0c Present instance login as full-screen cover on tvOS
The .sheet rendering on tvOS produced a tiny floating modal where the
"Sign In" title wrapped onto two lines and form fields overflowed. Use
.fullScreenCover on tvOS and wrap the login form in
TVSidebarDetailContainer so the title/icon sit in the standard 400pt
left sidebar. iOS and macOS keep the existing sheet presentation.
2026-05-06 21:44:55 +02:00
Arkadiusz Fal
411fcba037 Surface clearer error when adding a Piped frontend URL
Pointing AddRemoteServer at a Piped Vue SPA (e.g. the frontend host
rather than the API host) used to fail with a generic
"could not detect instance type" — every JSON probe got the same
index.html back. On the failure path, fetch `/` once more and look
for `<title>Piped</title>`; if matched, return a new
`pipedFrontendDetected` error that tells the user to enter the
Piped API URL instead.
2026-05-06 21:14:50 +02:00
Arkadiusz Fal
e3f4d764cc Add UI smoke test for Piped authenticated endpoints
Adds a Piped instance, logs in, and exercises the two settings flows that
hit the regressed endpoints directly — Import Subscriptions (/subscriptions)
and Import Playlists (/user/playlists). Asserts that "session is a required
parameter" never appears in the AX tree, catching the recent header-vs-query
auth regression end to end.

Promotes three tree-walking helpers (id_in_tree?, id_with_prefix_in_tree?,
label_in_tree?) onto UITest::Axe so the spec can fetch the AX tree once per
poll iteration and run all checks against it locally — roughly 6× fewer
`axe` subprocess spawns than calling element_exists? / text_visible? per
check, and a primitive other specs can reuse.
2026-05-06 20:17:28 +02:00
Arkadiusz Fal
6f8aa9a1b3 Block HTTP Basic Auth proxy for Piped sources
Piped's session token reuses the Authorization header, so a fronting basic
auth proxy can't coexist with logged-in Piped use — the two would clobber
each other's credentials on every authenticated request.

Add a supportsHTTPBasicAuthProxy capability on Instance/InstanceType (false
for Piped, true for everything else) and route it through:

- AddRemoteServerView refuses Piped if detection only succeeded behind basic
  auth, surfacing a localized "not supported" error instead of a silently
  broken instance, and hides the optional credentials section for Piped.
- EditSourceView hides the basic auth fields for Piped instances and clears
  any legacy stored credentials on save, in case a Piped source was added
  with credentials before this change.
2026-05-06 20:17:18 +02:00
Arkadiusz Fal
11841d7b41 Send Piped session token in Authorization header again
Commit aed78c13f moved the session token from the Authorization header to a
?authToken= query parameter on /subscriptions, /subscribe, /unsubscribe,
/user/playlists, and /playlists/{id}. Piped's backend only accepts the
?authToken= form on /feed; every other authenticated route reads it from
Authorization (the Java handler names the variable "session", which is why
the rejected requests returned "session is a required parameter"). Restore
the header form for those five routes and leave feed alone.
2026-05-06 20:17:09 +02:00
Arkadiusz Fal
fac297e4d6 Cache and prewarm Invidious proxy auto-detection
The proxy auto-detect path (when proxiesVideos is off) HEADed a
googlevideo URL with a 5 s timeout on every video. The verdict is a
property of the network, not the video, so the cost was paid for no
reason on videos 2..N. On a network where the CDN is blocked the full
5 s timeout was added to playback startup every single time.

Two changes:

1) ProxyDetectionCache (actor, per-instance, 10 min TTL). First miss
   pays the HEAD once and caches the verdict; subsequent videos hit
   the cache synchronously. Concurrent callers share one in-flight
   probe. The last-seen sample CDN URL is retained so future probes
   don't need a fresh URL from the current video.

2) PlayerService kicks off InvidiousAPI.prewarmProxyDetection() in
   parallel with the videoWith... API call. By the time streams come
   back, the verdict is usually already cached and proxyStreamsIfNeeded
   is a sync lookup. Cheap when there's nothing to prewarm.

Cache invalidation:
- on InstancesManager.update (URL change, proxy toggle flip)
- on InstancesManager.remove
- TTL covers the network-change case for now (no NWPathMonitor yet)
2026-05-04 08:04:55 +02:00
Arkadiusz Fal
93240b4314 Wire Yattee Server playback through /proxy/relay when proxiesVideos is on
The Sources -> Edit Source -> Proxy toggle now renders for Yattee
Server entries (supportsVideoProxying gains .yatteeServer). When the
toggle is on, playback fetches go through
videoWithProxyStreamsAndCaptionsAndStoryboards with mode .relay, so
the server returns signed /proxy/relay URLs (byte-relay, supports
HTTP Range, no on-disk caching). Downloads keep going through mode
.download so the server-side /proxy/fast/ flow continues to cache
files for repeat use.

InvidiousAPI.proxyStreamsIfNeeded early-returns for .yatteeServer
since proxying is now done at fetch time via ?proxy=true rather than
client-side host rewriting.
2026-05-04 08:01:51 +02:00
Arkadiusz Fal
73e3d8164b Keep macOS play pause control visible 2026-04-24 01:14:13 +02:00
Arkadiusz Fal
8d85749354 Show playlist title in macOS toolbar 2026-04-24 00:28:31 +02:00
Arkadiusz Fal
3b9144cd28 Fix macOS sidebar dynamic item switching 2026-04-23 23:19:39 +02:00
Arkadiusz Fal
85223894ff Improve macOS channel toolbar header 2026-04-23 23:10:37 +02:00
Arkadiusz Fal
6df80c0e79 Use user-selected accent color in Home, Subscriptions, and Downloads
Color.accentColor and .foregroundStyle(.tint) resolve to the asset
catalog accent on macOS, so Home shortcut cards, section header
links, the Subscriptions "All channels" header, and the Downloads
per-channel group headers stayed blue when the user picked a
different accent. Read the color from SettingsManager and apply it
directly, matching the pattern already used for the Play button.
2026-04-23 22:55:48 +02:00
Arkadiusz Fal
fd0eab7784 Prefetch fresh video thumbnail before swapping it into info view 2026-04-23 18:37:19 +02:00
Arkadiusz Fal
6eb215f59c fixup! Restore Settings as sidebar item on macOS 2026-04-23 18:26:21 +02:00
Arkadiusz Fal
664eeadba2 Stabilize Nuke cache key across rotating thumbnail URL tokens 2026-04-23 18:22:03 +02:00
Arkadiusz Fal
20b88a811e Use user-selected accent color for Play button tint on macOS 2026-04-23 18:07:58 +02:00
Arkadiusz Fal
a32582e171 Replace macOS video nav arrows with left/right keyboard shortcuts 2026-04-23 18:06:47 +02:00
Arkadiusz Fal
cda983651e Restore Settings as sidebar item on macOS
Remove the gear toolbar button that opened Settings as a sheet in the
NavigationSplitView sidebar column, and drop the macOS guard hiding
.settings from SidebarMainItem so it can be added to the sidebar and
rendered in the detail column like other items. The dedicated Settings
window (Cmd+,) is unchanged.
2026-04-23 18:00:42 +02:00
Arkadiusz Fal
b23dfde602 Use macOS-native styling and larger text in video info view 2026-04-23 07:58:01 +02:00
Arkadiusz Fal
e0ad43ca0b Move Integrations into main settings section above Advanced
Drop the standalone iOS section for Integrations and inline its row into
the main list right above Advanced Settings. Swap the tvOS sidebar order
so Integrations appears before Advanced as well. macOS was already
correctly ordered via SettingsSection enum declaration.
2026-04-23 07:39:03 +02:00
Arkadiusz Fal
f804cc1521 Rename YouTube Enhancements settings to Integrations
Also swap the icon to puzzlepiece.extension, which better conveys that
this section houses third-party service hookups (SponsorBlock, Return
YouTube Dislike, DeArrow, short-link resolution) rather than being
YouTube-specific.

Hide the Resolve Short Links toggle on tvOS — there's no way to tap
inline description links or reach a system browser there — and tighten
the openInSystemBrowser platform guards so the iOS-only UIApplication
path isn't compiled on tvOS.
2026-04-23 07:34:31 +02:00