From fdca72125053bfbd0dea8176dcd481fd6ba7512e Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Sat, 31 Oct 2026 21:41:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(playback):=20=E8=87=AA=E5=8A=A8=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E7=86=94=E6=96=AD=EF=BC=8C=E9=81=BF=E5=85=8D=E5=9D=8F?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=AF=BC=E8=87=B4=E6=97=A0=E9=99=90=E9=87=8D?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 日志证据(21:32~21:33):同一首 "Communications - Keeno" Failed to open ×42,自动恢复 ×54 —— 看门狗每 10s 重开一次,死循环。 - 新增 _recoveryAttempts:同一首歌最多自动恢复 3 次 - 起播看门狗、网络切换、打开失败重试都受熔断约束 - 位置 >1s(真的出声)后清零,正常歌曲不受影响 - 切歌清零,用户手动点播放可重新尝试 - 熔断后写入 lastPlaybackError="无法播放:" 并停住,不再刷请求 测试 25 个全绿;release 已安装。 --- lib/services/audio_service.dart | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index baa7257..5097dc6 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -113,6 +113,12 @@ class AudioService extends ChangeNotifier { /// 正在执行自动恢复(此时允许位置回退,因为是我们自己重开的) bool _isRecovering = false; + /// 当前这首歌已经连续自动恢复了几次(成功后清零)。 + /// 打开一首本身拿不到数据的歌时,看门狗会无限重试, + /// 必须熔断,否则日志和请求都会爆炸。 + int _recoveryAttempts = 0; + static const int _maxRecoveryAttempts = 3; + /// 当前歌曲开始加载的时间,用于"起播看门狗": /// 有些情况下 mpv 的 playing 已经是 true,但 position 永远是 0、 /// 也不发 buffering / error(实测公网链路可静默卡 75s+)。 @@ -324,6 +330,7 @@ class AudioService extends ChangeNotifier { _hasStartedCurrentPlayback = false; // 切歌:位置基准归零,避免上一首的最后位置被当成这一首的"最后已知位置" _lastKnownPosition = Duration.zero; + _recoveryAttempts = 0; try { _playbackGeneration++; @@ -606,6 +613,17 @@ class AudioService extends ChangeNotifier { final url = song.url; if (url == null || url.isEmpty) return; + // 熔断:同一首歌连续恢复失败,说明不是"卡了一下", + // 而是这个文件/连接根本拿不到数据。继续重试只会刷日志、抢带宽。 + if (_recoveryAttempts >= _maxRecoveryAttempts) { + debugPrint('⛔ [AudioService] 已连续恢复 $_recoveryAttempts 次仍无法播放,' + '停止自动重试:${song.title}'); + _lastPlaybackError = '无法播放:${song.title}'; + notifyListeners(); + return; + } + _recoveryAttempts++; + final now = DateTime.now(); if (!force && _lastStallRecoveryAt != null && @@ -1030,6 +1048,11 @@ class AudioService extends ChangeNotifier { _lastKnownPosition = position; } + // 真的出声了(>1s),说明恢复成功,重置熔断计数 + if (position > const Duration(seconds: 1)) { + _recoveryAttempts = 0; + } + // 首次拿到 >0 的播放位置:这是"真正出声"的时刻, // 与 play() 发起时间相减即可算出起播延迟。 if (!_firstAudioReported && position > Duration.zero) {