Files
QingTing-Player/lib/controllers/playback_controller.dart
T
lxh2875931338 708ab91ccb fix(playback): 播放页/迷你播放器立即显示已有封面,不再等元数据流程
原因有两层:
1) Song 模型没有封面路径,播放时只带 url/title/artist,
   播放页只能等元数据服务返回 artwork 字节后才显示;
2) 元数据回来后用新 Song 覆盖当前歌曲时丢了 artworkPath,
   即使带上了也会被冲掉。

- Song 增加 artworkPath 字段
- PlaybackController / 首页收藏 / playPlaylist 构造 Song 时带上
- 播放页:artwork 字节优先,其次直接读 artwork_path 文件
- 迷你播放器同样加 FileImage 兜底
- 元数据更新重建 Song 时保留 artworkPath

真机验证:点击播放后封面立即出现,不再先显示音符占位。
2026-11-01 16:15:49 +08:00

147 lines
6.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// lib/controllers/playback_controller.dart
import 'package:flutter/foundation.dart'; // ⭐ 添加
import '../services/audio_service.dart';
import '../services/webdav_service.dart';
import '../database/song_database.dart';
class PlaybackController {
static final PlaybackController _instance = PlaybackController._internal();
factory PlaybackController() => _instance;
PlaybackController._internal();
final AudioService _audioService = AudioService();
final SongDatabase _db = SongDatabase();
// ════════════════════════════════════════════════════════════
// 从 WebDAV 目录播放(点击歌曲)
// ════════════════════════════════════════════════════════════
Future<void> playFromWebDAV({
required String directoryPath,
required String clickedSongPath,
required List<WebDAVItem> allItems,
}) async {
// 1. 过滤出所有音乐文件(去掉目录)
final musicFiles = allItems.where((item) => !item.isDirectory).toList()
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
if (musicFiles.isEmpty) return;
// 2. 调试信息
debugPrint('🎯 [PlaybackController] musicFiles:');
for (int i = 0; i < musicFiles.length; i++) {
debugPrint(' $i: ${musicFiles[i].path}');
}
debugPrint('🎯 [PlaybackController] clickedSongPath: $clickedSongPath');
// 3. 找到点击歌曲在音乐文件列表中的位置
final clickedIndex =
musicFiles.indexWhere((item) => item.path == clickedSongPath);
debugPrint('🎯 [PlaybackController] clickedIndex: $clickedIndex');
if (clickedIndex == -1) {
debugPrint(
'⚠️ [PlaybackController] clicked song not found, fallback to single play');
await _playSingleSong(clickedSongPath);
return;
}
// 4. 构建 Song 队列
final queue = await _buildSongQueue(musicFiles);
// 5. 应用播放模式
final playMode = _audioService.playMode;
List<Song> finalQueue = List<Song>.from(queue);
int startIndex = clickedIndex;
if (playMode == PlayMode.shuffle) {
final shuffled = List<Song>.from(queue)..shuffle();
startIndex = shuffled.indexWhere((s) => s.id == clickedSongPath);
if (startIndex == -1) startIndex = 0;
finalQueue = shuffled;
}
debugPrint(
'🎯 [PlaybackController] finalQueue length: ${finalQueue.length}, startIndex: $startIndex');
_audioService.setQueue(finalQueue, startIndex: startIndex);
}
// ════════════════════════════════════════════════════════════
// 从 WebDAV 目录播放(全部播放)
// ════════════════════════════════════════════════════════════
Future<void> playAllFromWebDAV({
required String directoryPath,
required List<WebDAVItem> allItems,
}) async {
final musicFiles = allItems.where((item) => !item.isDirectory).toList()
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
if (musicFiles.isEmpty) return;
final queue = await _buildSongQueue(musicFiles);
final playMode = _audioService.playMode;
List<Song> finalQueue = List<Song>.from(queue);
if (playMode == PlayMode.shuffle) {
finalQueue = List<Song>.from(queue)..shuffle();
}
_audioService.setQueue(finalQueue, startIndex: 0);
}
// ════════════════════════════════════════════════════════════
// 单曲播放(fallback)
// ════════════════════════════════════════════════════════════
Future<void> _playSingleSong(String songPath) async {
debugPrint('🎯 [PlaybackController] _playSingleSong: $songPath');
final url = WebDAVService.instance.getFileUrl(songPath);
final dbSong = await _db.getSongByPath(songPath);
final song = Song(
id: songPath,
artworkPath: dbSong?['artwork_path'] as String?,
title: dbSong?['title'] as String? ??
songPath.split('/').last.replaceAll(RegExp(r'\.[^.]*$'), ''),
artist: dbSong?['artist'] as String? ?? '未知艺术家',
url: url,
);
_audioService.playSong(song);
}
// ════════════════════════════════════════════════════════════
// 构建 Song 队列
// ════════════════════════════════════════════════════════════
Future<List<Song>> _buildSongQueue(List<WebDAVItem> musicFiles) async {
final queue = <Song>[];
for (final item in musicFiles) {
final dbSong = await _db.getSongByPath(item.path);
final url = WebDAVService.instance.getFileUrl(item.path);
if (dbSong != null) {
queue.add(Song(
id: item.path,
artworkPath: dbSong['artwork_path'] as String?,
title: dbSong['title'] as String? ??
item.name.replaceAll(RegExp(r'\.[^.]*$'), ''),
artist: dbSong['artist'] as String? ?? '未知艺术家',
url: url,
));
} else {
queue.add(Song(
id: item.path,
title: item.name.replaceAll(RegExp(r'\.[^.]*$'), ''),
artist: '未知艺术家',
url: url,
));
}
}
return queue;
}
// ════════════════════════════════════════════════════════════
// 播放控制透传
// ════════════════════════════════════════════════════════════
void next() => _audioService.next();
void previous() => _audioService.previous();
void togglePlayMode() => _audioService.togglePlayMode();
PlayMode get playMode => _audioService.playMode;
}