Commit Graph

3380 Commits

Author SHA1 Message Date
Arkadiusz Fal
bebb62068a Scroll mini player marquee with Core Animation on iOS and tvOS
The TimelineView re-rendered both mini player lines every frame (~17% of
the main thread on an iPhone 12 mini, even while paused). The text is now
drawn once in a hosted view and a repeating keyframe animation slides its
layer in the render server. macOS keeps the TimelineView.
2026-10-26 23:13:29 +01:00
Arkadiusz Fal
c09e66cb5a Cut foreground CPU while playing and paused
- Send Now Playing elapsed time once per 5-second bucket; the modulo check
  fired for every frame of that second (12 updates/s on average).
- Skip mpv renders while paused unless mpv requests a redraw (seek, new
  frame, PiP change, new load) instead of redrawing the same frame at
  display rate.
- Read playback time untracked in the progress bar and time labels while
  the controls are hidden, so they are not re-rendered on every tick.
- Only clear the current SponsorBlock segment when one is set.
2026-10-26 19:35:12 +01:00
Arkadiusz Fal
73d491c56b Read the Duo status column's occlusion regions from a full-bleed reader
SwiftUI only reports reserved regions that intersect the querying view,
and the subscriptions content stops at the trailing safe-area column, so
on device the channel rail saw no regions and ran up under the time and
Dynamic Island. A background reader that ignores the safe area now
records the regions in global coordinates for the rail placement.
2026-10-25 16:58:31 +01:00
Arkadiusz Fal
728e3b7286 Cut foreground CPU during audio-only playback
- MPVRenderView keeps its display link paused while the stream has no
  video, instead of rendering and presenting 60 empty frames a second
- Chapter segments get clamped played/buffered fractions and are
  equatable, so a time update re-renders only the chapter under the
  playhead instead of every segment
- The mini player pauses its scrolling titles while the expanded player
  covers it (iOS/tvOS)
- Performance logging no longer flags the first sample after a
  foreground/background transition as high CPU

With the expanded player open in audio-only mode on an iPhone 12 mini,
app CPU dropped from ~30% to ~12% and wakeups from ~128/s to ~37/s.
2026-10-25 14:22:26 +01:00
Arkadiusz Fal
9412ca916c Add performance logging and background diagnostics
Performance Logging (Developer settings) logs a resource sample every 5s
in the foreground and background: CPU by area and thread, wakeups, GPU,
memory, disk, activity counters as rates (mpv property updates, player
time publishes, renders, saves, network, images, CloudKit events, log
entries, main run-loop wakeups), SwiftUI body evaluations of the player
views, and app/player/battery context. Each foreground/background
transition logs a summary of the period. MetricKit metrics and
diagnostics (CPU exceptions, hangs) are logged whenever logging is on.

Background diagnostics (iOS) log lifecycle, audio session and power
events with an app/player/render snapshot and a heartbeat that detects
suspension gaps and main-thread stalls. Logs are persisted per session
in Caches/Logs and the previous session is included in exports, so logs
survive the app being swiped away.
2026-10-24 19:36:51 +02:00
Arkadiusz Fal
21a6238a16 Stop re-rendering hidden player UI during background playback
Every mpv time-pos tick updated PlayerState.currentTime and bufferedTime,
re-rendering the invisible player controls, progress bar and chapter
segments several times per second while the app played in the
background, which drained the battery during long audio-only sessions.

- PlayerState keeps currentTime/bufferedTime current for readers but
  stops notifying observers while publishesTimeUpdates is off;
  PlayerService turns it off on entering the background and back on
  (with one catch-up notification) before returning to the foreground
- MarqueeText pauses its animation timeline outside the active scene
- Replace the 100 ms polling loops in RemoteControlCoordinator and
  ToastManager (iOS/tvOS) with observation-driven waits

Main-thread samples during 30 s of background audio-only playback on an
iPhone 12 mini went from 1289 to 248.
2026-10-24 17:32:34 +02:00
Arkadiusz Fal
2a49a5db68 Use production CloudKit environment for Xcode builds
Development-signed builds now opt into the Production CloudKit
environment via com.apple.developer.icloud-container-environment,
driven by a CLOUDKIT_ENV build setting. A new Debug-CKDev configuration
and "Yattee (CloudKit Dev)" scheme switch to the Development
environment for schema work.

The DEV badge and banner in iCloud settings now reflect the actual
environment (YatteeCloudKitEnvironment Info.plist key) instead of
#if DEBUG, and the sync engine logs the environment at startup.
2026-10-23 21:47:34 +02:00
Arkadiusz Fal
c60f483d3c Raise Open Link URL limit from 20 to 200 2026-10-23 21:23:23 +02:00
Arkadiusz Fal
fb4da41877 Fix player brightness controls on iPhone Duo
Brightness controls used UIScreen.main, which on iPhone Duo can be a
different display than the one hosting the player. Route all brightness
reads and writes through PlayerBrightness, which targets the screen of
the window hosting the controls.
2026-10-22 23:16:11 +02:00
Arkadiusz Fal
52774fc7cc Rework GitHub issue templates to ask for logs and reproduction details 2026-10-22 21:58:22 +02:00
Arkadiusz Fal
55b6c63da1 Publish AltStore source to dl.yattee.stream/altstore.json 2026-10-22 14:45:02 +02:00
Arkadiusz Fal
8ca44948cc Fix AltStore source icon URL path case 2026-10-22 14:31:30 +02:00
Arkadiusz Fal
3523e3f939 Fix AltStore source: drop empty headerURL, declare app group and local network permissions 2026-10-22 14:29:08 +02:00
github-actions[bot]
f7a6d89aff Update AltStore source for 2.0.0 (275) 2026-10-22 10:52:50 +00:00
Arkadiusz Fal
899be5bd22 Bump build number to 275 2.0.0-beta.275 2026-10-22 08:41:47 +02:00
Arkadiusz Fal
1c57a55fe1 Return to Home when the selected sidebar source is removed 2026-10-22 07:58:09 +02:00
Arkadiusz Fal
532e713322 Keep the separate macOS player window open when opening a link 2026-10-22 07:14:32 +02:00
Arkadiusz Fal
983e7679a9 Bump build number to 274 2026-10-21 22:17:37 +02:00
Arkadiusz Fal
f6dd827d11 Update CHANGELOG 2026-10-21 22:17:01 +02:00
Arkadiusz Fal
b058474d31 Skip the queue sheet when the current video has finished
Tapping a video after the current one played to the end asked Play Now or Play Next, though replacing a finished video loses nothing. An ended video no longer counts as active playback; a non-empty queue still shows the sheet.
2026-10-21 22:16:23 +02:00
Arkadiusz Fal
582aa56644 Add keyboard shortcuts to toggle corner layout panels
Playback menu items toggle the info panel below the video (⌥⌘I), the
side column (⌥⌘S) and both at once (⌥⌘P) while the corner layout is
active. Showing one region from the hidden state brings back only that
region; hiding the last visible one hides the whole panel.
2026-10-21 22:06:27 +02:00
Arkadiusz Fal
1fe7caa6bc Keep animated video resizes smooth on iOS
Every layout pass with a new size recreated the framebuffer and rendered
synchronously on the render queue, blocking the main thread behind the
in-flight render. Animated layout changes (corner layout panel toggles,
rotation) dropped to a few frames per second. Size changes arriving in
quick succession now let the layer stretch the last frame and recreate
the framebuffer once the size settles; one-off resizes still recreate
immediately.
2026-10-21 22:01:37 +02:00
Arkadiusz Fal
582984f909 Fill the whole page with the opaque background on macOS
Content-sized pages like the Search empty state only got the opaque
window background behind their own frame, leaving darker window
material on both sides.
2026-10-21 20:36:47 +02:00
Arkadiusz Fal
e276c99d0b Allow pinch-to-panscan on the video in the corner layout
The window-level pinch was blocked whenever the landscape panel was
pinned and visible (the corner layout always counts as pinned) or a
stale portrait-panel flag was set. In the corner layout the pinch now
starts when it begins inside the video region, and the panscan
control button is enabled there too.
2026-10-20 23:43:47 +02:00
Arkadiusz Fal
b98cc216ad Build releases on the Xcode 27.1 runner
The iPhone Duo hinge and status-column code uses iOS 27.1 SDK APIs, which
the macos-26 image (Xcode 26.6 max) cannot compile. Build jobs move to the
xcode-27 runner and select the 27.1 RC by full path, since the
Xcode_27.1.app symlink is ambiguous with the beta and App Store Connect
rejects beta-built uploads.
2026-10-20 23:24:53 +02:00
Arkadiusz Fal
3f3cc56cf8 Bump build number to 273 2026-10-20 22:49:52 +02:00
Arkadiusz Fal
14912f0203 Configure comments from their row in details panel settings
The separate comments group (pill mode, author avatars) is gone: comments
are a row in the pages/sections list whose editor holds both options.
Inline-sections hosts list comments as a locked pill row at the bottom;
a foldable iPhone in the corner layout lists real pages, including
comments and queue in the side column.
2026-10-20 22:26:21 +02:00
Arkadiusz Fal
f9c448b0aa Show comment avatars from newer Invidious versions
Newer Invidious returns a single authorThumbnail URL on comments instead
of the authorThumbnails array, so every comment fell back to the
placeholder avatar. Decode authorThumbnail as a fallback.
2026-10-20 20:31:18 +02:00
Arkadiusz Fal
cf880ab84b Add Copy Link to the macOS video context menu (#972)
SwiftUI's ShareLink menu on macOS offers no Copy Link item, so there was
no way to copy a video URL from a context menu. Add a macOS-only Copy
Link action next to Share.
2026-10-20 19:10:20 +02:00
Arkadiusz Fal
c4272c86f7 Start playlist Play at a random video in shuffle mode (#988)
Play always started from the first video, so shuffle only affected the
rest of the session. When the queue mode is Shuffle, rotate the list to
a random start so the remaining videos stay queued.
2026-10-19 23:26:41 +02:00
Arkadiusz Fal
4d8ba7e028 Invalidate the subscription feed when subscriptions change (#968)
Subscribing or unsubscribing, locally or via iCloud, only posted a
notification, so the cached feed stayed valid and Yattee Server never
received the new channel list until the cache expired. Invalidate the
feed cache on every subscription add and removal, and skip the disk
write when it is already stale.
2026-10-19 22:54:55 +02:00
Arkadiusz Fal
433e57e68d Recover downloads from an invalidated URLSession (#975)
Toggling cellular downloads recreated the background session with the
same identifier before the old one finished invalidating, and nothing
ever replaced a dead session, so every download threw "Task created in
a session that has been invalidated" until relaunch. Wait for the old
session's invalidation before creating the new one, rebuild the session
when the delegate reports the live one invalid, and recreate it once
when task creation hits the invalidated exception.
2026-10-19 22:31:52 +02:00
Arkadiusz Fal
4c7ccb83a6 Keep unsent CloudKit changes across Sync Now (#978)
refreshSync() discarded the persisted engine state and the pending
buffer, and the engine state is where queued saves and deletions live,
so tapping Sync Now within the debounce window dropped history,
progress and bookmark changes. Carry the old engine's pending changes
into the buffer that the new engine adopts, then schedule a send.
2026-10-19 21:45:45 +02:00
Arkadiusz Fal
ef372b65d9 Keep Settings in the tvOS sidebar (#986)
tvOS has no other route to Settings, so unchecking it in Sidebar settings
locked users out. Treat Settings as a required sidebar item on tvOS; an
already-stored hidden value is ignored because required items bypass the
visibility map.
2026-10-19 21:43:46 +02:00
Arkadiusz Fal
13eb89abc0 Report video as the Now Playing media type (#969)
NowPlayingService set the media-library key MPMediaItemPropertyMediaType,
which MediaRemote ignores, so external clients classified playback as
audio. Use MPNowPlayingInfoPropertyMediaType instead.
2026-10-19 18:02:42 +02:00
Arkadiusz Fal
f5e5202d79 Bump build number to 272 2026-10-18 23:49:01 +02:00
Arkadiusz Fal
827d10eda7 Focus the delayed tvOS Cancel when it appears
By the time the button shows, focus has already been lost, and the
focus engine is not guaranteed to pick up an element inserted later.
2026-10-18 23:30:22 +02:00
Arkadiusz Fal
22ec48af3d Clear a stopped-load error when the playlist arrives anyway
A remote playlist load cancelled on tvOS can still land. If the playlist
it brings is empty, playlistBody showed the stale "Loading was stopped."
instead of the empty state.
2026-10-18 23:23:06 +02:00
Arkadiusz Fal
c19be0a0ef Give the empty tvOS import screens a focusable Done button
With nothing to import, the playlists and subscriptions import screens
had no focusable content; only the container's toolbar Done, which is
not a dependable focus target on tvOS.
2026-10-18 23:15:16 +02:00
Arkadiusz Fal
88551a1366 Offer a delayed Cancel while a tvOS remote playlist loads
A remote playlist this account does not own showed only a spinner while
loading, with or without a cached header, so the tvOS remote had nothing
to focus. Cancel stops at the error view and its Retry, since a sidebar
playlist is a tab root that cannot be popped.
2026-10-18 19:51:09 +02:00
Arkadiusz Fal
270e5ffdd3 Offer a delayed Cancel while a tvOS channel loads, and fill the blank state
Without a cached header the channel showed only a spinner, and when the
channel request failed while the videos request succeeded (no cache to
build the channel from) it rendered nothing at all; both left the tvOS
remote with nothing to focus. Cancel stops at the error view and its
Retry, since a sidebar channel is a tab root that cannot be popped; the
blank state now shows the load error with Retry on every platform.
2026-10-18 09:04:48 +02:00
Arkadiusz Fal
dd6bbbe691 Offer a delayed Cancel while the tvOS video page loads
The video page showed only a spinner until the video loaded, and
rendered nothing at all in the frame before a videoID load started;
either left the tvOS remote with nothing to focus. Both now show the
spinner with the delayed Cancel, which pops the page.
2026-10-17 23:49:13 +02:00
Arkadiusz Fal
70cda3fa45 Offer a delayed Cancel while tvOS external video extraction loads
A screen showing only a spinner has nothing focusable, so a slow or hung
extraction stranded the tvOS remote. Add TVStalledLoadCancelButton, a
Cancel that appears under a loading spinner after three seconds so
normal loads never flash it, and use it for external video extraction.
2026-10-17 21:57:56 +02:00
Arkadiusz Fal
b613b8dbc8 Keep the tvOS media browser header visible while loading
The Refresh and View Options header was hidden until the first listing
arrived, leaving only a spinner; an SMB or WebDAV connection can hang
for a long time with nothing the remote could focus. Refresh ignores
presses while a load is in flight.
2026-10-17 20:22:32 +02:00
Arkadiusz Fal
f393d2b9cc Give the tvOS network share discovery sheet a focusable Cancel
The toolbar Cancel is iOS/macOS only, so while scanning with no shares
found yet (and when discovery is unavailable) the tvOS sheet had nothing
the remote could focus.
2026-10-17 15:23:00 +02:00
Arkadiusz Fal
eec39445b4 Give the empty tvOS translators screen a focusable Done button
If the bundled translator list fails to load the screen is a bare
ContentUnavailableView, leaving the tvOS remote nothing to focus.
2026-10-17 08:59:14 +02:00
Arkadiusz Fal
34cec578ad Show an empty contributors list as a placeholder with Retry
A successful load with no contributors, and the first frame before the
load started, rendered an empty form with nothing focusable, stranding
the tvOS remote.
2026-10-17 07:01:21 +02:00
Arkadiusz Fal
4fe7cceda5 Give the empty tvOS PeerTube instance browser a Retry button
When the directory returned no instances without an error, the tvOS
search header was hidden and the no-results state had nothing focusable.
2026-10-16 23:59:30 +02:00
Arkadiusz Fal
1f7c471fcc Give the tvOS media-source-not-found screen an Open Sources button
A media source destination whose source was removed rendered a bare
ContentUnavailableView, so the remote had nothing to focus.
2026-10-16 23:59:09 +02:00
Arkadiusz Fal
9002b253d0 Give the tvOS downloads placeholders a focusable Done button
yattee://downloads still reaches the downloads destination on tvOS,
where it renders a bare ContentUnavailableView with nothing focusable,
leaving the remote stuck.
2026-10-16 23:58:58 +02:00