From 9f53d1c6040d3220baf063b46be38ff0c609b4ee Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Sat, 24 Oct 2026 20:14:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(playback):=20=E6=89=93=E5=BC=80=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E5=90=8E=201.5s=20=E7=AB=8B=E5=8D=B3=E9=87=8D?= =?UTF-8?q?=E8=AF=95=EF=BC=8C=E5=88=87=E6=AD=8C=E5=89=8D=E6=91=87=E4=BB=8E?= =?UTF-8?q?=20~20s=20=E9=99=8D=E5=88=B0=20~7s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实测时间线(20:08:53 那次切歌): 53.881 next -> play 53.947 open 完成 65ms 59.525 player error: tcp ffurl_read / Failed to open (mpv 5.6s 后放弃) 09:13.801 起播看门狗才触发重连 (又等 14s) 原因:看门狗 5s 一跳、阈值 10s,最坏要两个 tick 才触发。 - error 流里识别 Failed to open / ffurl_read,1.5s 后立即重试一次 (每首歌最多一次,force 模式跳过节流) - 进度看门狗 tick 10s -> 5s,任何卡死最多多等 5s 预期:失败发生在 5~6s,重试在 7~8s 开始,前摇 ~10s 降到 7~8s; 若首次打开成功则完全无感。 --- lib/services/audio_service.dart | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index a71cb26..d150b2f 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -121,6 +121,9 @@ class AudioService extends ChangeNotifier { /// 播放中定时持久化位置(冷启动能回到准确位置) DateTime _lastPositionSaveAt = DateTime.now(); + /// 打开失败后的快速重试(每首歌最多一次) + int _openRetryGeneration = -1; + /// 最近一次播放失败的原因,供 UI 提示;成功播放后自动清空。 String? _lastPlaybackError; String? get lastPlaybackError => _lastPlaybackError; @@ -542,6 +545,25 @@ class AudioService extends ChangeNotifier { } } + /// 打开失败(ffurl_read / Failed to open)后 1.5s 立即重试一次。 + /// + /// 实测:切歌后 mpv 在 5~6s 时放弃打开,旧逻辑要等起播看门狗 + /// (5s 一跳 × 10s 阈值,最坏 20s)才重连,表现为十几秒的前摇。 + /// 每首歌最多快速重试一次,避免失败风暴。 + void _scheduleOpenRetry() { + if (_currentSong == null) return; + if (_isChangingTrack) return; + if (_openRetryGeneration == _playbackGeneration) return; + + _openRetryGeneration = _playbackGeneration; + debugPrint('🔁 [AudioService] 打开失败,1.5s 后自动重试'); + Timer(const Duration(milliseconds: 1500), () { + if (_currentSong == null) return; + if (_isChangingTrack) return; + _recoverFromStall(force: true); + }); + } + /// 卡死/网络切换自愈:重开当前歌曲并 seek 回原位置。 /// /// [force] 为 true 时跳过节流(用于网络切换这种明确的外部事件)。 @@ -988,6 +1010,12 @@ class AudioService extends ChangeNotifier { debugPrint('❌ [AudioService] player error: $error'); _lastPlaybackError = error; notifyListeners(); + // 打开失败(ffurl_read / Failed to open)时立刻安排一次重试, + // 不要等 10~20s 的起播看门狗。 + if (error.contains('Failed to open') || + error.contains('ffurl_read')) { + _scheduleOpenRetry(); + } }), ); @@ -1009,7 +1037,7 @@ class AudioService extends ChangeNotifier { _progressTimer?.cancel(); _lastProgressAt = DateTime.now(); _progressTimer = Timer.periodic( - const Duration(seconds: 10), + const Duration(seconds: 5), (_) => _checkPlaybackProgress(), );