The track-building effect in VideoContext reruns once api?.basePath and
isCurrentSubImageBased settle. An earlier async run could resolve after a
rerun and overwrite subtitleTracks/audioTracks with setTrack callbacks bound
to a stale `api`, breaking external-subtitle identity matching.
Add a cancellation token and route every state commit through guarded
committers so all six commit points (offline-transcoded audio/subs,
burned-in, and the online audio/subs paths) drop writes from a dead run,
plus bail out right after the awaited getAudioTracks when cancelled.
The setTrack/Disable callbacks close over isCurrentSubImageBased for the
transcode replacePlayer decision; add it to the track-building effect deps so
they rebuild when it flips (otherwise, in a transcoding session, callbacks could
stay on the MPV path after switching to/from a burned-in image sub and the
player would not refresh). Addresses CodeRabbit.
When an image subtitle was burned into a transcoded download it lives in the
video pixels and can't be disabled or swapped. Show only that '(burned in)'
entry instead of advertising Disable/text controls whose handlers can't affect
it (which would let the UI show a different selection than what's on screen).
Addresses CodeRabbit.
The setTrack callbacks build external-sub URLs from api?.basePath; add it to the
track-building effect deps so the list rebuilds once the API is ready (otherwise
online externals could resolve with undefined → notFound). Addresses CodeRabbit.
- Unify external detection: isExternalSubtitle drops the bare-DeliveryUrl case
(an Hls-delivered sub has a DeliveryUrl but isn't sub-add-ed) so sorting,
loading and resolution agree; compareTracksForMenu now uses it.
- applyMpvSubtitleSelection wraps player calls in try/catch — fire-and-forget
call sites no longer risk unhandled rejections.
- VideoContext offline-transcoded branch: treat missing IsTextSubtitleStream as
text (use !isImageBasedSubtitle), matching the shared helper.
- ItemContent.tv refreshSubtitleTracks: apply compareTracksForMenu like the
initial list.
- Tests: use the @/ alias; rework the embedded cases to actually exercise
identity (reversed player order) and the ordinal fallback (same-language,
no title).
The detail-page selector (MediaSourceButton, the #1176 replacement for
TrackSheet) and the TV detail/refresh paths (ItemContent.tv, Controls.tv
refreshSubtitleTracks) still listed subtitles in raw MediaStreams order
(externals first). Apply compareTracksForMenu there too so every menu
matches web. The in-player TV modal was already covered (fed from the
sorted VideoContext tracks).
direct-player resolves the selection on onTracksReady (online + offline, init +
runtime), VideoContext does the same for the mobile menu (incl. offline-transcoded),
and the menus (SubtitleTrackSelector, VideoContext, TVSubtitleSheet) now order
tracks like jellyfin-web. Fixes wrong-subtitle/wrong-language selection.
Fixes#954
- Match the loading skeleton to TVSearchSection's scaled layout (poster
width, item gap, edge padding, heading, poster radius) so placeholders
line up with the real content.
- Move the native search field up ~50px (drop marginTop).
- Remove the downward focus guide that re-captured upward focus, so
pressing up from the native search now reaches the tab bar.
Carry the live subtitle/audio selection to the next episode on all TV
navigation paths (next/prev buttons, autoplay) and feed TV subtitle modal
selections back into player state via onSubtitleIndexChange so the chosen
track is what gets carried.
Rank subtitles by language plus forced/hearing-impaired mode, with a
no-language fallback (mode + codec + position), and use an explicit match
flag so a deliberate "off" selection is retained too.
Add a local `tv-search` Expo module that hosts SwiftUI's `.searchable`
in a UIHostingController (adapted from expo-tvos-search, minus its native
results grid). It emits typed text to React Native so the existing search
pipeline and custom TV results grid are reused. Handles the RN-tvOS remote
gesture release needed for keyboard input on device.
Wire it into TVSearchPage as a sticky header above the scrollable results,
with a TVFocusGuideView bridge so focus can move from the tab bar into the
native search field.
The TV search input hardcoded fontSize and box dimensions, so it ignored
the TV display size setting. Drive font, height, padding, and icon from
the scaled `body` typography token so the whole component scales.
Handle the server's LibraryChanged WebSocket message to invalidate
library-dependent React Query caches when items are added/updated/
removed, so newly added episodes/movies appear without a manual
refresh. Debounced to coalesce a scan's burst of events.
Add useRefreshLibraryOnFocus as a fallback that re-checks on screen
focus (throttled, online-only, skips first focus), wired into home
(mobile + TV) and the library pages.
Render titled option groups as nested Menu submenus instead of flat
Pickers, and convert the Discover filters from ContextMenu to Menu.
Keeps single-tap-to-open behavior (ContextMenu requires a long press
and reads as a context menu) while giving the nicer nested grouping.
Native Button no longer renders RN <Text> children in SDK 55; use the
label prop. Wrap both buttons in a single Host + HStack with a trailing
Spacer so they sit flush-left with no centering inset.
@expo/ui's <Host> (SDK 55) fills its parent and reports its own size via
setStyleSize, so it can't size to content. With the Host's flex:1 height
depending on a zero-size wrapper, a circular dependency collapsed every
selector nested more than one level deep — only the first (Quality) stayed
visible in the download sheet.
Pin the wrapper View to the measured trigger size and let the Host fill it
via absoluteFill, breaking the cycle so Video/Audio/Subtitle render too.
Fixed a race condition where the upnext countdown started and a user
cancelled/stop the current playback that they would exit the player but
the timer would still be running and then start playing the next episode
and you wouldn't be able to press back or exit out of it
Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>