diff --git a/lib/controllers/playback_controller.dart b/lib/controllers/playback_controller.dart index 26da116..2f4d8e3 100644 --- a/lib/controllers/playback_controller.dart +++ b/lib/controllers/playback_controller.dart @@ -1,4 +1,5 @@ // 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'; @@ -12,27 +13,43 @@ class PlaybackController { final SongDatabase _db = SongDatabase(); // ════════════════════════════════════════════════════════════ - // 播放 WebDAV 目录中的歌曲(从点击歌曲开始) + // 从 WebDAV 目录播放(点击歌曲) // ════════════════════════════════════════════════════════════ Future playFromWebDAV({ required String directoryPath, required String clickedSongPath, required List allItems, }) async { - final musicFiles = allItems.where((item) => !item.isDirectory).toList(); + // 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 finalQueue = List.from(queue); int startIndex = clickedIndex; @@ -43,23 +60,27 @@ class PlaybackController { finalQueue = shuffled; } + debugPrint( + '🎯 [PlaybackController] finalQueue length: ${finalQueue.length}, startIndex: $startIndex'); + _audioService.setQueue(finalQueue, startIndex: startIndex); } // ════════════════════════════════════════════════════════════ - // 播放整个目录(从第一首开始) + // 从 WebDAV 目录播放(全部播放) // ════════════════════════════════════════════════════════════ Future playAllFromWebDAV({ required String directoryPath, required List allItems, }) async { - final musicFiles = allItems.where((item) => !item.isDirectory).toList(); + 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 finalQueue = List.from(queue); if (playMode == PlayMode.shuffle) { finalQueue = List.from(queue)..shuffle(); @@ -69,9 +90,10 @@ class PlaybackController { } // ════════════════════════════════════════════════════════════ - // 播放单曲(fallback) + // 单曲播放(fallback) // ════════════════════════════════════════════════════════════ Future _playSingleSong(String songPath) async { + debugPrint('🎯 [PlaybackController] _playSingleSong: $songPath'); final url = WebDAVService.instance.getFileUrl(songPath); final dbSong = await _db.getSongByPath(songPath); final song = Song( @@ -85,7 +107,7 @@ class PlaybackController { } // ════════════════════════════════════════════════════════════ - // 辅助方法:从音乐文件列表构建 Song 队列 + // 构建 Song 队列 // ════════════════════════════════════════════════════════════ Future> _buildSongQueue(List musicFiles) async { final queue = []; @@ -113,19 +135,10 @@ class PlaybackController { } // ════════════════════════════════════════════════════════════ - // 播放控制(透传到 AudioService) + // 播放控制透传 // ════════════════════════════════════════════════════════════ - void next() { - _audioService.next(); - } - - void previous() { - _audioService.previous(); - } - - void togglePlayMode() { - _audioService.togglePlayMode(); - } - + void next() => _audioService.next(); + void previous() => _audioService.previous(); + void togglePlayMode() => _audioService.togglePlayMode(); PlayMode get playMode => _audioService.playMode; } diff --git a/lib/pages/webdav_file_list_page.dart b/lib/pages/webdav_file_list_page.dart index c42708a..5c5e024 100644 --- a/lib/pages/webdav_file_list_page.dart +++ b/lib/pages/webdav_file_list_page.dart @@ -1,9 +1,6 @@ // lib/pages/webdav_file_list_page.dart import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import '../services/webdav_service.dart'; -import '../services/playback_service.dart'; -import '../services/audio_service.dart'; import '../constants/ui_constants.dart'; import '../base/base_state.dart'; import '../controllers/playback_controller.dart'; @@ -79,12 +76,11 @@ class _WebDAVFileListPageState extends BaseState { ); } -// ============================================================ -// 播放单首(点击歌曲) -// ============================================================ + // ============================================================ + // ⭐ 播放单首(使用 PlaybackController) + // ============================================================ void _playSong(WebDAVItem file) async { try { - // ⭐ 直接使用 PlaybackController 单例 await PlaybackController().playFromWebDAV( directoryPath: _currentPath, clickedSongPath: file.path, @@ -112,12 +108,11 @@ class _WebDAVFileListPageState extends BaseState { } } -// ============================================================ -// 全部播放 -// ============================================================ + // ============================================================ + // ⭐ 全部播放(使用 PlaybackController) + // ============================================================ void _playAll() async { try { - // ⭐ 全部播放使用 playAllFromWebDAV await PlaybackController().playAllFromWebDAV( directoryPath: _currentPath, allItems: _items, diff --git a/lib/services/app_lifecycle_manager.dart b/lib/services/app_lifecycle_manager.dart index 166ca4c..24d370a 100644 --- a/lib/services/app_lifecycle_manager.dart +++ b/lib/services/app_lifecycle_manager.dart @@ -1,8 +1,5 @@ // lib/services/app_lifecycle_manager.dart -import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:media_kit/media_kit.dart'; /// App 生命周期状态(扩展了 Flutter 原生状态) enum AppLifecycleStatus { diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index 31fcf41..8be63c5 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -3,14 +3,15 @@ import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:media_kit/media_kit.dart' as media_kit; // ⭐ 加别名 +import 'package:media_kit/media_kit.dart' as media_kit; import 'package:path_provider/path_provider.dart'; import 'playback_service.dart'; import '../metadata/metadata_service.dart'; import '../database/song_database.dart'; import '../utils/artwork_helper.dart'; import '../repositories/playlist_repository.dart'; -import '../models/playlist.dart'; // ⭐ 你的 Playlist 模型 +import '../models/playlist.dart'; +import 'webdav_service.dart'; enum PlayMode { sequential, @@ -116,7 +117,6 @@ class AudioService extends ChangeNotifier { } notifyListeners(); - // ⭐ 如果有当前歌单,同步更新歌单的播放模式 if (_currentPlaylistId != null) { _playlistRepo.updatePlaylistPlayMode(_currentPlaylistId!, _playMode); } @@ -154,7 +154,6 @@ class AudioService extends ChangeNotifier { // ---- 播放指定歌曲 ---- Future playSong(Song song) async { - // 播放单曲时清除歌单上下文 _currentPlaylistId = null; if (_queue.isEmpty || _queue[_currentIndex].id != song.id) { setQueue([song], startIndex: 0); @@ -167,21 +166,17 @@ class AudioService extends ChangeNotifier { // 播放歌单 // ════════════════════════════════════════════════════════════ - /// 播放整个歌单 Future playPlaylist(String playlistId, {int startIndex = 0}) async { _currentPlaylistId = playlistId; - // 1. 获取歌单的播放模式 final playlist = await _playlistRepo.getPlaylist(playlistId); if (playlist != null) { _playMode = playlist.playMode; } - // 2. 获取歌单歌曲 ID 列表 final songIds = await _playlistRepo.getPlaylistSongIds(playlistId); if (songIds.isEmpty) return; - // 3. 将 song_id 转换为 Song 对象 final songs = []; for (final id in songIds) { final dbSong = await _db.getSongByPath(id); @@ -193,7 +188,6 @@ class AudioService extends ChangeNotifier { url: dbSong['remote_path'] as String, )); } else { - // 如果 songs 表中没有记录,用路径作为 fallback songs.add(Song( id: id, title: id.split('/').last.replaceAll(RegExp(r'\.[^.]*$'), ''), @@ -208,19 +202,17 @@ class AudioService extends ChangeNotifier { } } - /// 将当前播放队列保存为歌单 Future saveQueueAsPlaylist(String name) async { - // ⭐ 等待 createPlaylist 返回 Playlist 对象 final playlist = await _playlistRepo.createPlaylist(name); for (int i = 0; i < _queue.length; i++) { final song = _queue[i]; await _playlistRepo.addSong(playlist.id, song.id); } - return playlist; // ⭐ 直接返回 Playlist 对象 + return playlist; } // ════════════════════════════════════════════════════════════ - // 播放核心 + // ⭐ 播放核心(修复:自动添加认证头) // ════════════════════════════════════════════════════════════ void _playCurrent() { @@ -242,17 +234,37 @@ class AudioService extends ChangeNotifier { return; } - PlaybackService().play(song.url!); + // ⭐ 关键修复:播放时自动添加认证头 + _playWithHeaders(song.url!); _syncPlayerStateDelayed(); - // ⭐ 立即推送基本信息(无 artwork) _onSongChanged?.call(song); - - // ⭐ 异步加载完整 metadata(含 artwork) _loadMetadataForCurrentSong(generation); } + /// ⭐ 播放带认证头的 URL + Future _playWithHeaders(String url) async { + final headers = await _getAuthHeadersForUrl(url); + await PlaybackService().play(url, headers: headers); + } + + /// ⭐ 获取 URL 对应的认证头(WebDAV 需要,本地文件不需要) + Future> _getAuthHeadersForUrl(String url) async { + if (url.startsWith('http://') || url.startsWith('https://')) { + try { + final headers = await WebDAVService.instance.getAuthHeaders(); + if (headers.isNotEmpty) { + debugPrint('🔑 [AudioService] 已添加认证头到播放请求'); + return headers; + } + } catch (e) { + debugPrint('⚠️ [AudioService] 获取认证头失败: $e'); + } + } + return {}; + } + String _generateSongKey(String url, int fileSize, int modifiedTime) { final raw = '$url|$fileSize|$modifiedTime'; return raw.hashCode.toString();