feat(tvos): fetch Top Shelf content live and show poster art

The Top Shelf extension now fetches Continue Watching directly from
Plex/Jellyfin/Emby instead of replaying a cache the app wrote on its
last foreground Discover pass. The app publishes per-profile server
descriptors on every shelf sync (`updateSources`): token-free metadata
in the app group, tokens in an app-group-shared keychain item, both
wiped by `clear`. On success the extension rewrites the cached payload
as the offline fallback; any fetch failure falls back to the previous
cache-replay behavior. Poster images are passed as remote URLs, so the
extension no longer depends on app-side artwork downloads.

Episodes now render season/series poster art (2:3, `.poster` shape)
instead of 16:9 episode stills, and labels lead with the S/E marker so
long titles no longer hide it behind the focused-item marquee. Shelf
schema v3 (Dart, Android, tvOS envelopes bumped together) discards
stale wide-art caches instead of letterboxing them into poster slots.

close #1474
close #1835
This commit is contained in:
edde746
2026-08-09 10:59:16 +02:00
parent 0b4fd9e8f3
commit 291a22a4a4
14 changed files with 1605 additions and 31 deletions
+16 -1
View File
@@ -82,10 +82,12 @@ class DiscoverProvider extends ChangeNotifier with DisposableChangeNotifierMixin
required this.isProfileBinding,
WatchStateStore? watchStateStore,
Future<void> Function(String profileId, List<MediaItem>)? syncSystemShelf,
Future<void> Function(String profileId, List<MediaServerClient> clients)? syncServerSources,
// A private field cannot be a named initializing formal callers can pass.
// ignore: prefer_initializing_formals
}) : _watchStateStore = watchStateStore,
_syncSystemShelfOverride = syncSystemShelf {
_syncSystemShelfOverride = syncSystemShelf,
_syncServerSourcesOverride = syncServerSources {
_loadCoordinator = CoalescedLoadCoordinator<String>(onFull: _loadOnce, onDelta: _loadDeltaOnce);
// Late server connects (reconnect after outage, slow wave) refresh
// discover the same way they refresh libraries. Removed in [dispose] so a
@@ -162,6 +164,7 @@ class DiscoverProvider extends ChangeNotifier with DisposableChangeNotifierMixin
/// load once binding settles).
final bool Function() isProfileBinding;
final Future<void> Function(String profileId, List<MediaItem>)? _syncSystemShelfOverride;
final Future<void> Function(String profileId, List<MediaServerClient> clients)? _syncServerSourcesOverride;
StreamSubscription<WatchStateEvent>? _watchStateSubscription;
StreamSubscription<DeletionEvent>? _deletionSubscription;
@@ -938,7 +941,16 @@ class DiscoverProvider extends ChangeNotifier with DisposableChangeNotifierMixin
if (isDisposed) return;
try {
// tvOS pulls Continue Watching itself; hand the Top Shelf extension
// the current online server sources before publishing items.
final sourcesOverride = _syncServerSourcesOverride;
final syncOverride = _syncSystemShelfOverride;
if (sourcesOverride != null) {
await sourcesOverride(owner, _onlineShelfSourceClients());
} else if (syncOverride == null) {
await SystemShelfService().syncServerSources(owner, _onlineShelfSourceClients());
}
if (isDisposed) return;
if (syncOverride != null) {
await syncOverride(owner, onDeck);
continue;
@@ -972,6 +984,9 @@ class DiscoverProvider extends ChangeNotifier with DisposableChangeNotifierMixin
throw Exception('No owning client available for $serverId');
}
List<MediaServerClient> _onlineShelfSourceClients() =>
_multiServer.serverManager.onlineClients.values.toList(growable: false);
@override
void dispose() {
_multiServer.removeOnlineServersListener(syncToOnlineServers);