Consecutive same-direction presses ramp up the jump amount: first 2 presses use
the profile seek interval, presses 3-7 triple it, and presses 8+ sextuple it.
Direction change or focus loss resets the counter. This lets quick taps do fine
scrubbing while holding the key covers large spans fast.
While the home catalog is loading and no rows are available yet, both the mobile
and TV home screens now show 4 animated skeleton shelf placeholders instead of a
blank screen. The pulse animation matches the explore screen skeleton.
EpisodeCard gains a blurUnwatched parameter that matches the alpha logic already
used in mobile MobileEpisodeRow: 0.5f for unwatched episodes when blur is on,
0.45f for watched episodes when blur is off. TvDetailScreenContent propagates
the profile setting so both platforms behave consistently.
Section labels, row titles, display names, watch-time counters, and section
headers now use FluxaDisplay instead of the default system sans-serif, aligning
settings visual hierarchy with the rest of the app.
TvDetailScreenContent now accepts initialMeta and uses it as a hero background
fallback (background → poster → initialMeta?.background → initialMeta?.poster)
so the card's artwork is visible immediately while the full detail loads.
Save vertical scroll position and focused row index in HomeViewModel on dispose.
Restore them on re-entry: scroll to the saved position and focus the previously
active shelf's first card instead of always jumping to the hero play button.
Per-shelf FocusRequesters replace the single firstShelfFocusRequester so any
row can be individually targeted after navigation returns.
TvDetailScreenContent: detail background now uses ImageRequest with
memoryCacheKey("detail-bg:url"). OfflineDownloadPages: folder poster and
episode artwork wrapped in ImageRequest with downloads-group/episode keys.
TvLibraryEditors: URL preview field and folder row wrapped with keyed requests.
Extract row composables into MobileHomeSections (MobileHomeHeroItem, HomeUserCollectionRow,
HomeCategoryRow), card composables into MobileHomeCards (HomeCatalogCard,
MobileCollectionFolderCard, layout helpers), and bottom sheet into
MobileHomeContinueWatching. HomeScreen.kt retains only the top-level composable,
MobileHomeRowSpec, and spec builder functions.
Dissolve the monolithic sections file into TvSettingsAccountSection,
TvSettingsAppearanceSection, TvSettingsContentSection, TvSettingsPlaybackSection,
and TvSettingsSystemSection. TvSettingsPreviews retains the preview composables.
All section functions stay internal for same-module access from TvSettingsScreen.
Replace hex color literals with named FluxaColors constants in library, error, TV
library/detail/hero, offline, settings tiles, addon store, welcome, profile, mobile
detail/settings/library/addons/auth screens.
Replace all raw tween(N) integer literals with named FluxaDimensions.AnimDuration
constants in player overlays, sidebar, source panel, profile components, parents
guide, login, explore, detail, library, source, search, and route transitions.
Multi-track relay (high): relay now tracks selectedTrackId driven by
updateTracks(). LibassInterceptingTrackOutput drops samples for non-selected
tracks and re-primes the header (via headerGeneration counter) when the
selected track switches. NativeLibassSubtitleOverlay gates relayRenderer on
embeddedSubtitle != null so disabling subs actually stops drawing.
Duration inference (high): pendingBody is flushed in onSeekFlush() with
lastInterEventMs capped to 5s, fixing the last-subtitle-never-shown problem.
Duration cap lowered from 60s to 30s to reduce lingering on sparse dialogue.
Font fallback (medium): LibassInjectingExtractorsFactory now accepts fontsDir
and threads it through to setHeader(), matching what the local/external path
already did. C++ nativeCreate uses fonts_dir for ass_set_fonts_dir and probes
for a NotoSans-Regular.ttf default_font instead of passing nullptr.
Render loop (medium): C++ nativeRender returns jint (1=has image, 0=no image).
When detect_change==0 and bitmap dimensions are unchanged it skips memset+blend
entirely and returns the cached last_had_image. Kotlin onDraw uses the int
result to decide whether to draw and switches to postInvalidateDelayed(100)
during subtitle-free periods instead of postInvalidateOnAnimation at 60 fps.
Thread safety (low): ensure_api() replaced bool+flag with std::call_once so
the first-call race between ExoPlayer's loader thread and main is eliminated.
Subtitle matching (low): selectedNativeAssSubtitle now tries label then URL
then (label+language) before falling back to language-only, preventing two
external subs in the same language from selecting the wrong one.
fetchSubtitleBytes switches to OkHttp and forwards StreamRequestPolicy headers
so addon URLs that require Referer/auth don't 403.
mpv:
- Set ao=audiotrack explicitly so the AudioTrack output is guaranteed
(avoids openSLES which lacks encoded passthrough)
- Add audio-channels=7.1,5.1,stereo so mpv negotiates multichannel
instead of defaulting to auto-safe stereo downmix
- Build audio-spdif at init time by querying AudioManager output device
encodings; only advertises AC3/E-AC3/DTS/DTS-HD/TrueHD formats the
device actually reports
ExoPlayer:
- Guard setVideoEffects(emptyList()) behind a videoEffectsActive flag:
only call it when IptPqc2ToneMapEffect was actually installed, never
on a clean path — the empty-list call triggers the GL VideoFrameProcessor
pipeline in media3 1.10.1 which tone-maps HDR even for an empty effect list
- detectPreferredVideoMimeType now accepts Context and checks display +
decoder DV capability first; on fully DV-capable devices it returns
VIDEO_DOLBY_VISION so DV variant tracks are preferred rather than
deprioritized behind H.265/H.264
Home vertical lists on both platforms now compose the first four cards of
upcoming rows ahead of scroll via LazyListPrefetchStrategy. TV card
requests move to the size-qualified memory cache key helper mobile already
used, so the same URL requested at poster and expanded sizes no longer
collides under one key; the shelf neighbor prewarm and expanded-poster
prefetch write the matching keys.
Scrim heights and alphas, seekbar track height, and the buffer/track
colors now come from FluxaDimensions.PlayerChrome and FluxaColors, so both
players render the same chrome: 160dp/0.72 top scrim, 230dp/0.86 bottom
scrim, 4dp track (TV was 3dp).
Billboard rotations now play one orchestrated moment: logo, metadata,
description, and buttons fade and rise in sequence, driven by a single
Animatable read inside graphicsLayer so the stagger never recomposes the
panel. Ad-hoc tween literals across hero, shelves, cards, and the ambient
tint move to FluxaDimensions.AnimDuration tokens; the hero title fallback
also picks up the display face.
Rows now dissolve at the left edge instead of hard-clipping. The DstIn
fade needs offscreen compositing, so it only engages once the row has
scrolled; rows sitting at their start render with no extra cost.
rememberAmbientHeroColor reuses the existing Palette extraction (LRU
cached) to pull the dark muted tone of the current hero artwork, blended
toward the app background and animated between billboard rotations. TV
bleeds the tint under the shelf area behind the LazyColumn; mobile gets a
glow band above the hero fold.
Each once-focused card kept its ExoPlayer instance alive until the card
left composition, so scrolling a shelf accumulated idle decoders. Release
on stop caps live trailer players at one.
Replace the static 3.5dp focus border and duplicate overlay ring with the
tv-material Surface focus system: 1.05 focused scale (disabled when the
profile turns animations off), a 2.5dp accent border, and a soft black
glow. Shelf rows stop passing no-op positioning callbacks so cards without
expanded posters detach their layout listeners.
Add a Compose stability configuration declaring Meta and UserProfile
stable (both are immutable data classes in the data module) so card
composables skip recomposition when shelf state changes. In TvMovieCard,
gate onGloballyPositioned behind expanded/focus consumers, keep the
modifier chain constant by drawing the focus border in transparent when
unfocused, stop keying the artwork request on focus state so D-pad moves
no longer rebuild ImageRequests unless a focus GIF exists, and drop
crossfade allocations. Continue-watching card scale now animates in a
graphicsLayer block instead of recomposing.
Bundle Archivo (OFL) as the brand display family with width/weight axes;
displays, titles, shelf row titles, and Top Ten numerals now render in
expanded Archivo. Card titles drop from Black/Bold to SemiBold with Medium
metadata so shelves get visible hierarchy.
Replace scattered color literals (Netflix-red E50914/E53935 progress and
accent fallbacks, drifting dark surface tones) with a single FluxaColors
object; AppTheme and ThemeHelper now source from the same tokens. Accent
fallback unifies on the theme ember E85D3F.