Files
QingTing-Player/lib/services/prefetch_service.dart
T
lxh2875931338 2d571eaae4 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
对比下一首的 "起播耗时" 是否下降。
2026-10-23 23:06:18 +08:00

69 lines
1.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// lib/services/prefetch_service.dart
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'webdav_service.dart';
/// 下一首预热。
///
/// 目的:在用户真正切歌之前,提前完成 DNS 解析、TLS 握手、反代/CDN 的首包,
/// 让 mpv 起播时少等一会儿。
///
/// 只发 HEAD + `Range: bytes=0-0`,不下载正文,带宽开销可以忽略;
/// 失败也完全静默,不影响当前播放。
class PrefetchService {
static final PrefetchService _instance = PrefetchService._internal();
factory PrefetchService() => _instance;
PrefetchService._internal();
final Dio _dio = Dio();
String? _lastUrl;
bool _busy = false;
Future<void> warm(String url) async {
if (url.isEmpty) return;
if (_busy) return;
if (_lastUrl == url) return;
_busy = true;
_lastUrl = url;
final sw = Stopwatch()..start();
try {
final headers = await WebDAVService.instance.getAuthHeaders();
if (headers.isEmpty) return;
final response = await _dio.head(
url,
options: Options(
headers: {
...headers,
'Range': 'bytes=0-0',
},
receiveTimeout: const Duration(seconds: 8),
sendTimeout: const Duration(seconds: 5),
// HEAD 返回 4xx/5xx 也不抛异常,交给日志记录
validateStatus: (_) => true,
),
);
debugPrint('🔥 [Prefetch] 预热完成 ${sw.elapsedMilliseconds}ms,'
'status=${response.statusCode}');
} catch (e) {
debugPrint('⚠️ [Prefetch] 预热失败(忽略): $e');
} finally {
_busy = false;
// 允许同一首歌在下一轮重新预热(比如重播)
if (_lastUrl == url) {
// 保留 _lastUrl 做去重,下一次换歌时会被覆盖
}
}
}
/// 切歌/停止时清掉去重状态
void reset() {
_lastUrl = null;
}
}