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) {