From bf9913ba0f1771f0c2f2d99532ed1f107fac5d75 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:52:54 +0200 Subject: [PATCH] fix(tv): re-tune on live stream fallback, fix decision 400 throw --- lib/screens/video_player_screen.dart | 25 +++++++++++++++++++------ lib/services/plex_client.dart | 1 + 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index 004fc096..b82f63b8 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -2313,10 +2313,13 @@ class VideoPlayerScreenState extends State with WidgetsBindin } /// Retry the live stream with degraded direct-stream settings. - /// Level 1: disable video direct stream. Level 2: also disable audio direct stream. + /// Re-tunes the channel for a fresh server session (the old one expires + /// while MPV exhausts its reconnect attempts). Future _retryLiveStream() async { final client = widget.liveClient; - if (client == null || _liveSessionPath == null || _liveSessionIdentifier == null || _transcodeSessionId == null) { + final channels = widget.liveChannels; + final channelIndex = _liveChannelIndex; + if (client == null || channels == null || channelIndex < 0 || channelIndex >= channels.length || widget.liveDvrKey == null) { appLogger.w('Cannot retry live stream — missing session info'); showGlobalErrorSnackBar(_lastLogError ?? 'Live stream failed'); _handleBackButton(); @@ -2325,14 +2328,24 @@ class VideoPlayerScreenState extends State with WidgetsBindin final ds = _liveStreamFallbackLevel < 1; final dsa = _liveStreamFallbackLevel < 2; - appLogger.i('Retrying live stream: directStream=$ds directStreamAudio=$dsa'); + final channel = channels[channelIndex]; + appLogger.i('Retrying live stream (re-tune ${channel.key}): directStream=$ds directStreamAudio=$dsa'); - // New transcode session so the server doesn't reuse the failed one. + // Re-tune to get a fresh capture session — the previous one is dead. + final tuneResult = await client.tuneChannel(widget.liveDvrKey!, channel.key); + if (tuneResult == null || !mounted) { + showGlobalErrorSnackBar(_lastLogError ?? 'Live stream failed'); + _handleBackButton(); + return; + } + + _liveSessionIdentifier = tuneResult.sessionIdentifier; + _liveSessionPath = tuneResult.sessionPath; _transcodeSessionId = PlexClient.generateSessionIdentifier(); final streamPath = await client.buildLiveStreamPath( - sessionPath: _liveSessionPath!, - sessionIdentifier: _liveSessionIdentifier!, + sessionPath: tuneResult.sessionPath, + sessionIdentifier: tuneResult.sessionIdentifier, transcodeSessionId: _transcodeSessionId!, directStream: ds, directStreamAudio: dsa, diff --git a/lib/services/plex_client.dart b/lib/services/plex_client.dart index e9ac10d4..3df59e56 100644 --- a/lib/services/plex_client.dart +++ b/lib/services/plex_client.dart @@ -2740,6 +2740,7 @@ class PlexClient { headers: {'Accept-Language': 'en'}, connectTimeout: ConnectionTimeouts.connect, receiveTimeout: ConnectionTimeouts.receive, + validateStatus: (status) => status != null && status < 500, ), ); final decisionUrl = '${config.baseUrl}/video/:/transcode/universal/decision?$queryString';