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(), );