From 3f9c25fe5e6018a4b15cf6d49c658308feb53045 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Thu, 2 Jul 2026 19:16:48 +0200 Subject: [PATCH] fix(ui): hide Play Version while the item's server is unreachable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At most one version of an item can be downloaded, and plain Play now targets it directly, so an offline Play Version picker is a no-op detour offering versions that cannot play (#1440). Backend capabilities are static, so the menu gate and the quality picker in _handlePlayVersion both get a liveness check — a server dropping between menu open and tap must not offer transcodes either. --- lib/widgets/media_context_menu.dart | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/widgets/media_context_menu.dart b/lib/widgets/media_context_menu.dart index a2681c53..a94c9006 100644 --- a/lib/widgets/media_context_menu.dart +++ b/lib/widgets/media_context_menu.dart @@ -235,6 +235,10 @@ class MediaContextMenuState extends State { // active server cannot perform. final mediaClient = _itemServerId != null ? multiServerProvider.getClientForServer(ServerId(_itemServerId!)) : null; final canTranscode = mediaClient?.capabilities.videoTranscoding ?? false; + // Static capabilities stay truthy while the server is unreachable, so + // version/quality choices need a liveness check on top. + final itemServerOnline = + _itemServerId != null && multiServerProvider.serverManager.isClientOnline(ServerId(_itemServerId!)); final canRemoveFromContinueWatching = mediaClient?.capabilities.continueWatchingRemoval ?? false; final canEditMetadata = isAdmin && supportsMetadataEdit(mediaClient, mediaKind); @@ -385,10 +389,15 @@ class MediaContextMenuState extends State { // settings, which is what the regular Play action already does. // Both backends inline their version list in browse responses // (`Media[]` for Plex, `MediaSources` for Jellyfin), so the count - // is known up front. + // is known up front. Also hidden while the item's server is + // unreachable: at most one version exists locally and plain Play + // already targets it, so the picker would be a no-op detour + // offering versions that can't play (issue #1440). final versionCount = (mediaItem.mediaVersions ?? const []).length; final hasVersionChoice = versionCount > 1; - if ((mediaKind == MediaKind.episode || mediaKind == MediaKind.movie) && (hasVersionChoice || canTranscode)) { + if ((mediaKind == MediaKind.episode || mediaKind == MediaKind.movie) && + (hasVersionChoice || canTranscode) && + itemServerOnline) { menuActions.add( _MenuAction(value: 'play_version', icon: Symbols.video_file_rounded, label: t.mediaMenu.playVersion), ); @@ -861,10 +870,15 @@ class MediaContextMenuState extends State { Future _handlePlayVersion(BuildContext context) async { final item = _mediaItem!; - final client = context.tryGetMediaClientForServer(serverIdOrNull(_itemServerId)); + final itemServerId = serverIdOrNull(_itemServerId); + final client = context.tryGetMediaClientForServer(itemServerId); + final itemServerOnline = + itemServerId != null && context.read().serverManager.isClientOnline(itemServerId); // Same flag the in-player Version & Quality sheet reads — keeps both - // surfaces honest about what the active backend can actually do. - final canTranscode = client?.capabilities.videoTranscoding ?? false; + // surfaces honest about what the active backend can actually do. Also + // requires a reachable server: capabilities are static, and a server + // dropping between menu open and tap must not offer transcodes. + final canTranscode = itemServerOnline && (client?.capabilities.videoTranscoding ?? false); final versions = client == null ? item.mediaVersions ?? const [] : await resolveMediaVersions(item, client); if (!context.mounted) return false;