播放路径是编码的,无元数据时直接截取文件名会显示 "%E6%9C%AA%E5%88%86..." 这种串。 - playPlaylist:统一解码后再去扩展名,DB 标题为空也回退到解码名 - PlaybackController:单曲与队列构建同样走 _displayName() - 有标签时仍优先用标签标题
160 lines
6.8 KiB
Dart
160 lines
6.8 KiB
Dart
// 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)
|
||
// ════════════════════════════════════════════════════════════
|
||
/// 播放路径/文件名是百分号编码的,展示前必须解码
|
||
String _displayName(String pathOrName) {
|
||
final raw = pathOrName.split('/').last;
|
||
String decoded;
|
||
try {
|
||
decoded = Uri.decodeComponent(raw);
|
||
} catch (_) {
|
||
decoded = raw;
|
||
}
|
||
return decoded.replaceAll(RegExp(r'\.[^.]*$'), '');
|
||
}
|
||
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?)?.trim().isNotEmpty == true
|
||
? dbSong!['title'] as String
|
||
: _displayName(songPath),
|
||
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?)?.trim().isNotEmpty == true
|
||
? dbSong['title'] as String
|
||
: _displayName(item.path),
|
||
artist: dbSong['artist'] as String? ?? '未知艺术家',
|
||
url: url,
|
||
));
|
||
} else {
|
||
queue.add(Song(
|
||
id: item.path,
|
||
title: _displayName(item.path),
|
||
artist: '未知艺术家',
|
||
url: url,
|
||
));
|
||
}
|
||
}
|
||
return queue;
|
||
}
|
||
|
||
// ════════════════════════════════════════════════════════════
|
||
// 播放控制透传
|
||
// ════════════════════════════════════════════════════════════
|
||
void next() => _audioService.next();
|
||
void previous() => _audioService.previous();
|
||
void togglePlayMode() => _audioService.togglePlayMode();
|
||
PlayMode get playMode => _audioService.playMode;
|
||
}
|