fix(playback): 打开失败后 1.5s 立即重试,切歌前摇从 ~20s 降到 ~7s
实测时间线(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; 若首次打开成功则完全无感。
This commit is contained in:
@@ -121,6 +121,9 @@ class AudioService extends ChangeNotifier {
|
|||||||
/// 播放中定时持久化位置(冷启动能回到准确位置)
|
/// 播放中定时持久化位置(冷启动能回到准确位置)
|
||||||
DateTime _lastPositionSaveAt = DateTime.now();
|
DateTime _lastPositionSaveAt = DateTime.now();
|
||||||
|
|
||||||
|
/// 打开失败后的快速重试(每首歌最多一次)
|
||||||
|
int _openRetryGeneration = -1;
|
||||||
|
|
||||||
/// 最近一次播放失败的原因,供 UI 提示;成功播放后自动清空。
|
/// 最近一次播放失败的原因,供 UI 提示;成功播放后自动清空。
|
||||||
String? _lastPlaybackError;
|
String? _lastPlaybackError;
|
||||||
String? get lastPlaybackError => _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 回原位置。
|
/// 卡死/网络切换自愈:重开当前歌曲并 seek 回原位置。
|
||||||
///
|
///
|
||||||
/// [force] 为 true 时跳过节流(用于网络切换这种明确的外部事件)。
|
/// [force] 为 true 时跳过节流(用于网络切换这种明确的外部事件)。
|
||||||
@@ -988,6 +1010,12 @@ class AudioService extends ChangeNotifier {
|
|||||||
debugPrint('❌ [AudioService] player error: $error');
|
debugPrint('❌ [AudioService] player error: $error');
|
||||||
_lastPlaybackError = error;
|
_lastPlaybackError = error;
|
||||||
notifyListeners();
|
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();
|
_progressTimer?.cancel();
|
||||||
_lastProgressAt = DateTime.now();
|
_lastProgressAt = DateTime.now();
|
||||||
_progressTimer = Timer.periodic(
|
_progressTimer = Timer.periodic(
|
||||||
const Duration(seconds: 10),
|
const Duration(seconds: 5),
|
||||||
(_) => _checkPlaybackProgress(),
|
(_) => _checkPlaybackProgress(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user