From bb74cd13512de36c197fe5333f83eee885caf874 Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Thu, 27 Aug 2026 23:27:05 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E6=92=AD=E6=94=BE=E8=B7=B3?= =?UTF-8?q?=E6=9B=B2=E4=B8=8E=E9=A6=96=E6=AC=A1=E5=90=AF=E5=8A=A8=E8=B7=B3?= =?UTF-8?q?=E6=9B=B2=E9=97=AE=E9=A2=98=E7=9A=84metadata=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=8C=E6=88=90=EF=BC=8C=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=B7=B2=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/services/audio_service.dart | 117 +++++++++++++++++--------------- 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index 3d21d1b..2960fe0 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -51,7 +51,7 @@ class AudioService extends ChangeNotifier { List _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 setQueue(List queue, {int startIndex = 0}) async { if (queue.isEmpty) { _clearQueue(); @@ -160,7 +161,6 @@ class AudioService extends ChangeNotifier { stopPlay(); } - // ---- 播放指定歌曲 ---- Future playSong(Song song) async { _currentPlaylistId = null; if (_queue.isEmpty || _queue[_currentIndex].id != song.id) { @@ -171,7 +171,7 @@ class AudioService extends ChangeNotifier { } // ════════════════════════════════════════════════════════════ - // 播放歌单 + // 歌单 // ════════════════════════════════════════════════════════════ Future playPlaylist(String playlistId, {int startIndex = 0}) async { @@ -220,7 +220,7 @@ class AudioService extends ChangeNotifier { } // ════════════════════════════════════════════════════════════ - // ⭐ 播放核心(完整生命周期控制) + // ⭐ 播放核心 // ════════════════════════════════════════════════════════════ Future _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 _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 _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 { @@ -324,6 +313,10 @@ class AudioService extends ChangeNotifier { return raw.hashCode.toString(); } + // ════════════════════════════════════════════════════════════ + // ⭐ Metadata 加载(四层校验) + // ════════════════════════════════════════════════════════════ + Future _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 clearCurrentSongCache() async { if (_currentSong == null) return; final song = _currentSong!; @@ -433,10 +438,19 @@ class AudioService extends ChangeNotifier { } } - // ---- 下一首 ---- + // ════════════════════════════════════════════════════════════ + // ⭐ 上一首 / 下一首(带防并发) + // ════════════════════════════════════════════════════════════ + Future 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 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;