Commit Graph
58 Commits
Author SHA1 Message Date
edde746 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.
2026-08-05 06:09:26 +02:00
edde746 a9f0532f5f fix(ui): keep pushed screens clear of the Android navigation bar
Plezy is edge-to-edge on Android whether it asks to be or not: targetSdk is
36, Android 15 enforces edge-to-edge for apps targeting 35+, and Android 16
disables the windowOptOutEdgeToEdgeEnforcement escape hatch. The only
SystemUiMode.edgeToEdge call in the app fires on video-player exit, so on
API 35+ the window is edge-to-edge from the first frame and
MediaQuery.padding.bottom is a real ~48dp overlap under 3-button navigation.

MainScreen's phone layout hides that. It supplies a bottomNavigationBar and
never sets extendBody, so Flutter's Scaffold strips padding.bottom from the
body MediaQuery and every tab is already safe. Routes pushed on the profile
navigator are full-screen siblings of MainScreen with no bottom bar, so they
receive the untouched inset and nothing consumes it - the last settings card
and the final log lines render under the back, home, and recents buttons.

Three shared hosts own most of those routes, so the inset is consumed there:
FocusedScrollScaffold (25 screens, counting the SettingsPage wrapper) and
FocusableDetailScreenMixin.buildDetailScaffold (4) now append a trailing
SliverSystemBottomInset, and the four screens that build their own Scaffold
around a CustomScrollView append it directly.

The new widget codifies the convention this repository had already written
down but open-coded - insets baked into the scroll content rather than a
SafeArea around the scroll view - so content still paints under the bar while
the scroll extent grows enough to bring the last row above it. It reads
padding from its own context and collapses to zero height wherever the inset
is already zero: desktop, Android TV, tvOS via _AppleTvScale, and inside
MainScreen's tab bodies. No platform branching, and it stacks additively with
the music detail screens' existing mini-player spacers, which is correct
because the mini-player itself floats above the navigation bar on a pushed
route.

Scroll views that are not sliver lists take the inset in their own padding:
the companion remote's ListView, the auth screen's scroll container, and the
two SliverFillRemaining sign-in forms, whose children size themselves from
the extent remaining before them and so cannot be helped by a trailing
sliver. The logs empty state is left alone for the same reason inverted - it
already fills the viewport, and a trailing inset would only add scroll slack.

Verified on a Pixel 7 running Android 16 (API 36) with 3-button navigation:
Settings, Logs, and Video Playback all end clear of the bar.

close #1766
2026-08-03 00:05:21 +02:00
edde746 352b88109b refactor: extract shared mixins and helpers, drop dead abstractions
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.
2026-07-26 06:09:48 +02:00
edde746 e0bf66eea8 fix(runtime): harden application service boundaries 2026-07-24 03:46:46 +02:00
edde746 e4db04fa62 fix: align UI focus and sheet behavior 2026-07-13 11:28:32 +02:00
edde746 44be03d39c refactor(profiles): shared auth/mint flows + screen fixes 2026-07-02 11:41:25 +02:00
edde746 6f39a286d3 fix(profiles): remount profile session
close #1371
2026-06-18 00:53:48 +02:00
edde746 62abd3c30a feat(jellyfin): simpler, code-first Quick Connect screen 2026-06-12 03:16:58 +02:00
edde746 3599b0b0e1 fix(startup): no-servers flash on fresh login + faster connect splash
- start binder before navigating from auth, mark isBinding synchronously
- overlap plex.tv resource refresh with optimistic cached-metadata connect,
  reconcile tokens/membership in background once it lands
- cached endpoint probe gets a head start on the race instead of serially
  blocking it
- defer phase-1 HTTPS upgrade off the splash critical path
- per-server connect progress stream for incremental splash checkmarks
- startup timing instrumentation (bind/fetch/race/connect elapsedMs)
2026-06-12 01:46:43 +02:00
edde746 74b8dc4561 refactor: type server identifiers 2026-06-01 11:06:03 +02:00
edde746 7988c83bec fix: improve d-pad focus navigation 2026-05-07 00:27:58 +02:00
edde746 3b18b44cd2 fix(tv): show provider choices on login 2026-05-06 12:47:39 +02:00
edde746 a40879849b fix(tvos): hide browser auth option 2026-05-03 02:00:39 +02:00
edde746 876a44b244 feat(tvos): add virtual keyboard input 2026-05-03 01:32:58 +02:00
edde746 244b291255 refactor: share controller disposal and Plex DTO parsing 2026-05-02 09:43:08 +02:00
edde746 7057ee93ac fix: defer profile binding before selection 2026-05-01 03:16:32 +02:00
edde746 31d2d9dc98 feat: jellyfin 2026-05-01 01:20:36 +02:00
edde746 feba5b6f74 lint: enforce unawaited_futures, prefer_final_locals, etc. 2026-04-25 12:27:28 +02:00
edde746 557ef69697 fix: guard setState/context against disposed widget 2026-04-21 22:29:57 +02:00
edde746 ee020d81bd refactor: trakt/download hardening and cleanup sweep 2026-04-21 13:57:03 +02:00
edde746 1edf3da984 refactor: consolidate dialogs to DialogActionButton 2026-04-21 12:05:21 +02:00
edde746 9e2e1f72ce refactor: remove debug token i18n strings 2026-03-08 15:13:19 +01:00
edde746 8cd48f3af5 fix: firstClient null check race 2026-03-04 10:01:40 +01:00
edde746 a6119414ca Merge branch 'fix-plex-notifications' 2026-02-27 22:20:12 +01:00
edde746 00667fe8b8 fix: add const constructors and remove unused imports 2026-02-27 15:43:34 +01:00
Matt Vogel 6eeb484c30 Pass persistent client UUID through auth screen login path
The auth screen was calling connectAndInitialize without a clientIdentifier,
causing MultiServerManager to fall back to a throwaway timestamp ID for all
probe requests during first login. This meant Plex registered a new device on
every login, not just the first ever install.
2026-02-26 08:21:06 -05:00
edde746 8eda892b68 feat: themed splash screen with status messages 2026-02-26 11:55:45 +01:00
edde746 7f8d4c7946 fix: dpad focus for dialogs and buttons 2026-02-22 14:50:59 +01:00
edde746 c3b10d6c45 fix: fallback to external browser when Custom Tabs unavailable 2026-02-16 04:21:44 +01:00
edde746 04438c8e45 refactor: code quality & format 2026-02-15 04:07:09 +01:00
edde746 6e9b78e5b5 fix: missing translations, simplify translations 2026-02-10 21:37:27 +01:00
edde746 cd2b29e0d5 fix: await profile loading on login 2026-02-01 02:41:59 +01:00
edde746 d3c7b55213 refactor: simplify server connection logic 2026-01-28 04:23:27 +01:00
edde746 8332ef8b9f fix: tv browser sign in 2026-01-26 15:26:27 +01:00
edde746 1f8dccbba7 fix: idiot proof android tv login 2026-01-26 03:15:56 +01:00
edde746 9c6b7740cf fix: load libraries on first login 2026-01-25 06:44:47 +01:00
edde746 79ac2793e3 refactor: remove redundant server enabling 2025-12-15 05:45:38 +01:00
edde746 3c89d0eba8 refactor: update formatting 2025-12-15 02:29:03 +01:00
edde746 fb17600bb0 refactor: consolidate 2025-12-15 01:16:22 +01:00
edde746 a2a5a5ae55 refactor: simplify & deduplicate 2025-12-14 21:03:11 +01:00
edde746 e804a2a3cc feat: app reviewer stupidity check 2025-12-07 19:39:56 +01:00
edde746 d9ea099727 refactor: deduplicate 2025-12-07 15:00:28 +01:00
edde746 6b150f7304 fix: qr code sign in on mobile 2025-12-04 12:27:23 +01:00
edde746 a6f0a0b6c0 refactor: format & deduplicate 2025-12-02 23:59:49 +01:00
edde746 6ae64e6057 fix: don't sign in with no servers 2025-11-25 09:43:54 +01:00
edde746 b455db4bdf refactor: clean up 2025-11-22 06:33:49 +01:00
edde746 166654d374 refactor: use play queues & other improvements 2025-11-22 03:24:01 +01:00
edde746 6e8a1a373e feat: remove server switcher 2025-11-21 23:59:39 +01:00
Doezer 3366ed02f5 Ran dart format using this pwsh command
$FILES = Get-ChildItem -Path lib, test -Recurse -Filter "*.dart" -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -notmatch '\.(g|freezed)\.dart$' } | Select-Object -ExpandProperty FullName; if (-not $FILES) { Write-Host "No Dart files found to format"; exit 0 } else { dart format --output=write --set-exit-if-changed $FILES }
2025-11-13 22:53:31 +01:00
edde746 12192c96c7 refactor: two column auth screen 2025-11-12 17:54:25 +01:00