// 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 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; } }