Files
QingTing-Player/lib/controllers/playback_controller.dart
T
lxh2875931338 3a60cabca5 fix(playback): 歌单/文件列表播放时标题不再显示百分号编码
播放路径是编码的,无元数据时直接截取文件名会显示
"%E6%9C%AA%E5%88%86..." 这种串。

- playPlaylist:统一解码后再去扩展名,DB 标题为空也回退到解码名
- PlaybackController:单曲与队列构建同样走 _displayName()
- 有标签时仍优先用标签标题
2026-11-01 16:34:44 +08:00

160 lines
6.8 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)
// ════════════════════════════════════════════════════════════
/// 播放路径/文件名是百分号编码的,展示前必须解码
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;
}