顺序播放跳曲与首次启动跳曲问题的metadata部分修复完成,测试已通过
This commit is contained in:
@@ -51,7 +51,7 @@ class AudioService extends ChangeNotifier {
|
||||
List<int> _shuffledIndices = [];
|
||||
int _shuffledIndex = -1;
|
||||
|
||||
// ---- 当前播放的歌单 ID(用于模式同步) ----
|
||||
// ---- 当前播放的歌单 ID ----
|
||||
String? _currentPlaylistId;
|
||||
|
||||
// ---- 高频进度 ----
|
||||
@@ -68,7 +68,6 @@ class AudioService extends ChangeNotifier {
|
||||
bool _hasStartedCurrentPlayback = false;
|
||||
bool _isUserSeeking = false;
|
||||
|
||||
// ⭐ 播放代数:每次切歌递增,用于校验异步任务是否过期
|
||||
int _playbackGeneration = 0;
|
||||
|
||||
void Function(Song)? _onSongChanged;
|
||||
@@ -105,7 +104,6 @@ class AudioService extends ChangeNotifier {
|
||||
_onSongChanged = callback;
|
||||
}
|
||||
|
||||
// ---- 切换播放模式(同步更新歌单) ----
|
||||
void togglePlayMode() {
|
||||
switch (_playMode) {
|
||||
case PlayMode.sequential:
|
||||
@@ -125,7 +123,10 @@ class AudioService extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 设置播放队列 ----
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 队列管理
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
Future<void> setQueue(List<Song> queue, {int startIndex = 0}) async {
|
||||
if (queue.isEmpty) {
|
||||
_clearQueue();
|
||||
@@ -160,7 +161,6 @@ class AudioService extends ChangeNotifier {
|
||||
stopPlay();
|
||||
}
|
||||
|
||||
// ---- 播放指定歌曲 ----
|
||||
Future<void> playSong(Song song) async {
|
||||
_currentPlaylistId = null;
|
||||
if (_queue.isEmpty || _queue[_currentIndex].id != song.id) {
|
||||
@@ -171,7 +171,7 @@ class AudioService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 播放歌单
|
||||
// 歌单
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
Future<void> playPlaylist(String playlistId, {int startIndex = 0}) async {
|
||||
@@ -220,7 +220,7 @@ class AudioService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// ⭐ 播放核心(完整生命周期控制)
|
||||
// ⭐ 播放核心
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
Future<void> _playCurrent() async {
|
||||
@@ -234,7 +234,6 @@ class AudioService extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
// ⭐ 进入切歌状态
|
||||
_isChangingTrack = true;
|
||||
_handlingCompletion = false;
|
||||
_hasStartedCurrentPlayback = false;
|
||||
@@ -253,15 +252,11 @@ class AudioService extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
// ⭐ 关键:等待播放器加载完成
|
||||
await _playWithHeaders(song.url!);
|
||||
|
||||
// ⭐ 等待播放真正开始
|
||||
await _waitForPlaybackStarted();
|
||||
|
||||
_syncPlayerStateDelayed();
|
||||
|
||||
// ⭐ 只有真正开始播放后才推送通知
|
||||
if (_hasStartedCurrentPlayback) {
|
||||
_onSongChanged?.call(song);
|
||||
_loadMetadataForCurrentSong(generation);
|
||||
@@ -271,18 +266,15 @@ class AudioService extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// ⭐ 等待播放器真正开始播放
|
||||
Future<void> _waitForPlaybackStarted() async {
|
||||
final player = PlaybackService().player;
|
||||
|
||||
// 如果已经在播放,直接标记
|
||||
if (player.state.playing) {
|
||||
_hasStartedCurrentPlayback = true;
|
||||
debugPrint('🎵 [AudioService] playback already started');
|
||||
return;
|
||||
}
|
||||
|
||||
// 等待 playing 变为 true,超时 3 秒
|
||||
try {
|
||||
await player.stream.playing
|
||||
.where((playing) => playing == true)
|
||||
@@ -292,18 +284,15 @@ class AudioService extends ChangeNotifier {
|
||||
debugPrint('🎵 [AudioService] playback started');
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ [AudioService] wait for playback timeout: $e');
|
||||
// 超时也标记为已开始,避免永久阻塞
|
||||
_hasStartedCurrentPlayback = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// ⭐ 播放带认证头的 URL
|
||||
Future<void> _playWithHeaders(String url) async {
|
||||
final headers = await _getAuthHeadersForUrl(url);
|
||||
await PlaybackService().play(url, headers: headers);
|
||||
}
|
||||
|
||||
/// ⭐ 获取 URL 对应的认证头(WebDAV 需要,本地文件不需要)
|
||||
Future<Map<String, String>> _getAuthHeadersForUrl(String url) async {
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
try {
|
||||
@@ -324,6 +313,10 @@ class AudioService extends ChangeNotifier {
|
||||
return raw.hashCode.toString();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// ⭐ Metadata 加载(四层校验)
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
Future<void> _loadMetadataForCurrentSong(int generation) async {
|
||||
if (_currentIndex < 0 || _currentIndex >= _queue.length) return;
|
||||
|
||||
@@ -343,41 +336,53 @@ class AudioService extends ChangeNotifier {
|
||||
fileId: song.id,
|
||||
);
|
||||
|
||||
// 第一层:generation 校验
|
||||
if (_playbackGeneration != generation) {
|
||||
debugPrint('⚠️ [AudioService] metadata stale (generation), ignoring');
|
||||
debugPrint('⚠️ [AudioService] metadata stale (generation)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 第二层:索引校验
|
||||
if (_currentIndex != currentIndex) {
|
||||
debugPrint('⚠️ [AudioService] metadata stale (index changed)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 第三层:歌曲 ID 校验
|
||||
if (_currentIndex >= _queue.length ||
|
||||
_queue[_currentIndex].id != songId) {
|
||||
debugPrint('⚠️ [AudioService] metadata stale (song changed)');
|
||||
return;
|
||||
}
|
||||
|
||||
// ⭐ 第四层:_currentSong 校验(防止状态不同步)
|
||||
if (_currentSong == null || _currentSong!.id != songId) {
|
||||
debugPrint('⚠️ [AudioService] metadata stale: current song mismatch');
|
||||
return;
|
||||
}
|
||||
|
||||
if (metadata.isNotEmpty) {
|
||||
final updatedSong = Song(
|
||||
id: song.id,
|
||||
title: metadata.title.isNotEmpty ? metadata.title : song.title,
|
||||
artist: metadata.artist.isNotEmpty ? metadata.artist : song.artist,
|
||||
url: song.url,
|
||||
artwork: metadata.artwork,
|
||||
);
|
||||
_queue[_currentIndex] = updatedSong;
|
||||
_currentSong = updatedSong;
|
||||
notifyListeners();
|
||||
|
||||
_onSongChanged?.call(updatedSong);
|
||||
debugPrint(
|
||||
'⚠️ [AudioService] metadata stale (index changed), ignoring');
|
||||
return;
|
||||
'✅ [AudioService] metadata updated: ${updatedSong.title} - ${updatedSong.artist} (gen $generation)');
|
||||
}
|
||||
if (currentIndex >= _queue.length || _queue[currentIndex].id != songId) {
|
||||
debugPrint('⚠️ [AudioService] metadata stale (song changed), ignoring');
|
||||
return;
|
||||
}
|
||||
|
||||
final updatedSong = Song(
|
||||
id: song.id,
|
||||
title: metadata.title.isNotEmpty ? metadata.title : song.title,
|
||||
artist: metadata.artist.isNotEmpty ? metadata.artist : song.artist,
|
||||
url: song.url,
|
||||
artwork: metadata.artwork,
|
||||
);
|
||||
_queue[_currentIndex] = updatedSong;
|
||||
_currentSong = updatedSong;
|
||||
notifyListeners();
|
||||
|
||||
_onSongChanged?.call(updatedSong);
|
||||
debugPrint(
|
||||
'✅ [AudioService] metadata updated: ${updatedSong.title} - ${updatedSong.artist} (generation $generation)');
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ [AudioService] metadata load failed: $e, using fallback');
|
||||
debugPrint('⚠️ [AudioService] metadata load failed: $e');
|
||||
_onSongChanged?.call(song);
|
||||
}
|
||||
}
|
||||
|
||||
/// 清除当前歌曲的缓存
|
||||
Future<void> clearCurrentSongCache() async {
|
||||
if (_currentSong == null) return;
|
||||
final song = _currentSong!;
|
||||
@@ -433,10 +438,19 @@ class AudioService extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 下一首 ----
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// ⭐ 上一首 / 下一首(带防并发)
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
||||
Future<void> next() async {
|
||||
if (_queue.isEmpty) return;
|
||||
|
||||
// ⭐ 如果正在切换歌曲,忽略本次请求
|
||||
if (_isChangingTrack) {
|
||||
debugPrint('⚠️ [AudioService] next() ignored: track switch in progress');
|
||||
return;
|
||||
}
|
||||
|
||||
if (_playMode == PlayMode.shuffle) {
|
||||
if (_shuffledIndices.isEmpty) return;
|
||||
final nextIdx = (_shuffledIndex + 1) % _shuffledIndices.length;
|
||||
@@ -451,10 +465,15 @@ class AudioService extends ChangeNotifier {
|
||||
await _playCurrent();
|
||||
}
|
||||
|
||||
// ---- 上一首 ----
|
||||
Future<void> previous() async {
|
||||
if (_queue.isEmpty) return;
|
||||
|
||||
if (_isChangingTrack) {
|
||||
debugPrint(
|
||||
'⚠️ [AudioService] previous() ignored: track switch in progress');
|
||||
return;
|
||||
}
|
||||
|
||||
if (_playMode == PlayMode.shuffle) {
|
||||
if (_shuffledIndices.isEmpty) return;
|
||||
final prevIdx = (_shuffledIndex - 1) % _shuffledIndices.length;
|
||||
@@ -592,26 +611,22 @@ class AudioService extends ChangeNotifier {
|
||||
// ⭐ 完整生命周期校验的 completed 监听
|
||||
_subscriptions.add(
|
||||
player.stream.completed.listen((_) {
|
||||
// 第一层:切歌期间屏蔽
|
||||
if (_isChangingTrack) {
|
||||
debugPrint('⚠️ [AudioService] completed ignored during track switch');
|
||||
return;
|
||||
}
|
||||
|
||||
// 第二层:从未真正开始播放,忽略
|
||||
if (!_hasStartedCurrentPlayback) {
|
||||
debugPrint(
|
||||
'⚠️ [AudioService] completed ignored: playback never started');
|
||||
return;
|
||||
}
|
||||
|
||||
// 第三层:防重入
|
||||
if (_handlingCompletion) {
|
||||
debugPrint('⚠️ [service] completed ignored: already handling');
|
||||
return;
|
||||
}
|
||||
|
||||
// 第四层:额外校验 - 当前歌曲必须有有效的 duration
|
||||
final pos = player.state.position;
|
||||
final dur = player.state.duration;
|
||||
if (dur.inMilliseconds <= 0) {
|
||||
@@ -620,10 +635,9 @@ class AudioService extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
// 第五层:位置必须接近歌曲末尾(允许 500ms 误差)
|
||||
if (pos.inMilliseconds < dur.inMilliseconds - 500) {
|
||||
debugPrint(
|
||||
'⚠️ [AudioService] completed ignored: position=$pos, duration=$dur, not at end');
|
||||
'⚠️ [AudioService] completed ignored: position=$pos, duration=$dur');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -643,16 +657,13 @@ class AudioService extends ChangeNotifier {
|
||||
_subscriptions.clear();
|
||||
}
|
||||
|
||||
// ---- 播放完成处理 ----
|
||||
void _onPlaybackCompleted() {
|
||||
// 队列状态检查
|
||||
if (_queue.isEmpty || _currentIndex < 0 || _currentIndex >= _queue.length) {
|
||||
debugPrint('⚠️ [service] completed ignored: invalid queue');
|
||||
_handlingCompletion = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是最后一首且不是单曲循环模式,不触发 next
|
||||
if (_currentIndex + 1 >= _queue.length && _playMode != PlayMode.repeatOne) {
|
||||
debugPrint('⚠️ [service] completed ignored: last song in queue');
|
||||
_handlingCompletion = false;
|
||||
|
||||
Reference in New Issue
Block a user