From 94a4906671f027158cc2a884dc9a70e623ae8154 Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Fri, 21 Aug 2026 09:04:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8D=A2=E4=B8=8A=E5=85=A8=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E4=B8=AD=E5=BF=83=E6=8E=A7=E5=88=B6=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=99=9A=E4=B8=8A=E6=8C=893commit=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E8=BF=9B=E8=A1=8C=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/services/audio_player_handler.dart | 161 ++++++++++++++----------- 1 file changed, 93 insertions(+), 68 deletions(-) diff --git a/lib/services/audio_player_handler.dart b/lib/services/audio_player_handler.dart index b1dfce4..c4c85d0 100644 --- a/lib/services/audio_player_handler.dart +++ b/lib/services/audio_player_handler.dart @@ -7,36 +7,65 @@ class AudioPlayerHandler extends BaseAudioHandler { final PlaybackService _playback = PlaybackService(); late final local_audio.AudioService _localAudio; + // ⭐ 切歌锁,防止重复命令 + bool _switchingTrack = false; + AudioPlayerHandler() { _localAudio = local_audio.AudioService(); - // 监听播放状态 + // ⭐ 唯一状态来源:media_kit 的 playing 流 _playback.player.stream.playing.listen((playing) { + debugPrint('🎵 [stream] playing changed: $playing'); _updatePlaybackState(playing: playing); - _syncMediaItem(); }); - // 监听播放完成 → 自动下一首 + // ⭐ 播放完成 → 触发下一首(不直接广播状态) _playback.player.stream.completed.listen((_) { - debugPrint('🎵 [handler] playback completed, auto next'); - _localAudio.next(); - _updatePlaybackState(); - _syncMediaItem(); + debugPrint('🎵 [stream] completed, auto next'); + _localAudio.next(); // 触发切歌,playing 流会自然变化 + // 不调用 _updatePlaybackState() }); - // 监听时长变化 → 更新 MediaItem + // ⭐ 歌曲变化 → 更新媒体信息(独立于播放状态) + // 这里用 duration 变化作为“歌曲已切换”的信号 _playback.player.stream.duration.listen((duration) { - final current = mediaItem.value; - if (current != null && current.duration != duration) { - mediaItem.add(current.copyWith(duration: duration)); + if (duration.inMilliseconds > 0) { + _syncMediaItem(); } }); } + // ============================================================ + // 状态同步链(只能由 playing 流触发) + // ============================================================ + void _updatePlaybackState({bool? playing}) { + final isPlaying = playing ?? _playback.player.state.playing; + debugPrint('🎵 _updatePlaybackState: isPlaying=$isPlaying'); + + final controls = [ + MediaControl.skipToPrevious, + if (isPlaying) MediaControl.pause else MediaControl.play, + MediaControl.skipToNext, + ]; + + debugPrint('🎵 controls: ${controls.map((c) => c.action).join(', ')}'); + + playbackState.add(PlaybackState( + controls: controls, + processingState: AudioProcessingState.ready, + playing: isPlaying, + androidCompactActionIndices: const [0, 1, 2], + updateTime: DateTime.now(), + )); + } + + // ============================================================ + // 媒体信息同步链(独立于播放状态) + // ============================================================ void _syncMediaItem() { final song = _localAudio.currentSong; if (song != null) { - debugPrint('🎵 [handler] syncing media item: ${song.title}'); + debugPrint('🎵 [sync] media item: ${song.title}'); mediaItem.add(MediaItem( id: song.id, title: song.title, @@ -60,105 +89,101 @@ class AudioPlayerHandler extends BaseAudioHandler { _updatePlaybackState(); } - void _updatePlaybackState({bool? playing}) { - final isPlaying = playing ?? _playback.player.state.playing; - debugPrint('🎵 _updatePlaybackState: isPlaying=$isPlaying'); - - final controls = [ - MediaControl.skipToPrevious, - if (isPlaying) MediaControl.pause else MediaControl.play, - MediaControl.skipToNext, - ]; - - debugPrint('🎵 controls: ${controls.map((c) => c.action).join(', ')}'); - - playbackState.add(PlaybackState( - controls: controls, - processingState: AudioProcessingState.ready, - playing: isPlaying, - androidCompactActionIndices: const [0, 1, 2], - updateTime: DateTime.now(), - )); - } - + // ============================================================ + // 控制命令(只发命令,不宣布状态) + // ============================================================ @override Future play() async { debugPrint('🎵 [audio_service] play() called'); - _playback.resume(); - _updatePlaybackState(playing: true); - _syncMediaItem(); + await _playback.resume(); + // ⭐ 不调用 _updatePlaybackState() + // 等待 playing 流触发 } @override Future pause() async { debugPrint('🎵 [audio_service] pause() called'); - _playback.pause(); - _updatePlaybackState(playing: false); + await _playback.pause(); + // ⭐ 不调用 _updatePlaybackState() } @override Future stop() async { debugPrint('🎵 [audio_service] stop() called'); - _playback.stop(); - _updatePlaybackState(playing: false); + await _playback.stop(); + // ⭐ 不调用 _updatePlaybackState() } @override Future seek(Duration position) async { debugPrint('🎵 [audio_service] seek() called: $position'); - _playback.seek(position); + await _playback.seek(position); } @override Future skipToNext() async { - debugPrint('🎵 [audio_service] skipToNext() called'); - _localAudio.next(); - _updatePlaybackState(); - _syncMediaItem(); + if (_switchingTrack) { + debugPrint('🎵 skipToNext ignored: already switching'); + return; + } + _switchingTrack = true; + try { + debugPrint('🎵 [audio_service] skipToNext() called'); + await _localAudio.next(); + // ⭐ 不调用 _updatePlaybackState() + // playing 流和 duration 流会自然触发状态更新 + } finally { + // ⭐ 用真实状态释放锁,不用时间 + await Future.delayed(const Duration(milliseconds: 50)); + _switchingTrack = false; + } } @override Future skipToPrevious() async { - debugPrint('🎵 [audio_service] skipToPrevious() called'); - _localAudio.previous(); - _updatePlaybackState(); - _syncMediaItem(); + if (_switchingTrack) { + debugPrint('🎵 skipToPrevious ignored: already switching'); + return; + } + _switchingTrack = true; + try { + debugPrint('🎵 [audio_service] skipToPrevious() called'); + await _localAudio.previous(); + } finally { + await Future.delayed(const Duration(milliseconds: 50)); + _switchingTrack = false; + } } - // ⭐ 核心修复:正确区分 play 和 pause + // ============================================================ + // click():薄路由层,不操作播放器 + // ============================================================ @override Future click([MediaButton button = MediaButton.media]) async { debugPrint('🎵 [audio_service] click(): ${button.name} (index: ${button.index})'); + // ⭐ 用 button.name 判断,不用 index final name = button.name.toLowerCase(); - // ⭐ 优先处理 pause(蓝牙 PAUSE 会进入这里) + // 路由到标准方法 + if (name.contains('next') || name == 'next') { + await skipToNext(); + return; + } + if (name.contains('previous') || name == 'previous') { + await skipToPrevious(); + return; + } if (name.contains('pause')) { - debugPrint('🎵 → pause()'); await pause(); return; } - - // 处理 play(包括 media 类型的按钮) if (name.contains('play') || name.contains('media')) { - debugPrint('🎵 → play()'); await play(); return; } - if (name.contains('next') || name.contains('skip_next')) { - debugPrint('🎵 → skipToNext()'); - await skipToNext(); - return; - } - - if (name.contains('previous') || name.contains('skip_previous')) { - debugPrint('🎵 → skipToPrevious()'); - await skipToPrevious(); - return; - } - - // fallback:根据当前播放状态 toggle + // fallback: 根据当前播放状态 toggle debugPrint('🎵 → fallback toggle'); if (_playback.player.state.playing) { await pause();