引入部分缓存机制
引入封面图信息 正在进入测试阶段
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
// lib/database/song_database.dart
|
||||
import 'package:path/path.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class SongDatabase {
|
||||
static final SongDatabase _instance = SongDatabase._internal();
|
||||
factory SongDatabase() => _instance;
|
||||
SongDatabase._internal();
|
||||
|
||||
static Database? _database;
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_database != null) return _database!;
|
||||
_database = await _initDatabase();
|
||||
return _database!;
|
||||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final path = join(dir.path, 'qingting_songs.db');
|
||||
return await openDatabase(
|
||||
path,
|
||||
version: 1,
|
||||
onCreate: _onCreate,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onCreate(Database db, int version) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE songs (
|
||||
song_key TEXT PRIMARY KEY,
|
||||
remote_path TEXT,
|
||||
file_size INTEGER,
|
||||
modified_time INTEGER,
|
||||
etag TEXT,
|
||||
title TEXT,
|
||||
artist TEXT,
|
||||
album TEXT,
|
||||
genre TEXT,
|
||||
artwork_path TEXT,
|
||||
confidence REAL DEFAULT 0.0,
|
||||
validation_count INTEGER DEFAULT 0,
|
||||
first_scan INTEGER,
|
||||
last_scan INTEGER,
|
||||
metadata_status TEXT DEFAULT 'pending'
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE metadata_cache (
|
||||
song_key TEXT PRIMARY KEY,
|
||||
cache_path TEXT,
|
||||
cache_created_at INTEGER,
|
||||
cache_size INTEGER,
|
||||
FOREIGN KEY (song_key) REFERENCES songs(song_key) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('CREATE INDEX idx_songs_artist ON songs(artist)');
|
||||
await db.execute('CREATE INDEX idx_songs_title ON songs(title)');
|
||||
}
|
||||
|
||||
// ---- 查询 ----
|
||||
Future<Map<String, dynamic>?> getSong(String songKey) async {
|
||||
final db = await database;
|
||||
final result =
|
||||
await db.query('songs', where: 'song_key = ?', whereArgs: [songKey]);
|
||||
return result.isNotEmpty ? result.first : null;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getSongsByArtist(String artist,
|
||||
{int limit = 20}) async {
|
||||
if (artist.isEmpty) return [];
|
||||
final db = await database;
|
||||
return await db.query(
|
||||
'songs',
|
||||
where: 'artist = ?',
|
||||
whereArgs: [artist],
|
||||
limit: limit,
|
||||
);
|
||||
}
|
||||
|
||||
// ---- 写入 ----
|
||||
Future<void> insertSong(Map<String, dynamic> song) async {
|
||||
final db = await database;
|
||||
await db.insert('songs', song,
|
||||
conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
Future<void> updateSong(String songKey, Map<String, dynamic> updates) async {
|
||||
final db = await database;
|
||||
await db.update(
|
||||
'songs',
|
||||
updates,
|
||||
where: 'song_key = ?',
|
||||
whereArgs: [songKey],
|
||||
);
|
||||
}
|
||||
|
||||
// ---- 缓存 ----
|
||||
Future<void> insertCache(Map<String, dynamic> cache) async {
|
||||
final db = await database;
|
||||
await db.insert('metadata_cache', cache,
|
||||
conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getCache(String songKey) async {
|
||||
final db = await database;
|
||||
final result = await db
|
||||
.query('metadata_cache', where: 'song_key = ?', whereArgs: [songKey]);
|
||||
return result.isNotEmpty ? result.first : null;
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
final db = await database;
|
||||
await db.close();
|
||||
_database = null;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ class RawMetadata {
|
||||
final String title;
|
||||
final String artist;
|
||||
final String album;
|
||||
final List<String> genres; // ⬅️ 改为 List<String>
|
||||
final List<String> performers; // ⬅️ 新增
|
||||
final List<String> genres;
|
||||
final List<String> performers;
|
||||
final int year;
|
||||
final int trackNumber;
|
||||
final int trackTotal;
|
||||
@@ -83,13 +83,26 @@ class NormalizedMetadata {
|
||||
bool get isNotEmpty => !isEmpty;
|
||||
}
|
||||
|
||||
/// 候选元数据
|
||||
class CandidateMetadata {
|
||||
final NormalizedMetadata metadata;
|
||||
final double confidence;
|
||||
final Map<String, double> evidence;
|
||||
|
||||
const CandidateMetadata({
|
||||
required this.metadata,
|
||||
required this.confidence,
|
||||
this.evidence = const {},
|
||||
});
|
||||
}
|
||||
|
||||
/// 最终确认的元数据
|
||||
class FinalMetadata {
|
||||
final String title;
|
||||
final String artist;
|
||||
final String album;
|
||||
final String genre; // ⬅️ 简化为单个流派(取第一个)
|
||||
final List<String> genres; // ⬅️ 保留完整列表
|
||||
final String genre;
|
||||
final List<String> genres;
|
||||
final List<String> performers;
|
||||
final int year;
|
||||
final int trackNumber;
|
||||
@@ -128,16 +141,3 @@ class FinalMetadata {
|
||||
|
||||
bool get isNotEmpty => !isEmpty;
|
||||
}
|
||||
|
||||
/// 候选元数据
|
||||
class CandidateMetadata {
|
||||
final NormalizedMetadata metadata;
|
||||
final double confidence;
|
||||
final Map<String, double> evidence;
|
||||
|
||||
const CandidateMetadata({
|
||||
required this.metadata,
|
||||
required this.confidence,
|
||||
this.evidence = const {},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,19 +4,13 @@ import 'metadata_model.dart';
|
||||
class MetadataNormalizer {
|
||||
NormalizedMetadata normalize(RawMetadata raw,
|
||||
{String fileName = '', String filePath = ''}) {
|
||||
// 1. 修剪
|
||||
final title = raw.title.trim();
|
||||
final artist = raw.artist.trim();
|
||||
|
||||
// 2. 如果 title 为空,从文件名推断
|
||||
final finalTitle =
|
||||
title.isNotEmpty ? title : _inferTitleFromFileName(fileName);
|
||||
|
||||
// 3. 如果 artist 为空,从路径推断
|
||||
final finalArtist =
|
||||
artist.isNotEmpty ? artist : _inferArtistFromPath(filePath);
|
||||
|
||||
// 4. 如果 performers 不为空且 artist 为空,使用 performers
|
||||
final finalArtist2 = finalArtist.isNotEmpty
|
||||
? finalArtist
|
||||
: (raw.performers.isNotEmpty ? raw.performers.first : '未知艺术家');
|
||||
@@ -57,6 +51,7 @@ class MetadataNormalizer {
|
||||
|
||||
CandidateMetadata evaluate(
|
||||
NormalizedMetadata normalized, List<Map<String, dynamic>> history) {
|
||||
// 第一版:直接接受,置信度 0.8
|
||||
return CandidateMetadata(
|
||||
metadata: normalized,
|
||||
confidence: 0.8,
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
// lib/metadata/metadata_reader.dart
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart'; // ⭐ 添加
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:audio_metadata_reader/audio_metadata_reader.dart' as amr;
|
||||
import 'metadata_model.dart';
|
||||
|
||||
class MetadataReader {
|
||||
Future<RawMetadata> readRawMetadata(File file) async {
|
||||
try {
|
||||
final meta = amr.readMetadata(file, getImage: false);
|
||||
// ⭐ getImage: true 读取封面图
|
||||
final meta = amr.readMetadata(file, getImage: true);
|
||||
|
||||
// ⭐ 提取第一张图片
|
||||
Uint8List? artwork;
|
||||
if (meta.pictures != null && meta.pictures!.isNotEmpty) {
|
||||
artwork = meta.pictures.first.bytes;
|
||||
}
|
||||
|
||||
return RawMetadata(
|
||||
title: meta.title ?? '',
|
||||
artist: meta.artist ?? '',
|
||||
album: meta.album ?? '',
|
||||
// ⭐ genres 和 performers 是非空 List<String>,不需要 ??
|
||||
genres: meta.genres,
|
||||
performers: meta.performers,
|
||||
year: _toInt(meta.year),
|
||||
@@ -22,6 +28,7 @@ class MetadataReader {
|
||||
discNumber: _toInt(meta.discNumber),
|
||||
totalDisc: _toInt(meta.totalDisc),
|
||||
duration: meta.duration,
|
||||
artwork: artwork,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ [MetadataReader] read failed: $e');
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// lib/metadata/metadata_service.dart
|
||||
// ⭐ 移除未使用的 import 'dart:io';
|
||||
import 'package:flutter/foundation.dart'; // ⭐ 添加
|
||||
import 'dart:io'; // ⭐ 添加这一行
|
||||
import 'dart:typed_data'; // ⭐ 如果已经有更好
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../database/song_database.dart';
|
||||
import 'metadata_model.dart';
|
||||
import 'metadata_reader.dart';
|
||||
import 'metadata_normalizer.dart';
|
||||
import 'metadata_cache.dart';
|
||||
import 'file_provider.dart';
|
||||
import '../utils/artwork_helper.dart';
|
||||
|
||||
class MetadataService {
|
||||
static final MetadataService _instance = MetadataService._internal();
|
||||
@@ -14,7 +17,13 @@ class MetadataService {
|
||||
|
||||
final MetadataReader _reader = MetadataReader();
|
||||
final MetadataNormalizer _normalizer = MetadataNormalizer();
|
||||
final MetadataCache _cache = MetadataCache();
|
||||
final MetadataCache _memoryCache = MetadataCache();
|
||||
final SongDatabase _db = SongDatabase();
|
||||
|
||||
String _generateSongKey(String url, int fileSize, int modifiedTime) {
|
||||
final raw = '$url|$fileSize|$modifiedTime';
|
||||
return raw.hashCode.toString();
|
||||
}
|
||||
|
||||
FileProvider _getProvider(String url) {
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
@@ -29,11 +38,11 @@ class MetadataService {
|
||||
required String fileId,
|
||||
bool forceRefresh = false,
|
||||
}) async {
|
||||
// 1. 检查缓存
|
||||
// 1. 内存缓存
|
||||
if (!forceRefresh) {
|
||||
final cached = await _cache.get(fileId);
|
||||
final cached = await _memoryCache.get(fileId);
|
||||
if (cached != null && cached.isNotEmpty) {
|
||||
debugPrint('📦 [MetadataService] cache hit: ${cached.title}');
|
||||
debugPrint('📦 [MetadataService] memory hit: ${cached.title}');
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
@@ -43,33 +52,96 @@ class MetadataService {
|
||||
final file = await provider.getFile(url);
|
||||
|
||||
if (file == null || !await file.exists()) {
|
||||
debugPrint(
|
||||
'⚠️ [MetadataService] file not available, using filename fallback');
|
||||
debugPrint('⚠️ [MetadataService] file not available, using fallback');
|
||||
return _fallbackFromFileName(fileName);
|
||||
}
|
||||
|
||||
// 3. 读取 metadata
|
||||
final stat = await file.stat();
|
||||
final songKey =
|
||||
_generateSongKey(url, stat.size, stat.modified.millisecondsSinceEpoch);
|
||||
|
||||
// 3. SQLite 查询(高置信度直接使用)
|
||||
if (!forceRefresh) {
|
||||
final dbSong = await _db.getSong(songKey);
|
||||
if (dbSong != null) {
|
||||
final confidence = (dbSong['confidence'] as num?)?.toDouble() ?? 0.0;
|
||||
if (confidence >= 0.6) {
|
||||
debugPrint(
|
||||
'📦 [MetadataService] SQLite hit: ${dbSong['title']} - ${dbSong['artist']}');
|
||||
|
||||
// 读取封面图
|
||||
final artworkPath = dbSong['artwork_path'] as String?;
|
||||
Uint8List? artwork;
|
||||
if (artworkPath != null && artworkPath.isNotEmpty) {
|
||||
try {
|
||||
final artFile = File(artworkPath);
|
||||
if (await artFile.exists()) {
|
||||
artwork = await artFile.readAsBytes();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
final metadata = FinalMetadata(
|
||||
title: dbSong['title'] as String? ?? '',
|
||||
artist: dbSong['artist'] as String? ?? '',
|
||||
album: dbSong['album'] as String? ?? '',
|
||||
genre: dbSong['genre'] as String? ?? '',
|
||||
artwork: artwork,
|
||||
source: 'database',
|
||||
);
|
||||
await _memoryCache.put(fileId, metadata);
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 读取原始 metadata
|
||||
final raw = await _reader.readRawMetadata(file);
|
||||
|
||||
// 4. 标准化
|
||||
// 5. 标准化
|
||||
final normalized = _normalizer.normalize(
|
||||
raw,
|
||||
fileName: fileName,
|
||||
filePath: url,
|
||||
);
|
||||
|
||||
// 5. 评估(第一阶段:直接接受)
|
||||
final history = await _cache.getHistory(normalized.artist, limit: 10);
|
||||
// 6. 评估
|
||||
final history = await _db.getSongsByArtist(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}');
|
||||
// 7. 保存封面图到本地
|
||||
String? artworkPath;
|
||||
if (finalMetadata.artwork != null && finalMetadata.artwork!.isNotEmpty) {
|
||||
artworkPath =
|
||||
await ArtworkHelper.saveArtwork(finalMetadata.artwork!, songKey);
|
||||
}
|
||||
|
||||
// 8. 保存到 SQLite
|
||||
await _db.insertSong({
|
||||
'song_key': songKey,
|
||||
'remote_path': url,
|
||||
'file_size': stat.size,
|
||||
'modified_time': stat.modified.millisecondsSinceEpoch,
|
||||
'title': finalMetadata.title,
|
||||
'artist': finalMetadata.artist,
|
||||
'album': finalMetadata.album,
|
||||
'genre': finalMetadata.genre,
|
||||
'artwork_path': artworkPath,
|
||||
'confidence': candidate.confidence,
|
||||
'validation_count': 1,
|
||||
'first_scan': DateTime.now().millisecondsSinceEpoch,
|
||||
'last_scan': DateTime.now().millisecondsSinceEpoch,
|
||||
'metadata_status': candidate.confidence >= 0.6 ? 'verified' : 'pending',
|
||||
});
|
||||
|
||||
// 9. 内存缓存
|
||||
await _memoryCache.put(fileId, finalMetadata);
|
||||
|
||||
debugPrint(
|
||||
'✅ [MetadataService] saved: ${finalMetadata.title} - ${finalMetadata.artist} (confidence: ${candidate.confidence})');
|
||||
return finalMetadata;
|
||||
}
|
||||
|
||||
|
||||
@@ -154,12 +154,17 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
spreadRadius: 10,
|
||||
),
|
||||
],
|
||||
image: song.artwork != null
|
||||
? DecorationImage(
|
||||
image: MemoryImage(song.artwork!),
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.music_note,
|
||||
size: 64,
|
||||
color: Colors.white24,
|
||||
),
|
||||
child: song.artwork == null
|
||||
? const Icon(Icons.music_note,
|
||||
size: 64, color: Colors.white24)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
Column(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// lib/services/audio_player_handler.dart
|
||||
|
||||
import 'dart:io'; // ⭐ 添加
|
||||
import 'dart:typed_data'; // ⭐ 添加
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:audio_service/audio_service.dart' as audio_service;
|
||||
import '../audio/player_controller.dart';
|
||||
@@ -53,22 +54,6 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler {
|
||||
});
|
||||
}
|
||||
|
||||
void _updateMediaItemPosition(Duration position) {
|
||||
final currentMediaItem = mediaItem.value;
|
||||
if (currentMediaItem != null) {
|
||||
mediaItem.add(audio_service.MediaItem(
|
||||
id: currentMediaItem.id,
|
||||
title: currentMediaItem.title,
|
||||
artist: currentMediaItem.artist,
|
||||
duration: currentMediaItem.duration,
|
||||
extras: {
|
||||
'position': position.inMilliseconds,
|
||||
...?currentMediaItem.extras,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _publishState() {
|
||||
final state = _state.playbackState;
|
||||
playbackState.add(audio_service.PlaybackState(
|
||||
@@ -111,38 +96,76 @@ class AudioPlayerHandler extends audio_service.BaseAudioHandler {
|
||||
);
|
||||
}
|
||||
|
||||
void updateNotification({
|
||||
required String id,
|
||||
required String title,
|
||||
required String artist,
|
||||
Uint8List? artwork,
|
||||
}) {
|
||||
_updateMediaItem(
|
||||
id: id,
|
||||
title: title,
|
||||
artist: artist,
|
||||
artwork: artwork,
|
||||
);
|
||||
}
|
||||
|
||||
void _updateMediaItem({
|
||||
required String id,
|
||||
required String title,
|
||||
required String artist,
|
||||
Duration? duration,
|
||||
Uint8List? artwork,
|
||||
}) {
|
||||
_currentId = id;
|
||||
_currentTitle = title;
|
||||
_currentArtist = artist;
|
||||
final position = _player.position;
|
||||
|
||||
debugPrint(
|
||||
'📢 [handler] updateMediaItem: $title - $artist (position=${position.inSeconds}s)');
|
||||
debugPrint('📢 [handler] updateMediaItem: $title - $artist');
|
||||
|
||||
// ⭐ 如果有 artwork,保存为临时文件并设置 artUri
|
||||
Uri? artUri;
|
||||
if (artwork != null && artwork.isNotEmpty) {
|
||||
try {
|
||||
final tempDir = Directory.systemTemp;
|
||||
final artPath = '${tempDir.path}/art_${id.hashCode}.jpg';
|
||||
final artFile = File(artPath);
|
||||
artFile.writeAsBytesSync(artwork);
|
||||
artUri = Uri.file(artPath);
|
||||
debugPrint('📢 [handler] artwork saved: $artPath');
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ [handler] save artwork failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
mediaItem.add(audio_service.MediaItem(
|
||||
id: id,
|
||||
title: title,
|
||||
artist: artist,
|
||||
duration: duration ?? _player.duration,
|
||||
artUri: artUri,
|
||||
extras: {
|
||||
'position': position.inMilliseconds,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
void updateNotification({
|
||||
required String id,
|
||||
required String title,
|
||||
required String artist,
|
||||
}) {
|
||||
_updateMediaItem(id: id, title: title, artist: artist);
|
||||
_publishState();
|
||||
void _updateMediaItemPosition(Duration position) {
|
||||
final currentMediaItem = mediaItem.value;
|
||||
if (currentMediaItem != null) {
|
||||
mediaItem.add(audio_service.MediaItem(
|
||||
id: currentMediaItem.id,
|
||||
title: currentMediaItem.title,
|
||||
artist: currentMediaItem.artist,
|
||||
duration: currentMediaItem.duration,
|
||||
artUri: currentMediaItem.artUri,
|
||||
extras: {
|
||||
'position': position.inMilliseconds,
|
||||
...?currentMediaItem.extras,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'playback_service.dart';
|
||||
import '../metadata/metadata_service.dart';
|
||||
import 'dart:typed_data';
|
||||
|
||||
enum PlayMode {
|
||||
sequential,
|
||||
@@ -16,12 +17,14 @@ class Song {
|
||||
final String title;
|
||||
final String artist;
|
||||
final String? url;
|
||||
final Uint8List? artwork;
|
||||
|
||||
Song({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.artist,
|
||||
this.url,
|
||||
this.artwork,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,6 +184,8 @@ class AudioService extends ChangeNotifier {
|
||||
title: metadata.title.isNotEmpty ? metadata.title : song.title,
|
||||
artist: metadata.artist.isNotEmpty ? metadata.artist : song.artist,
|
||||
url: song.url,
|
||||
artwork: metadata.artwork,
|
||||
// ⭐ 如果需要传递 artwork,可以在这里添加字段
|
||||
);
|
||||
_queue[_currentIndex] = updatedSong;
|
||||
_currentSong = updatedSong;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// lib/utils/artwork_helper.dart
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class ArtworkHelper {
|
||||
static const String _artworkDir = 'artworks';
|
||||
|
||||
/// 保存封面图到本地
|
||||
static Future<String?> saveArtwork(Uint8List data, String songKey) async {
|
||||
try {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final artworkDir = Directory('${dir.path}/$_artworkDir');
|
||||
if (!await artworkDir.exists()) {
|
||||
await artworkDir.create(recursive: true);
|
||||
}
|
||||
|
||||
final path = '${artworkDir.path}/$songKey.jpg';
|
||||
final file = File(path);
|
||||
await file.writeAsBytes(data);
|
||||
return path;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取封面图文件
|
||||
static Future<File?> getArtwork(String songKey) async {
|
||||
try {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final path = '${dir.path}/$_artworkDir/$songKey.jpg';
|
||||
final file = File(path);
|
||||
if (await file.exists()) {
|
||||
return file;
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除封面图
|
||||
static Future<void> deleteArtwork(String songKey) async {
|
||||
try {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final path = '${dir.path}/$_artworkDir/$songKey.jpg';
|
||||
final file = File(path);
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user