fix(offline): honor settled profile visibility
This commit is contained in:
@@ -45,6 +45,10 @@ class MultiServerProvider extends ChangeNotifier with DisposableChangeNotifierMi
|
||||
/// ids in the set surface through [serverIds] / [onlineServerIds].
|
||||
Set<String>? _visibleServerIds;
|
||||
|
||||
/// True once the active profile has explicitly resolved visibility. An empty
|
||||
/// set is meaningful: the profile has servers, but none are currently visible.
|
||||
bool get hasExplicitVisibleServerFilter => _visibleServerIds != null;
|
||||
|
||||
/// Server ids the active profile is expected to have access to, including
|
||||
/// unreachable servers that do not have a live client in [MultiServerManager].
|
||||
/// This is intentionally separate from [_visibleServerIds]: visible ids drive
|
||||
|
||||
@@ -31,9 +31,9 @@ class OfflineModeProvider extends ChangeNotifier with DisposableChangeNotifierMi
|
||||
: _multiServerProvider = multiServerProvider,
|
||||
_hasServerConnection = (multiServerProvider?.hasConnectedServers ?? _serverManager.onlineServerIds.isNotEmpty) {
|
||||
// Pre-seed the "received status" flag if there are already online
|
||||
// servers (e.g. provider rebuilt mid-session) — otherwise we'd
|
||||
// incorrectly say "online" after the manager already emitted.
|
||||
if (_hasServerConnection) _hasReceivedServerStatus = true;
|
||||
// servers (e.g. provider rebuilt mid-session) or the active profile's
|
||||
// visibility filter has already settled.
|
||||
_markServerStatusKnownIfSettled();
|
||||
_lastOfflineState = isOffline;
|
||||
_multiServerProvider?.addListener(_handleMultiServerProviderChanged);
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class OfflineModeProvider extends ChangeNotifier with DisposableChangeNotifierMi
|
||||
_multiServerProvider = provider;
|
||||
_multiServerProvider?.addListener(_handleMultiServerProviderChanged);
|
||||
_hasServerConnection = provider.hasConnectedServers;
|
||||
if (_hasServerConnection) _hasReceivedServerStatus = true;
|
||||
_markServerStatusKnownIfSettled();
|
||||
_notifyIfOfflineChanged();
|
||||
}
|
||||
|
||||
@@ -95,10 +95,16 @@ class OfflineModeProvider extends ChangeNotifier with DisposableChangeNotifierMi
|
||||
|
||||
void _handleMultiServerProviderChanged() {
|
||||
_hasServerConnection = _multiServerProvider?.hasConnectedServers ?? _serverManager.onlineServerIds.isNotEmpty;
|
||||
if (_hasServerConnection) _hasReceivedServerStatus = true;
|
||||
_markServerStatusKnownIfSettled();
|
||||
_notifyIfOfflineChanged();
|
||||
}
|
||||
|
||||
void _markServerStatusKnownIfSettled() {
|
||||
if (_hasServerConnection || (_multiServerProvider?.hasExplicitVisibleServerFilter ?? false)) {
|
||||
_hasReceivedServerStatus = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _notifyIfOfflineChanged() {
|
||||
final offline = isOffline;
|
||||
if (_lastOfflineState == offline) return;
|
||||
|
||||
@@ -429,8 +429,10 @@ mixin _JellyfinPlaybackMethods on MediaServerCacheMixin {
|
||||
/// transcode bitrate against the same ceiling. Original playback passes null
|
||||
/// to avoid capping high-bitrate files. [mediaSourceId] pins the negotiation
|
||||
/// to a specific version when the item has multiple sources.
|
||||
/// [startTimeTicks] asks Jellyfin to start transcoding at the requested
|
||||
/// source offset so server-generated streams line up with the app timeline.
|
||||
/// [startTimeTicks] is forwarded to Jellyfin's playback negotiation for
|
||||
/// resume-aware stream metadata. Our video transcode profile is HLS, and
|
||||
/// Jellyfin omits `StartTimeTicks` from the returned HLS URL, so the player
|
||||
/// still performs the initial seek.
|
||||
/// [audioStreamIndex] / [subtitleStreamIndex] tell the server which streams
|
||||
/// to pick for the transcode profile (Jellyfin's negotiation factors them in
|
||||
/// when picking codec compatibility).
|
||||
|
||||
@@ -95,14 +95,18 @@ void main() {
|
||||
var notified = 0;
|
||||
p.addListener(() => notified++);
|
||||
|
||||
expect(p.hasExplicitVisibleServerFilter, isFalse);
|
||||
|
||||
// Empty set is a real value (different from null) — switching from
|
||||
// null → {} should notify so consumers know the active profile has
|
||||
// no servers, not "all servers".
|
||||
p.setVisibleServerIds(<String>{});
|
||||
expect(notified, 1);
|
||||
expect(p.hasExplicitVisibleServerFilter, isTrue);
|
||||
|
||||
p.setVisibleServerIds({'a', 'b'});
|
||||
expect(notified, 2);
|
||||
expect(p.hasExplicitVisibleServerFilter, isTrue);
|
||||
|
||||
// Idempotent: same membership is a no-op.
|
||||
p.setVisibleServerIds({'b', 'a'});
|
||||
@@ -111,6 +115,7 @@ void main() {
|
||||
// Clearing back to null after a real filter is a state change.
|
||||
p.setVisibleServerIds(null);
|
||||
expect(notified, 3);
|
||||
expect(p.hasExplicitVisibleServerFilter, isFalse);
|
||||
|
||||
p.dispose();
|
||||
});
|
||||
|
||||
@@ -166,6 +166,33 @@ void main() {
|
||||
manager.dispose();
|
||||
});
|
||||
|
||||
test('expected but unreachable profile servers enter offline once visibility settles', () async {
|
||||
final manager = MultiServerManager();
|
||||
final multi = MultiServerProvider(manager, DataAggregationService(manager));
|
||||
final p = OfflineModeProvider(manager, multiServerProvider: multi);
|
||||
|
||||
expect(p.isOffline, isFalse);
|
||||
|
||||
var notifications = 0;
|
||||
p.addListener(() => notifications++);
|
||||
|
||||
multi.setExpectedVisibleServerIds({'jf-machine'});
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(p.isOffline, isFalse);
|
||||
expect(notifications, 0);
|
||||
|
||||
multi.setVisibleServerIds(<String>{});
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(p.isOffline, isTrue);
|
||||
expect(notifications, 1);
|
||||
|
||||
p.dispose();
|
||||
multi.dispose();
|
||||
manager.dispose();
|
||||
});
|
||||
|
||||
test('Plex auth errors without live clients stay out of generic offline', () async {
|
||||
final manager = MultiServerManager();
|
||||
final multi = MultiServerProvider(manager, DataAggregationService(manager));
|
||||
|
||||
Reference in New Issue
Block a user