import 'dart:io'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; /// 与图片等分开的专用视频磁盘缓存(`flutter_cache_manager`)。 final CacheManager funymeeVideoCacheManager = CacheManager( Config( 'funymeeVideoCache', stalePeriod: const Duration(days: 14), maxNrOfCacheObjects: 200, ), ); /// 用 **scheme + host + path** 作缓存键,忽略 **query**(GCS 等签名参数每次变化,否则永远 cache miss)。 /// 下载请求仍使用完整 [url](含 query)。 String videoCacheKeyForUrl(String url) { final raw = url.trim(); final u = Uri.tryParse(raw); if (u == null || !u.hasScheme) return raw; if (u.scheme != 'http' && u.scheme != 'https') return raw; if (u.host.isEmpty) return raw; final path = u.path.isEmpty ? '/' : u.path; return '${u.scheme}://${u.host}$path'; } /// 排除明显非视频的磁盘文件(HTML 错误页、JSON、过小),避免 ExoPlayer /// [UnrecognizedInputFormatException] 反复读坏缓存。 Future videoCachedFileLooksPlayable(File f) async { try { final len = await f.length(); if (len < 512) return false; late List head; try { head = await f.openRead(0, 16).first; } catch (_) { return false; } if (head.length < 12) return false; // HTML / XML 错误页 if (head[0] == 0x3C) { final s = String.fromCharCodes(head.take(8)); if (s.startsWith('= 8) { final tag = String.fromCharCodes(head.sublist(4, 8)); if (tag == 'ftyp') return true; } // WebM EBML if (head[0] == 0x1a && head[1] == 0x45 && head[2] == 0xdf) return true; // 较大二进制:交给解码器(避免误判少见封装) return len >= 4096; } catch (_) { return false; } }