顺序播放跳曲与首次启动跳曲问题的metadata部分修复完成,测试已通过

This commit is contained in:
2026-08-27 23:27:05 +08:00
parent 6cb1167a21
commit bb74cd1351
+64 -53
View File
@@ -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;