import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:hgdj/tools_base/debug_log.dart'; import 'package:http/http.dart' hide Response; import '../../extension/extensions.dart'; import '../../hj_utils/file_util.dart'; import '../net/load_apk/dio_cli.dart'; import 'cancel_token_manager.dart'; /// dio 文件下载器(视频流缓存的 FileService) class DioFileService extends FileService { /// 真正请求的 dio final _dio = createDio(); @override Future get(String url, {Map? headers = const {}}) async { // cancelToken 用文件名注册、下载结束后按同一 key 移除 //(原来 createToken 用 name 而 remove 用 url,key 不一致:既移不掉导致泄漏,又会 firstWhere 抛 StateError) final name = FileUtil.getName(url); final token = CancelTokenManager().createToken(name); // 每次请求用独立 Options:本类随 VideoCacheManager 单例,共享一份 Options 会被并发请求互相覆盖 headers final options = Options( method: 'GET', sendTimeout: const Duration(milliseconds: 5000), receiveTimeout: const Duration(milliseconds: 10000), headers: {...?headers, 'cache-control': 'max-age=31104000'}, contentType: ContentType.binary.toString(), responseType: ResponseType.stream, ); Response? resp; try { debugLog("DioFileService get: $url"); resp = await _dio.get(url, cancelToken: token, options: options); } catch (e) { debugLog("DioFileService get error: $url -> $e"); } finally { CancelTokenManager().remove(name); } // 只认 200/206;其余(含请求异常 resp==null)直接抛:避免返回空流挂起、坏响应被缓存 final statusCode = resp?.statusCode ?? HttpStatus.badRequest; if (resp?.data == null || (statusCode != 200 && statusCode != 206)) { throw HttpException("video chunk fetch failed ($statusCode): $url"); } final respHeaders = {}; resp!.headers.forEach((key, values) { if (values.notEmpty()) respHeaders[key] = values.first; }); // Content-Length 可能缺失/非数字,安全解析(原 contentLengthS[0] 在空串时会 RangeError);缺失传 null 表示长度未知 final contentLength = int.tryParse(respHeaders[Headers.contentLengthHeader] ?? ''); debugLog( "DioFileService resp($statusCode): $url contentLength:$contentLength"); return HttpGetResponse(StreamedResponse( resp.data!.stream, statusCode, headers: respHeaders, contentLength: contentLength, )); } }