feat(playback): 下一首预热(60% 时 HEAD 预握手)
日志实测:open 完成仅 37~60ms,但起播耗时 2.1~2.8s, 说明等待发生在 mpv 拿首批数据,而不是建连。 - 新增 PrefetchService:HEAD + Range 0-0,只走 DNS/TLS/反代, 不下载正文;失败静默,不影响播放 - AudioService 在进度达到 60%(且时长 > 60s)时预热下一首, 每首歌只触发一次;shuffle 模式下按当前随机索引推算 待验证:播到 60% 时应出现 🔥 [AudioService] 预热下一首:xxx 🔥 [Prefetch] 预热完成 xxxms,status=200/206 对比下一首的 "起播耗时" 是否下降。
This commit is contained in:
@@ -8,6 +8,7 @@ import 'package:media_kit/media_kit.dart' as media_kit;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'playback_service.dart';
|
||||
import 'network_watcher.dart';
|
||||
import 'prefetch_service.dart';
|
||||
import '../metadata/metadata_service.dart';
|
||||
import '../database/song_database.dart';
|
||||
import '../utils/artwork_helper.dart';
|
||||
@@ -107,6 +108,7 @@ class AudioService extends ChangeNotifier {
|
||||
/// 也不发 buffering / error(实测公网链路可静默卡 75s+)。
|
||||
DateTime? _trackStartAt;
|
||||
DateTime? _playReadyAt;
|
||||
int _prefetchedGeneration = -1;
|
||||
bool _firstAudioReported = false;
|
||||
|
||||
/// 最近一次播放失败的原因,供 UI 提示;成功播放后自动清空。
|
||||
@@ -418,6 +420,39 @@ class AudioService extends ChangeNotifier {
|
||||
}();
|
||||
}
|
||||
|
||||
/// 播到 60% 时预热下一首(HEAD + Range 0-0,不下载正文)。
|
||||
///
|
||||
/// 日志实测起播延迟 2~3s 都在"等首批数据",提前完成 DNS/TLS/反代握手
|
||||
/// 可以把这段时间压掉一部分;失败完全静默,不影响当前播放。
|
||||
void _maybePrefetchNext(Duration position) {
|
||||
final duration = durationNotifier.value;
|
||||
if (duration.inSeconds < 60) return;
|
||||
if (position.inMilliseconds < duration.inMilliseconds * 0.6) return;
|
||||
if (_prefetchedGeneration == _playbackGeneration) return;
|
||||
|
||||
final next = _peekNextSong();
|
||||
final url = next?.url;
|
||||
if (url == null || url.isEmpty) return;
|
||||
|
||||
_prefetchedGeneration = _playbackGeneration;
|
||||
debugPrint('🔥 [AudioService] 预热下一首:${next!.title}');
|
||||
PrefetchService().warm(url);
|
||||
}
|
||||
|
||||
/// 只推算下一首,不改变队列/索引(next() 会改)。
|
||||
Song? _peekNextSong() {
|
||||
if (_queue.isEmpty) return null;
|
||||
|
||||
if (_playMode == PlayMode.shuffle) {
|
||||
if (_shuffledIndices.isEmpty) return null;
|
||||
final nextIdx = (_shuffledIndex + 1) % _shuffledIndices.length;
|
||||
return _queue[_shuffledIndices[nextIdx]];
|
||||
}
|
||||
|
||||
final nextIdx = (_currentIndex + 1) % _queue.length;
|
||||
return _queue[nextIdx];
|
||||
}
|
||||
|
||||
/// 进度看门狗:播放中进度 30s 不动、且近期出现过缓冲,判定为卡死。
|
||||
///
|
||||
/// 为什么不能只看 `buffering`:弱网下它会在 true/false 之间反复抖动,
|
||||
@@ -861,6 +896,7 @@ class AudioService extends ChangeNotifier {
|
||||
_playReadyAt = null;
|
||||
}
|
||||
}
|
||||
_maybePrefetchNext(position);
|
||||
positionNotifier.value = position;
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user