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:
@@ -0,0 +1,68 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user