fix(playback): 增加进度看门狗,兜住不产生 buffering 事件的半开连接
公网链路实测(清远->华为云->香港CN2->openresty->OpenList HTTPS)中 出现过 tcp: ffurl_read 错误但未触发 buffering=true 的情况, 仅靠缓冲事件无法覆盖这类"连接半死"的卡顿。 - 每 10s 检查一次播放进度;播放中且 30s 未推进 → 触发自愈(重开 + seek 回原位) - 切歌中/暂停时重置基准,避免误触发 - 与 buffering 看门狗共用 2 分钟节流 验证:公网路径连续播放 65s,无 false positive,进度正常推进。
This commit is contained in:
@@ -89,6 +89,12 @@ class AudioService extends ChangeNotifier {
|
||||
Timer? _stallTimer;
|
||||
DateTime? _lastStallRecoveryAt;
|
||||
|
||||
/// 进度看门狗:某些卡死不会触发 buffering 事件(例如 CDN 半开连接),
|
||||
/// 用「播放中进度长时间不动」兜底。
|
||||
Timer? _progressTimer;
|
||||
Duration _lastObservedPosition = Duration.zero;
|
||||
DateTime _lastProgressAt = DateTime.now();
|
||||
|
||||
/// 最近一次播放失败的原因,供 UI 提示;成功播放后自动清空。
|
||||
String? _lastPlaybackError;
|
||||
String? get lastPlaybackError => _lastPlaybackError;
|
||||
@@ -344,8 +350,31 @@ class AudioService extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// 缓冲卡死自愈:重开当前歌曲并 seek 回原位置。
|
||||
///
|
||||
/// 进度看门狗:播放中但进度 30 秒没动,说明流已经卡死(半开连接/CDN 卡住)。
|
||||
void _checkPlaybackProgress() {
|
||||
if (_isChangingTrack || !_isPlaying) {
|
||||
// 切歌中或暂停时不参与判断,重置基准
|
||||
_lastObservedPosition = positionNotifier.value;
|
||||
_lastProgressAt = DateTime.now();
|
||||
return;
|
||||
}
|
||||
|
||||
final pos = positionNotifier.value;
|
||||
if (pos != _lastObservedPosition) {
|
||||
_lastObservedPosition = pos;
|
||||
_lastProgressAt = DateTime.now();
|
||||
return;
|
||||
}
|
||||
|
||||
if (DateTime.now().difference(_lastProgressAt) >= _stallThreshold) {
|
||||
debugPrint('🩺 [AudioService] 播放进度卡在 $pos 超过 '
|
||||
'${_stallThreshold.inSeconds}s,触发自愈');
|
||||
_lastProgressAt = DateTime.now();
|
||||
_recoverFromStall();
|
||||
}
|
||||
}
|
||||
|
||||
/// 缓冲卡死自愈:重开当前歌曲并 seek 回原位置。 ///
|
||||
/// 触发条件:mpv 的 buffering 持续为 true 超过 [_stallThreshold]。
|
||||
/// 两次自动恢复之间至少间隔 2 分钟,避免源站长时间不可用时反复重连。
|
||||
Future<void> _recoverFromStall() async {
|
||||
@@ -746,6 +775,15 @@ class AudioService extends ChangeNotifier {
|
||||
}),
|
||||
);
|
||||
|
||||
// 进度看门狗:有些半开连接不会产生 buffering=true,
|
||||
// 但播放进度会一直停在原地,这里兜住这种情况。
|
||||
_progressTimer?.cancel();
|
||||
_lastProgressAt = DateTime.now();
|
||||
_progressTimer = Timer.periodic(
|
||||
const Duration(seconds: 10),
|
||||
(_) => _checkPlaybackProgress(),
|
||||
);
|
||||
|
||||
// ⭐ 完整生命周期校验的 completed 监听
|
||||
_subscriptions.add(
|
||||
player.stream.completed.listen((_) {
|
||||
@@ -969,6 +1007,8 @@ class AudioService extends ChangeNotifier {
|
||||
// 先保存再清理
|
||||
_stallTimer?.cancel();
|
||||
_stallTimer = null;
|
||||
_progressTimer?.cancel();
|
||||
_progressTimer = null;
|
||||
savePlaybackState();
|
||||
_stopListening();
|
||||
positionNotifier.dispose();
|
||||
|
||||
Reference in New Issue
Block a user