Xcode 27.2's altool logs an ERROR line about the uploader resume file even
when the upload succeeds, which made upload_to_testflight fail before
setting the changelog and distributing to external groups.
TestFlight crashpoint "_hasTableWithName: + 12" (0xdead10cc) kills the
app ~1s after launch while ModelContainer.init runs WAL recovery from
YatteeApp.init(), before UIApplicationMain. That init also runs during
prewarm launches that iOS can suspend, and the suspended process holds
the store's SQLite lock. In some reports TipKit was opening its own
store from Tips.configure at the same time.
On iOS, DataManager now opens its store lazily, and AppDelegate's
didFinishLaunching configures TipKit and opens the store inside an
OpenDataStore background task. macOS and tvOS keep opening at init.
In-memory stores (tests, previews) open immediately.
Instrumentation: launch logs record prewarm, launch options, whether
the background task was granted, and open duration and trigger. Debug
iOS builds assert before opening if anything touches the store ahead
of didFinishLaunching.
This is a mitigation, not a confirmed fix; watch the crashpoint in the
next build. Moving the store out of the app group is deliberately not
done.
With Enable Logging on, MPVBackend watches hwdec-current (property event
plus a single-property poll in the stats loop, since mpv does not always
emit an event for a silent fallback). When a file that was hardware
decoded drops to software, it logs a warning with codec/stream, playback
position, scene phase and time since the last change, thermal/low-power
state, PiP and the recent mpv log lines, and shows a toast asking to
export logs (deferred until the app is active again).
The background diagnostics heartbeat now includes the decoder state.
The heat in the iOS 27.2 TestFlight report (~36% CPU for 85 minutes with
playback paused) started right after an audio route change while paused:
mpv's ao_avfoundation logged "restarting due to system notification"
and an unnamed thread went to ~36% immediately, still in the foreground.
Its restart handler calls stop() then start() regardless of pause state;
start() sets the synchronizer rate to 1 and requests media data, but
paused mpv supplies no samples, so the empty renderer re-requests in a
tight loop and the running renderer keeps iOS from suspending the app.
When that log line arrives and mpv is paused, issue ao-reload so the AO
is rebuilt paused. (The display link guard in the previous commit did not
reproduce the spin when tested on a device.)
A TestFlight log (iOS 27.2, build 276) showed ~36% CPU on an unnamed
thread for 85 minutes with playback paused in the background, heating
the phone. A lock during a quick inactive/active bounce rebuilt the
player container after the app had entered the background, and its
resumeRendering() un-paused the display link there; heartbeats showed
displayLink=running@60 inBackground=true for the whole period. An
earlier identical rebuild in the foreground left background CPU at 0.4%.
resumeRendering() now ignores calls in the background (unless PiP is
rendering) and a display link created there starts paused;
appDidBecomeActive resumes it.
On iPhone Duo the page first lays out at full width, then narrows for the
status column, and the width-change re-snap animated from the first card
to the tapped one. Only animate the re-snap when the current card was
already on screen at the old width.
Pausing rendering in the background left mpv decoding every frame
(hardware decode plus copy back) only to drop it, keeping a 1080p video
at ~15% CPU with the screen off. After 5 s in the background without PiP
the video track is now disabled (vid=no); returning to the foreground
re-enables it and seeks exactly to the current position, which keeps
audio and video in sync (avsync 0.000 vs -0.044 and 22 drops without the
seek). Streams loaded while backgrounded start with video disabled.
Background CPU drops to ~3.7% on an iPhone 12 mini.
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.
- 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.
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.
- 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.