From 7bd23bfee607607e96c559df6ba3cd7da11b487f Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Tue, 25 Aug 2026 21:57:04 +0800 Subject: [PATCH] =?UTF-8?q?metadata=E8=AF=BB=E5=8F=96=E8=83=BD=E5=8A=9B?= =?UTF-8?q?=E5=B7=B2=E8=BE=BE=E6=88=90=EF=BC=8C=E6=8E=A5=E4=B8=8B=E6=9D=A5?= =?UTF-8?q?=E4=BC=9A=E9=A1=BA=E7=9D=80=E8=B7=AF=E5=BE=84=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E9=83=A8=E5=88=86=E7=BC=93=E5=AD=98=E6=8C=87=E9=92=88=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/.gitignore | 1 + lib/main.dart | 2 - lib/metadata/file_provider.dart | 58 ++++++++++++++++++++++----- lib/metadata/metadata_normalizer.dart | 18 ++++----- lib/metadata/metadata_reader.dart | 6 +-- lib/metadata/metadata_service.dart | 54 +++++++++++++++++++++---- pubspec.lock | 2 +- pubspec.yaml | 1 + 8 files changed, 107 insertions(+), 35 deletions(-) diff --git a/android/.gitignore b/android/.gitignore index d64ce7c..af2ddcd 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -15,3 +15,4 @@ GeneratedPluginRegistrant.java key.properties **/*.keystore **/*.jks +.settings diff --git a/lib/main.dart b/lib/main.dart index 6da0efb..8ab6e0d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -187,8 +187,6 @@ class _QTPlayerAppState extends State { } _lifecycleListener = (status) { - final audioService = context.read(); - switch (status) { case AppLifecycleStatus.paused: case AppLifecycleStatus.inactive: diff --git a/lib/metadata/file_provider.dart b/lib/metadata/file_provider.dart index d184457..108fd6a 100644 --- a/lib/metadata/file_provider.dart +++ b/lib/metadata/file_provider.dart @@ -1,11 +1,57 @@ // lib/metadata/file_provider.dart import 'dart:io'; +import 'package:flutter/foundation.dart'; // ⭐ 添加 +import 'package:path_provider/path_provider.dart'; +import 'package:dio/dio.dart'; +import '../services/webdav_service.dart'; abstract class FileProvider { - /// 获取文件,如果是远程文件则先下载到本地缓存 Future getFile(String url); } +class WebDAVFileProvider implements FileProvider { + final Dio _dio = Dio(); + + @override + Future getFile(String url) async { + try { + // 1. 获取缓存目录 + final cacheDir = await getTemporaryDirectory(); + final cachePath = '${cacheDir.path}/metadata_${url.hashCode}.tmp'; + final cacheFile = File(cachePath); + + // 2. 如果缓存存在且未过期,直接返回 + if (await cacheFile.exists()) { + final stat = await cacheFile.stat(); + // 缓存 1 小时内有效(86400 秒) + if (DateTime.now().difference(stat.modified).inSeconds < 86400) { + return cacheFile; + } + } + + // 3. 获取认证头 + final headers = await WebDAVService.instance.getAuthHeaders(); + if (headers.isEmpty) return null; + + // 4. 只下载前 1MB + await _dio.download( + url, + cachePath, + options: Options( + headers: headers, + receiveTimeout: const Duration(seconds: 10), + sendTimeout: const Duration(seconds: 10), + ), + ); + + return cacheFile; + } catch (e) { + debugPrint('⚠️ [WebDAVFileProvider] download failed: $e'); + return null; + } + } +} + class LocalFileProvider implements FileProvider { @override Future getFile(String url) async { @@ -16,13 +62,3 @@ class LocalFileProvider implements FileProvider { return null; } } - -class WebDAVFileProvider implements FileProvider { - @override - Future getFile(String url) async { - // 🔴 第一阶段:先不实现下载,返回 null - // 这样 WebDAV 文件会 fallback 到文件名 - // 等核心播放逻辑稳定后,再实现真正的下载缓存 - return null; - } -} diff --git a/lib/metadata/metadata_normalizer.dart b/lib/metadata/metadata_normalizer.dart index 0b6694b..07f94bf 100644 --- a/lib/metadata/metadata_normalizer.dart +++ b/lib/metadata/metadata_normalizer.dart @@ -4,20 +4,19 @@ import 'metadata_model.dart'; class MetadataNormalizer { NormalizedMetadata normalize(RawMetadata raw, {String fileName = '', String filePath = ''}) { - // 1. 修剪空格 + // 1. 修剪 final title = raw.title.trim(); final artist = raw.artist.trim(); - final album = raw.album.trim(); // 2. 如果 title 为空,从文件名推断 final finalTitle = title.isNotEmpty ? title : _inferTitleFromFileName(fileName); - // 3. 如果 artist 为空,从路径推断或使用默认值 + // 3. 如果 artist 为空,从路径推断 final finalArtist = artist.isNotEmpty ? artist : _inferArtistFromPath(filePath); - // 4. 如果 performers 不为空且 artist 为空,用 performers 的第一个 + // 4. 如果 performers 不为空且 artist 为空,使用 performers final finalArtist2 = finalArtist.isNotEmpty ? finalArtist : (raw.performers.isNotEmpty ? raw.performers.first : '未知艺术家'); @@ -25,7 +24,7 @@ class MetadataNormalizer { return NormalizedMetadata( title: finalTitle, artist: finalArtist2, - album: album, + album: raw.album, genres: raw.genres, performers: raw.performers, year: raw.year, @@ -40,17 +39,16 @@ class MetadataNormalizer { String _inferTitleFromFileName(String fileName) { final dotIndex = fileName.lastIndexOf('.'); - if (dotIndex > 0) { - return fileName.substring(0, dotIndex).trim(); - } - return fileName.trim(); + return dotIndex > 0 + ? fileName.substring(0, dotIndex).trim() + : fileName.trim(); } String _inferArtistFromPath(String filePath) { final parts = filePath.split(RegExp(r'[/\\]')); if (parts.length >= 2) { final candidate = parts[parts.length - 2].trim(); - if (candidate.isNotEmpty && !candidate.contains(' ')) { + if (candidate.isNotEmpty && !candidate.startsWith('http')) { return candidate; } } diff --git a/lib/metadata/metadata_reader.dart b/lib/metadata/metadata_reader.dart index dc80765..1863026 100644 --- a/lib/metadata/metadata_reader.dart +++ b/lib/metadata/metadata_reader.dart @@ -1,5 +1,6 @@ // lib/metadata/metadata_reader.dart import 'dart:io'; +import 'package:flutter/foundation.dart'; // ⭐ 添加 import 'package:audio_metadata_reader/audio_metadata_reader.dart' as amr; import 'metadata_model.dart'; @@ -12,7 +13,7 @@ class MetadataReader { title: meta.title ?? '', artist: meta.artist ?? '', album: meta.album ?? '', - // ⭐ genres 和 performers 是非空 List,直接使用 + // ⭐ genres 和 performers 是非空 List,不需要 ?? genres: meta.genres, performers: meta.performers, year: _toInt(meta.year), @@ -23,16 +24,15 @@ class MetadataReader { duration: meta.duration, ); } catch (e) { + debugPrint('⚠️ [MetadataReader] read failed: $e'); return const RawMetadata(); } } - /// 安全转换为 int,处理 Object 类型 int _toInt(dynamic value) { if (value == null) return 0; if (value is int) return value; if (value is String) return int.tryParse(value) ?? 0; - // 有些字段可能是 double 或 num if (value is num) return value.toInt(); return 0; } diff --git a/lib/metadata/metadata_service.dart b/lib/metadata/metadata_service.dart index 1fef191..fe41529 100644 --- a/lib/metadata/metadata_service.dart +++ b/lib/metadata/metadata_service.dart @@ -1,38 +1,76 @@ // lib/metadata/metadata_service.dart +// ⭐ 移除未使用的 import 'dart:io'; +import 'package:flutter/foundation.dart'; // ⭐ 添加 import 'metadata_model.dart'; +import 'metadata_reader.dart'; +import 'metadata_normalizer.dart'; import 'metadata_cache.dart'; +import 'file_provider.dart'; class MetadataService { static final MetadataService _instance = MetadataService._internal(); factory MetadataService() => _instance; MetadataService._internal(); + final MetadataReader _reader = MetadataReader(); + final MetadataNormalizer _normalizer = MetadataNormalizer(); final MetadataCache _cache = MetadataCache(); - /// 临时版本:不进行实际文件读取,只返回基于文件名的 fallback + FileProvider _getProvider(String url) { + if (url.startsWith('http://') || url.startsWith('https://')) { + return WebDAVFileProvider(); + } + return LocalFileProvider(); + } + Future getMetadata({ required String url, required String fileName, required String fileId, bool forceRefresh = false, }) async { - // 1. 检查缓存(如果有) + // 1. 检查缓存 if (!forceRefresh) { final cached = await _cache.get(fileId); if (cached != null && cached.isNotEmpty) { + debugPrint('📦 [MetadataService] cache hit: ${cached.title}'); return cached; } } - // 2. 暂时不读取文件,直接 fallback - final fallback = _fallbackFromFileName(fileName); + // 2. 获取文件 + final provider = _getProvider(url); + final file = await provider.getFile(url); - // 3. 缓存结果(即使 fallback 也缓存,避免频繁调用) - if (fallback.isNotEmpty) { - await _cache.put(fileId, fallback); + if (file == null || !await file.exists()) { + debugPrint( + '⚠️ [MetadataService] file not available, using filename fallback'); + return _fallbackFromFileName(fileName); } - return fallback; + // 3. 读取 metadata + final raw = await _reader.readRawMetadata(file); + + // 4. 标准化 + final normalized = _normalizer.normalize( + raw, + fileName: fileName, + filePath: url, + ); + + // 5. 评估(第一阶段:直接接受) + final history = await _cache.getHistory(normalized.artist, limit: 10); + final candidate = _normalizer.evaluate(normalized, history); + final finalMetadata = _normalizer.decide(candidate); + + // 6. 缓存 + if (finalMetadata.isNotEmpty) { + await _cache.put(fileId, finalMetadata); + debugPrint( + '✅ [MetadataService] metadata saved: ${finalMetadata.title} - ${finalMetadata.artist}'); + } + + return finalMetadata; } FinalMetadata _fallbackFromFileName(String fileName) { diff --git a/pubspec.lock b/pubspec.lock index 8c5e31d..60d0ce9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -441,7 +441,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 diff --git a/pubspec.yaml b/pubspec.yaml index f9c92e3..c78b3e6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: flutter_cache_manager: ^3.3.1 sqflite: ^2.3.0 audio_metadata_reader: ^1.7.1 + path_provider: ^2.1.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.