When a source advertises subtitles the native track list has not
produced yet, applyTrackSelectionWhenReady keeps an automatic selection
armed for up to thirty seconds. That late pass re-runs
TrackSelectionService against the stored preferences, so a track the
user picked in the meantime was silently reset. The Maestro codec suites
caught it: the English E-AC3 and Japanese DTS-HD flows select an audio
track, and fifteen seconds later the deadline puts the preferred
language back.
Adds explicit user-selection entry points that retire the pending
automatic selection first, and routes the sheet callbacks and the
remote's cycle shortcuts through them. Subtitles get the same treatment,
because the same pass re-selects them.
Bumping the generation is sufficient: TrackSelectionService re-checks it
in the statement immediately before each select call, and a mutation
already in flight was dispatched before the user's and so lands first.
`dart format --set-exit-if-changed` over lib and test rewrites these.
The analysis job never reached its formatting step, so the drift went
unnoticed. No behaviour changes.
StubMusicPlaybackService is a base for test doubles with no production
caller, so `check-unused-code lib` flagged it and the analysis job
failed. Moves it to test/test_helpers/, where shared fakes belong.
`clean_translations.py --strict` reported seven unused keys.
Three are genuinely dead: TrackSelectionHelper.getEmptyMessage was
removed as unreachable in 4307c49c, and the sheets that need an empty
state carry their own strings. Removes them from every locale.
The four accessibility keys are false positives. tv_number_spinner
aliased the subtree as `final a11y = ...accessibility`, which the static
scanner cannot follow — its docstring says as much. Binds the documented
`final t = Translations.of(context)` instead and hoists the two labels,
so the semantics stay identical and the scanner sees the chains.
Complete the contributed translations against the current English source and
register the locales in the language picker.
Fills the 51 keys the contributions predated, including the whole
downloads.backgroundWarning block that every locale must translate, and
restores the ${count} placeholder the sync-rule confirmation had dropped in all
three files.
Also corrects contributed strings: Azerbaijani "Imkan (Kopyalama)"
(possibility/copying) for resolution and several Turkish spellings, and Kazakh
Latin-script leaks ("Keyinirek", "Subtitr") plus Uzbek loanwords ("бош", "бир",
"муаммо") that do not read as Kazakh.
Consolidates #1689, #1690, and #1691.
Co-authored-by: Omc725 <98108290+Omc725@users.noreply.github.com>
Complete the contributed translation against the current English source and
register Turkish in the language picker.
Fills the 51 keys the contribution predated, including the whole
downloads.backgroundWarning block that every locale must translate, and
corrects a few contributed strings: "Sesi Kıs" (volume down) for mute,
"Disket" (floppy) for disc, "bitiş hızı" (finishing speed) for bitrate, and a
"Kısayol Ayaıla" typo.
Consolidates #1683 and #1688, which contributed byte-identical files.
Co-authored-by: Omc725 <98108290+Omc725@users.noreply.github.com>
Settings rows carried their own platform-conditional typography and
density, so on desktop and TV they rendered a 16px title, 14px subtitle
and 80px row while every other row in the app — the Focusable*ListTile
defaults plus ThemeData.listTileTheme's `dense: true` — renders 13/12
in 61px.
Drop the overrides instead of re-tuning them: the tile defaults already
encode the app's row style, and the explicit title styles were redundant
under a dense ListTile (they also masked the disabled/selected title
color). settingsOptionTitleStyle now only serves group children that are
not ListTiles, and matches the dense title unconditionally.
SettingsGroup hands its children that same compact density, so the plain
ListTiles used as non-interactive info rows stop standing 11px taller
than their interactive siblings.
Groundwork for #1300. Establishes the session, buffering and route
handling Dolby's application guide prescribes, and adds the diagnostic
arm needed to find out whether Apple's sample-buffer renderer can carry
Atmos objects at all.
Audio session, per the guide's sequence:
- Adopt the long-form playback profile in one atomic call at app launch
and activate the session there. The SDK only accepts that policy with
category Playback, a Default/MoviePlayback/SpokenAudio mode and no
options, so it cannot be assembled from separate calls.
- Report the resolved rendering mode in the player, hidden unless the
system resolves it. Apple only resolves it for CarPlay and AirPlay, so
an unresolved value means unknown, never "not Dolby".
Diagnostics (Apple TV only, Settings > Video Playback > Atmos Output Test):
- Add a sample-buffer arm. It reads the asset with AVAssetReader at
outputSettings nil and hands the untouched compressed buffers and the
untouched format description straight to the renderer, with a variant
that rebuilds the description the way playback builds it. Every
existing mode went through AVPlayer, so nothing exercised the path
playback actually uses; this is what tells us whether the renderer or
our construction is at fault.
- Add an AirPlay route picker. AirPlay is the only route where the system
resolves the rendering mode and the supported channel layouts, so it is
what makes those observations reachable at all, and the AVPlayer arms
now allow external playback so every arm can be compared on the same
destination.
- Add a session-mode toggle for the one profile difference between the
guide and previous playback behaviour.
- Report the session profile, supported layouts, both format
descriptions, the magic cookie and the renderer status, and release the
session on stop so a failed run cannot contaminate the next one.
Also bumps MPVKit to 1.0.14, which carries the matching audio output
work: the channel layout AVFoundation itself uses for Dolby content, a
renderer-failure observer so the fallback to PCM can actually run, the
prescribed feed ordering and preroll, flush recovery that re-supplies the
discarded audio instead of shifting later audio into its place, and
capability-driven fallback on route and capability changes.
This does not yet fix#1300. Whether the sample-buffer renderer can carry
JOC is still unknown; it removes every difference from the documented
setup that could explain the failure, and gives us the arm to answer it
on real hardware.
Consolidates duplicated logic behind shared implementations — paginated
grid tabs, focus chrome, cached remote stores, sheet selection columns,
the server artifact store and a test fixture layer — and removes code
that had become unreachable. Net reduction of about 5,500 lines with no
behaviour change.
Where a fix had landed separately in code that moved into a shared
helper, the fix was re-applied inside the helper rather than left behind
in the copy that went away.
user_switch_response.dart was left holding a single 13-line function after
UserSwitchResponse's decorative fields were dropped, so the filename no
longer described its contents and it sat at lib/models/ root while every
other Plex model lives in lib/models/plex/.
Renamed to lib/models/plex/plex_switch_response.dart, with the test moved
alongside the other plex_*_test.dart files. The parser stays public so the
#1488 drift characterization tests keep exercising it directly.
Deduplicates the hand-rolled coalescing/caching maps, the Plex client cast,
the missing-serverId event guard and the progress-failure backoff, and drops
the MusicPlaybackService availability gate, which could never fail in
production.
The search screens, the out-of-band auth dialogs, the live TV guide and the
list-download paths each carried their own copy of the same shell. Extracts
SearchInputField and PendingAuthDialog and routes the duplicated download
and guide helpers through one implementation.
Focus chrome was implemented twice, once in the focusable wrapper and once
in the focus builders; both now go through FocusChrome. TvColorPicker's
channel row was a copy of TvNumberSpinner and is now that widget in compact
density.
Also trims unused helpers and fields and simplifies the Jellyfin browse
paths.
Extracts the repeated toolbar fade into a single ToolbarScrim widget, folds
duplicated request/retry handling in the media server HTTP client, and
collapses the parallel playback-source, download-manager and live TV helper
paths into shared implementations.
Collapses indirection layers and one-caller abstractions across the video
player, shortcut dispatch, shader loading and context-menu code, including
the VideoPIPManager pass-through over PipService.
- Merge SeerrMovieDetails/SeerrTvDetails into one SeerrDetails model and
route both detail endpoints through a single request helper.
- Replace the three parallel tracker session/store/rebind-generation
triples in TrackersProvider with a _TrackerSlot record plus one _rebind
path.
- Fold the three JellyfinSequentialLauncher entry points onto a shared
_launchLocalQueue helper that owns loading, abort, shuffle and publish;
each caller now supplies only its fetch.
- PaginatedCardGridTabState: the collections and playlists tabs were 95%
identical; they now supply only pageSize/fetchPage/idOf instead of each
duplicating the grid, memo, inflation budget and focus wiring.
- EtagCachedRemoteStore: the anime-lists and fribb mapping stores now share
one download/cache/isolate-parse/conditional-GET lifecycle.
- FocusableTileStateMixin manages its own initState/didUpdateWidget/dispose
instead of requiring every caller to forward three lifecycle hooks.
Also drops unused ServerCapabilities entries and dead code in
focusable_list_tile and music/track_row.
Introduces shared seams for paginated views, D-pad reorder, media control
routing, async singletons and the device method channel, then points the
open-coded copies at them.
Also removes unused models and duplicated provider/server plumbing, folds
the twice-implemented artifact store in the server, and factors the
repeated Flutter toolchain prologue in CI into a composite action.
Several pairs of near-identical code paths differ in one load-bearing
line. Each site now carries a comment naming the invariant that forces it
apart, backed by a characterization test so a future deduplication fails
loudly instead of silently changing behaviour.
Pinned: focusable wrapper vs. chip D-pad activation policy, profile
connection cleanup's raw-id vs. ServerId-typed server projections, live TV
tab loaders, video player display matching and playback service wiring,
track selection container ordering, tracker HTTP client status ladder, and
the MediaServerHttpClient shutdown/cancellation contract versus
ManagedHttpClient's closing guard.
New tests:
test/focus/dpad_activation_policy_test.dart
test/services/track_selection_container_ordinal_test.dart
test/services/trackers/tracker_status_ladder_test.dart
test/utils/media_server_http_client_shutdown_test.dart
Drops dead code across services, models, utils and widgets, including the
connection auth service, which had no implementer, and the Live TV DVR
provisioning models, which had no caller.
Tests that only covered deleted behaviour are removed or trimmed. No
behaviour change.
Complete and revise every shipped locale against the current English source, preserve locale-specific plurals, and map script-specific Chinese locales through device, Intl, duration, and Plex boundaries.
Co-authored-by: emgeje <mgj@mgj.hu>
Co-authored-by: junyou1998 <junyou1998@gmail.com>