UserProfileProvider kept a second cache of the active user's audio/subtitle defaults beside AccountPreferencesRepository, re-implemented the active profile/connection listener wiring and Plex Home token resolution, and for Plex Home profiles bypassed the shared cache entirely (fetching /user with its own client), so a write in the Account preferences screen never reached Home-profile playback. The controller now resolves the active account (the head of its sorted account list), loads it through the one repository on attach, on profile switch, and when the account's token changes, and exposes it as activePreferences; ensureActiveLoaded replaces initialize() for the startup gate. Playback, offline waiting, and logout storage clearing read the controller directly; UserProfileProvider, the per-backend user-profile DTOs, PlexAuthService.getUserProfile, and JellyfinClient.fetchUserProfile are deleted. The ranked defaultAudioLanguages/defaultSubtitleLanguages lists go with them: Plex Web never reads them, PMS applies them when it stamps `selected` on streams, and Plezy already trusts that flag ahead of the profile. They only refined a best-effort audio fallback for Plex items with no media info, which the primary language covers; on that path a second preferred language is no longer tried before the file's default track. Plex Home profiles now read /user/profile with the switched token through the same cache as local profiles.
134 lines
5.8 KiB
Dart
134 lines
5.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../media/ids.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../media/media_item.dart';
|
|
import '../media/media_library.dart';
|
|
import '../media/media_server_client.dart';
|
|
import '../services/plex_client.dart';
|
|
import '../i18n/strings.g.dart';
|
|
import '../providers/multi_server_provider.dart';
|
|
import 'app_logger.dart';
|
|
|
|
extension ProviderExtensions on BuildContext {
|
|
/// Internal: resolve a [PlexClient] from a serverId or fall back to the
|
|
/// first online server. Returns null if neither yields a Plex client.
|
|
/// Non-Plex servers (Jellyfin) are skipped — these helpers exist for
|
|
/// Plex-only flows that have no neutral equivalent (DVR tuning, match).
|
|
/// Backend-agnostic flows use the [_resolveMediaClient]
|
|
/// helpers below.
|
|
PlexClient? _resolveClient(ServerId? serverId) {
|
|
final provider = Provider.of<MultiServerProvider>(this, listen: false);
|
|
return _resolvePrioritized(serverId, provider.onlineServerIds, provider.getPlexClientForServer);
|
|
}
|
|
|
|
/// Internal: like [_resolveClient] but throws a localized exception when
|
|
/// no client is available. The thrown message is the canonical
|
|
/// `t.errors.noClientAvailable` so callers can surface it directly.
|
|
PlexClient _requireClient(ServerId? serverId, {bool fallback = true}) {
|
|
final provider = Provider.of<MultiServerProvider>(this, listen: false);
|
|
if (serverId != null) {
|
|
final client = provider.getPlexClientForServer(serverId);
|
|
if (client != null) return client;
|
|
if (!fallback) {
|
|
appLogger.e('No Plex client found for server $serverId');
|
|
throw Exception(t.errors.noClientAvailable);
|
|
}
|
|
}
|
|
final client = _resolveClient(null);
|
|
if (client == null) {
|
|
throw Exception(t.errors.noClientAvailable);
|
|
}
|
|
return client;
|
|
}
|
|
|
|
PlexClient getPlexClientForServer(ServerId serverId) => _requireClient(serverId, fallback: false);
|
|
|
|
PlexClient? tryGetPlexClientForServer(ServerId? serverId) {
|
|
if (serverId == null) return null;
|
|
final provider = Provider.of<MultiServerProvider?>(this, listen: false);
|
|
return provider?.getPlexClientForServer(serverId);
|
|
}
|
|
|
|
PlexClient getPlexClientForLibrary(MediaLibrary library) {
|
|
final serverId = serverIdOrNull(library.serverId);
|
|
if (serverId == null) throw Exception(t.errors.noClientAvailable);
|
|
return getPlexClientForServer(serverId);
|
|
}
|
|
|
|
PlexClient getPlexClientWithFallback(ServerId? serverId) => _requireClient(serverId);
|
|
|
|
// ── Backend-neutral helpers ──────────────────────────────────────
|
|
// These return [MediaServerClient] regardless of backend kind so callers
|
|
// that consume only the [MediaServerClient] surface don't need to type-
|
|
// check the result. Use [getPlexClientForServer] / [getPlexClientForLibrary]
|
|
// when you specifically need a [PlexClient] (Plex-only flows like Live TV or
|
|
// match/fix-match).
|
|
|
|
MediaServerClient? _resolveMediaClient(ServerId? serverId) {
|
|
final provider = Provider.of<MultiServerProvider>(this, listen: false);
|
|
return _resolvePrioritized(serverId, provider.onlineServerIds, provider.getClientForServer);
|
|
}
|
|
|
|
MediaServerClient? tryGetMediaClientForServer(ServerId? serverId) {
|
|
if (serverId == null) return null;
|
|
final provider = Provider.of<MultiServerProvider?>(this, listen: false);
|
|
return provider?.getClientForServer(serverId);
|
|
}
|
|
|
|
/// Get a [MediaServerClient] for the given serverId. Throws when the
|
|
/// server isn't registered or is offline. Mirrors the throwing variant of
|
|
/// the Plex-typed [getPlexClientForServer] helpers.
|
|
MediaServerClient getMediaClientForServer(ServerId serverId) {
|
|
final provider = Provider.of<MultiServerProvider>(this, listen: false);
|
|
final c = provider.getClientForServer(serverId);
|
|
if (c == null) throw Exception(t.errors.noClientAvailable);
|
|
return c;
|
|
}
|
|
|
|
MediaServerClient getMediaClientForLibrary(MediaLibrary library) {
|
|
final serverId = serverIdOrNull(library.serverId);
|
|
if (serverId == null) throw Exception(t.errors.noClientAvailable);
|
|
return getMediaClientForServer(serverId);
|
|
}
|
|
|
|
/// Get a [MediaServerClient] for a [MediaItem], or null in offline mode /
|
|
/// when the server isn't online.
|
|
MediaServerClient? getMediaClientForItemOrNull(MediaItem item, {bool isOffline = false}) {
|
|
if (isOffline) return null;
|
|
return tryGetMediaClientForServer(serverIdOrNull(item.serverId));
|
|
}
|
|
|
|
/// Get a [MediaServerClient] for [serverId], falling back to the first
|
|
/// online server when not found. Throws if no client is available.
|
|
MediaServerClient getMediaClientWithFallback(ServerId? serverId) {
|
|
final c = _resolveMediaClient(serverId);
|
|
if (c == null) throw Exception(t.errors.noClientAvailable);
|
|
return c;
|
|
}
|
|
|
|
/// Like [getMediaClientWithFallback] but returns null instead of throwing
|
|
/// when no client is registered. Use this for non-critical surfaces (image
|
|
/// loaders, list cards) that can render a fallback when the client isn't
|
|
/// available — throwing during `build` would crash the widget instead.
|
|
MediaServerClient? tryGetMediaClientWithFallback(ServerId? serverId) {
|
|
final provider = Provider.of<MultiServerProvider?>(this, listen: false);
|
|
if (provider == null) return null;
|
|
return _resolvePrioritized(serverId, provider.onlineServerIds, provider.getClientForServer);
|
|
}
|
|
}
|
|
|
|
/// Try [preferred] first, then fall back through [fallbacks] in order. Returns
|
|
/// the first non-null result from [resolve], or `null` if every candidate
|
|
/// resolves to null.
|
|
T? _resolvePrioritized<T>(String? preferred, Iterable<String> fallbacks, T? Function(ServerId) resolve) {
|
|
if (preferred != null) {
|
|
final c = resolve(ServerId(preferred));
|
|
if (c != null) return c;
|
|
}
|
|
for (final id in fallbacks) {
|
|
final c = resolve(ServerId(id));
|
|
if (c != null) return c;
|
|
}
|
|
return null;
|
|
}
|