dependabot/github_actions/actions/setup-java-6.0.0
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cb55134cef |
fix(startup): move optional work off the launch gate
Cold start on the target TV box reaches its first frame 21 ms after `dart_main`, so nothing Dart-side gates the splash. Everything below is on the path to the first *useful* frame, which is a strictly serial chain and where the viewer actually waits. - `monoTheme` is a pure function of two bools that builds a full `ColorScheme`, an applied-and-copied 15-style `TextTheme`, ~14 sub-themes and then clones the whole `ThemeData` again. It was rebuilt five times per cold start, two of them before `runApp`, and twice more per app-shell rebuild. It is memoized now, keyed by palette plus `TargetPlatform` -- the platform matters because `ThemeData()` derives tap target size, visual density and typography from `defaultTargetPlatform`, so a palette-only key would be wrong under a debug or test platform override. - `initializeDateFormatting` ignores its locale argument and builds CLDR symbols and patterns for all 121 locales synchronously. It blocked the gate ahead of the database open for data that only content screens use. - `DownloadStorageService.initialize` ended in a `path_provider` round trip plus mkdir at the tail of the gate, contradicting the comment above it that already explained offline artwork is not a launch requirement. - `recoverInterruptedDownloads()` and `TrackerCoordinator.initialize()` ran from `initState` of the widget whose first build produces the first app frame, and the RSS watchdog installed a periodic timer there whose first useful sample is 15 s away regardless. - `CredentialVault` decrypted every token with pure-Dart AES-GCM on the main isolate, uncached, on every registry read and on every Drift re-emit -- and the binder writes tokens during the startup sweep, so writes re-triggered reads. Decryption is memoized by ciphertext, with `invalidateCache()` wired into the preference-store repair path so a repaired install cannot serve stale plaintext. - `reloadFromStorage` now coalesces in-flight callers. The two serial awaits around the legacy migration are deliberately not merged; only genuinely concurrent callers share a snapshot. - `_sameConnections` ran two `jsonEncode` calls per connection on every Drift emit purely to compare, allocating two maps and two strings each time. - The splash rendered one `CircularProgressIndicator` per pending server on top of the aggregate one, so N+1 tickers scheduled a frame every vsync for the whole of `awaitBindingSettle` -- competing with the startup work they were reporting on. Measured on device in a settled dexopt state: time to `main_screen` 1495 -> 1400 ms, `credentials_loaded` 1238 -> 1128 ms, `database_ready` 501 -> 443 ms. First frame is unchanged at ~18 ms, as expected. |
||
|
|
05fd622968 |
feat(emby): add Emby as a MediaBrowser backend alongside Jellyfin
Emby is Jellyfin's upstream ancestor and speaks a near-identical MediaBrowser
API, so the existing Jellyfin stack is parameterised by a `MediaBrowserDialect`
rather than forked. `JellyfinClient`, its auth service, endpoint discovery, LAN
discovery, and the add/edit connection screens all take the dialect and keep one
implementation; `MediaBackend.emby` and `ConnectionKind.emby` carry it through
the neutral models, the Drift `kind` discriminator, downloads, and caches.
Every divergence below was measured against a live Emby 4.9.5 server, not
inferred from documentation, and each is documented at its capability getter.
Jellyfin's request strings stay byte-identical so nothing about its behaviour
changes.
Routes and auth
- Emby only accepts the pre-10.9 user-scoped item routes (`/Users/{id}/Items/…`,
`/Users/{id}/PlayedItems/…`, `/Users/{id}/FavoriteItems/…`); the unprefixed
forms Jellyfin 10.11 added return 404.
- The API is also served under a legacy `/emby` prefix, and both dialects accept
the token as `X-Emby-Token` or `api_key=`.
- Emby answers only its own LAN discovery datagram ("who is EmbyServer?") and
ignores Jellyfin's; its default HTTPS port is 8920.
- No `/QuickConnect` route exists, so Quick Connect stays Jellyfin-only.
Row fields Emby withholds
- `ProductionYear`, `OfficialRating`, `PremiereDate` and `DateCreated` are absent
from list rows unless named in `Fields`, which would otherwise strip the year
and age-rating badge from every card in the app.
- `UserData.LastPlayedDate` never appears on a list row under `Fields=UserData`,
`EnableUserData=true` or the user-scoped `Ids=` form — only on the single-item
detail route, or when the Emby-specific `UserDataLastPlayedDate` token is
requested. Without it every recency-ordered surface silently degrades to
library-add time, and `JellyfinApiCache.applyWatchState` stamps
`DateTime.now()` on watched rows, so an offline watch-state pull would rewrite
the cached play time of everything it walked.
Continue Watching and Next Up
- Emby computes Next Up per series only: the library-wide `/Shows/NextUp` query
returns nothing under every parameter combination tried. The shelf is
therefore reconstructed from a played-episode recency scan plus one
`/Shows/NextUp?SeriesId=` per distinct series, bounded by a shared wall clock
that covers the scan as well — per-request timeouts cannot bound the pass
because `MediaServerHttpClient` times the connect and receive phases
independently. Rows are stamped with their series' newest play from the same
response that ordered them, so no per-series enrichment request is needed.
- `/Shows/NextUp` ignores `NextUpDateCutoff`, and no server-side played-date
filter exists to delegate to (`MinDatePlayed` and `MinDateLastPlayed` are
ignored; `MinDateLastSaved`, `MinDateCreated` and `MinPremiereDate` filter
unrelated dates), so the 365-day window is applied to the scanned dates.
- The resume route returns items with no saved position, including plain next
episodes, so the Emby resume leg reads from `/Items?Filters=IsResumable`.
- Emby is ahead of Jellyfin in one place: `/Users/{id}/Items/{id}/HideFromResume`
makes Continue Watching removal a real capability.
Everything else
- `/Sessions/Playing` and `/Sessions/Playing/Progress` reject a body with no
`PlaySessionId` (HTTP 400), so playback reporting always sends one.
- Passing any `MediaTypes` value to the playlist query returns an empty list.
- There is no aggregate `/Items/Filters` route; the four filter facets are
reassembled from `/Genres`, `/OfficialRatings`, `/Studios` and `/Tags`.
- Metadata writes take name-pair lists (`Genres: [{'Name': 'Action'}]`); the
plain string array is accepted and then silently discarded.
- Custom artwork uploads must be base64 text, not raw bytes — which was broken
for Jellyfin too and is fixed for both.
- Trickplay, media segments and lyrics 404 on Emby, so scrub previews are absent
and intro/credit markers fall back to chapter names.
Verified against a local Emby 4.9.5 and a Jellyfin 10.11.11 control server:
onboarding, browse, detail, playable stream URLs serving real bytes, subtitle
sidecars, watch-state write and restore, hubs, cross-server aggregation and
search across both backends simultaneously.
|
||
|
|
66549e3a67 |
fix(prefs): route every credential read through the tolerant path
The wrong-type recovery only covered reads that went through a BaseSharedPreferencesService instance. The three stores that hold credentials read the shared cache directly, so a mistyped value there still threw a raw TypeError or, for Seerr, was swallowed by a catch-all and reported as "no session" — the registry documented protection it did not actually provide. readPreferenceTolerantly now takes the cache, so CredentialVault, TrackerAccountStore and SeerrSessionStore get the same classification as the settings layer. CredentialVault's post-write re-read moves outside its catch: a wrong-typed value written by another isolate was swallowed there, and the process then returned a key that never durably landed, making every ciphertext written under it unreadable on the next launch. Those stores are consulted long after startup, where a throw is an unhandled provider error rather than a repair prompt, so SettingsService initialization now walks the cached key set once and reads every sensitive key. That puts the failure inside a fatal gate step while the store is still open and a surgical single-key repair is possible. The remaining direct reads in settings and storage are routed too; the only ones left are the library-density dual-type migration, which probes both types deliberately, and an untyped switch that is type-safe by construction. |
||
|
|
61f7d33fdf |
fix(profiles): treat vault decrypt failures as lost credentials
A failed MAC check (key/ciphertext divergence: restored backup, clobbered prefs, racing key generation across isolates) threw from CredentialVault.reveal on the startup profile-settings path and crash-looped the app until data was wiped — one device logged 31 fatals in 16 minutes on 2.8.0. Decrypt failure now means the credential is lost, never a crash: reveal() returns null, ProfileConnectionRegistry maps it onto the existing empty-token lazy-fetch sentinel and heals the row so later boots re-acquire the token instead of re-failing, and revealConnectionConfig degrades tokens to empty strings without marking them migrated. Key init also reloads prefs before deciding to generate and re-reads after writing, adopting whatever landed so all isolates converge on a single key instead of orphaning ciphertext. |
||
|
|
31d2d9dc98 | feat: jellyfin |