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.
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.
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.
NowPlayingService set the media-library key MPMediaItemPropertyMediaType,
which MediaRemote ignores, so external clients classified playback as
audio. Use MPNowPlayingInfoPropertyMediaType instead.
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.
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.
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.
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.
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.
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.
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.
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.
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.
yattee://downloads still reaches the downloads destination on tvOS,
where it renders a bare ContentUnavailableView with nothing focusable,
leaving the remote stuck.
The batch buttons are disabled until a video resolves, and loading or
failed rows are not focusable, so with every link still loading or
failed the remote was stuck.
The not-found state had nothing focusable, and an empty remote playlist
this account does not own showed no left-column buttons either, so the
remote was stuck. Not found offers Retry (remote) or Open Playlists
(local); the empty remote playlist offers Refresh.
A remote playlist arrives with its title, so the body always rendered
playlistContent and the errorView branch was never reached: a failed
load showed the empty-playlist placeholder and no way to retry.
The toolbar Done is iOS/macOS only, so after the last legacy account or
source was imported or removed the tvOS screen had nothing focusable and
the remote was stuck.
With nothing in progress the screen showed only a ContentUnavailableView,
leaving the focus engine nowhere to land and the remote stuck. Search is
the target because it always has a focusable field.
With no accounts the screen showed only a ContentUnavailableView, so the
focus engine had nothing to land on and the remote could not reach the
sidebar again; the app had to be force-quit. Sources is also where an
account is signed in.
One build 270 crash log aborts in talloc (talloc_abort_unknown_value)
inside libsmbclient name resolution while a media-source download opens
a file through SMBBridge, with FFmpeg reading an smb:// file on another
thread at the same time.
libsmbclient keeps its talloc stackframe behind a table of thread
functions, and none was installed (this build does not export
smbc_thread_posix()), so every thread shared one stackframe and the two
users freed each other's allocations. Install a pthread implementation
of smb_thread_functions at load time, before any libsmbclient use,
including FFmpeg's. The struct layout matches what talloc_stack and
smb_threads in the bundled library read (get_tls at +0x30, lock_mutex
at +0x10).
Four build 270 crash logs abort inside the GL driver
(GLDContextRec::flushContextInternal) from presentRenderbuffer, reached
from the immediate render queued by appDidBecomeActive/resumeRendering.
Only displayLinkFired checked isInBackground; renders dispatched
directly to the render queue could run, or present, after the app was
backgrounded again, where GPU work is not permitted.
Check isInBackground at the top of performRender and again before
presenting. PiP rendering is unaffected.
17 of the build 270 crash logs are 0xdead10cc kills on the main thread
inside DataManager.save(), every one reached from the 5-second
PlayerService progress-save timer. During background audio the timer
keeps writing; when playback stops the app can be suspended mid-save,
still holding the SQLite lock in the shared app-group container.
Wrap the periodic save in a background-task assertion when the app is
not active, and skip the write when the position has not moved since
the last save, so a paused player stops writing every five seconds.
The remaining six 0xdead10cc logs die in DataManager.init recovering
the WAL during a background launch. They are most likely the aftermath
of these dirty kills, so store opening is left as is.
watchEntriesMap() built its dictionary with uniqueKeysWithValues, but the
same video ID can be recorded under more than one source scope, which
trapped on the duplicate key (3 crash logs from build 270, all in
SearchView.loadWatchEntries). Keep the most recent entry instead.
The subscription metadata map in Manage Channels had the same shape (a
channel subscribed under several account scopes), as did the server
history hydration map; both now tolerate duplicates.
The carousel's lazy stack kept the old widths of cards it had laid out
off-screen, so after a resize (tab bar to split view sidebar on iPad,
rotation) every later card sat at a stale offset and the current card
landed beside the viewport. The stack now takes a new identity per
card width, and the existing re-snap aligns the current card.
Swapping between the tab bar and the sidebar also rebuilds the page,
which reset the carousel to the video it was opened on. The page's
position and continuation-loaded videos now live on its queue context,
fresh for every push, so a rebuilt page resumes where it was.
The detail column absorbs its NavigationStack, so the mini player inset
on the stack only reached the root page and disappeared on push. Float
it over the split view, past the sidebar, and have every page reserve
its height as bottom safe area.
The corner preset's landscape fallback opened the panel whenever the
device could host the corner layout. On the iPhone Duo exterior display
the player falls back to the widescreen layout, so the floating panel
opened on every rotation into landscape. Resolve the fallback from
whether the corner layout fits the current size instead.
On the iPhone Duo exterior display in landscape with the camera in the
top corner, the window's 82pt top safe area comes only from the camera
and status items block, and dropped the whole top bar below it. When
everything occluding the top safe area sits in one corner, the top bar
stays at the edge and the corner adaptation offset clears the block.
The top bar no longer adds its own leading cutout clearance on top of
that offset, and cutouts only count when they reach into a bar's row.
On the iPhone Duo exterior display in landscape the camera cutout sits
in the bottom trailing corner, outside the safe area, and covered the
rotate button and the end of the progress bar. The top and bottom bars
now take extra padding on the side where an occlusion reserved region
reaches into them. Regions spanning the middle, like a portrait Dynamic
Island, are left to the safe area.
The separate player window could be resized below what the corner layout
needs. Free resize now floors at the corner layout's minimum (its width
threshold, the narrowest side column plus a video column wide enough for
the control bar, and 600pt tall), and an undersized window grows to it
when the corner layout takes over. The video column minimum is 560pt on
macOS so the control bar capsule fits.
The control bar's fixed 650pt frame also widened the controls stack past
a narrower video column, clipping the top bar's avatar and close button;
the frame is now capped to the container width.