diff --git a/lib/audio/playback_state_manager.dart b/lib/audio/playback_state_manager.dart new file mode 100644 index 0000000..5c3486a --- /dev/null +++ b/lib/audio/playback_state_manager.dart @@ -0,0 +1,72 @@ +// lib/audio/playback_state_manager.dart +import 'package:audio_service/audio_service.dart' as audio_service; + +class PlaybackStateManager { + static final PlaybackStateManager _instance = + PlaybackStateManager._internal(); + factory PlaybackStateManager() => _instance; + PlaybackStateManager._internal(); + + bool _playing = false; + bool get playing => _playing; + + Duration _position = Duration.zero; + Duration get position => _position; + + Duration _duration = Duration.zero; + Duration get duration => _duration; + + // ⭐ 核心改动:使用 playPause 替代 play/pause 分离 + audio_service.PlaybackState get playbackState { + // ⭐ 固定使用 playPause,不再动态切换 + final playControl = audio_service.MediaControl( + androidIcon: _playing ? 'drawable/ic_pause' : 'drawable/ic_play', + label: _playing ? '暂停' : '播放', + action: audio_service.MediaAction.playPause, // 固定 + ); + + final controls = [ + audio_service.MediaControl( + androidIcon: 'drawable/ic_previous', + label: '上一曲', + action: audio_service.MediaAction.skipToPrevious, + ), + playControl, + audio_service.MediaControl( + androidIcon: 'drawable/ic_next', + label: '下一曲', + action: audio_service.MediaAction.skipToNext, + ), + ]; + + return audio_service.PlaybackState( + controls: controls, + processingState: audio_service.AudioProcessingState.ready, + playing: _playing, // ⭐ 这里才是真正的播放状态,通知栏会根据它显示正确的图标 + androidCompactActionIndices: const [0, 1, 2], + updateTime: DateTime.now(), + //extras: {'_refresh': DateTime.now().millisecondsSinceEpoch}, + ); + } + + // ---- 状态更新方法保持不变 ---- + void updatePlaying(bool playing) { + if (_playing != playing) { + _playing = playing; + } + } + + void updatePosition(Duration position) { + _position = position; + } + + void updateDuration(Duration duration) { + _duration = duration; + } + + void reset() { + _playing = false; + _position = Duration.zero; + _duration = Duration.zero; + } +} diff --git a/lib/audio/player_controller.dart b/lib/audio/player_controller.dart new file mode 100644 index 0000000..e23ffde --- /dev/null +++ b/lib/audio/player_controller.dart @@ -0,0 +1,33 @@ +// lib/audio/player_controller.dart +import 'package:media_kit/media_kit.dart'; +import '../services/playback_service.dart'; // ⭐ 修正路径 + +class PlayerController { + static final PlayerController _instance = PlayerController._internal(); + factory PlayerController() => _instance; + PlayerController._internal(); + + final PlaybackService _playback = PlaybackService(); + + // ---- 状态流(只读) ---- + Stream get playingStream => _playback.player.stream.playing; + Stream get positionStream => _playback.player.stream.position; + Stream get durationStream => _playback.player.stream.duration; + Stream get completedStream => _playback.player.stream.completed; + + // ---- 当前状态快照 ---- + bool get isPlaying => _playback.player.state.playing; + Duration get position => _playback.player.state.position; + Duration get duration => _playback.player.state.duration; + + // ---- 控制命令 ---- + Future play() => _playback.resume(); + Future pause() => _playback.pause(); + Future stop() => _playback.stop(); + Future seek(Duration position) => _playback.seek(position); + + // ---- 加载歌曲 ---- + void load(String url, {Map? headers}) { + _playback.play(url, headers: headers); + } +} diff --git a/lib/main.dart b/lib/main.dart index 9306319..da1abac 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -287,18 +287,21 @@ class _QTPlayerAppState extends State { '🎨 [QTPlayerApp] build #$_buildCount ${stopwatch.elapsedMilliseconds}ms'); // 监听歌曲变化,更新通知栏 + // main.dart 中 build 方法里的这部分 final audioService = context.watch(); final handler = context.read(); final song = audioService.currentSong; WidgetsBinding.instance.addPostFrameCallback((_) { if (song != null) { + // ⭐ song 不为空时才调用 handler.updateNotification( id: song.id, title: song.title, artist: song.artist, ); } else { + // 清空通知 handler.mediaItem.add(null); } }); diff --git a/lib/services/audio_player_handler.dart b/lib/services/audio_player_handler.dart index c4c85d0..0620bc5 100644 --- a/lib/services/audio_player_handler.dart +++ b/lib/services/audio_player_handler.dart @@ -1,78 +1,53 @@ -import 'package:flutter/material.dart'; -import 'package:audio_service/audio_service.dart'; -import 'playback_service.dart'; -import 'audio_service.dart' as local_audio; +// lib/services/audio_player_handler.dart +import 'package:flutter/foundation.dart'; +import 'package:audio_service/audio_service.dart' as audio_service; +import '../audio/player_controller.dart'; +import '../audio/playback_state_manager.dart'; +import '../services/audio_service.dart'; // 你的业务 AudioService -class AudioPlayerHandler extends BaseAudioHandler { - final PlaybackService _playback = PlaybackService(); - late final local_audio.AudioService _localAudio; - - // ⭐ 切歌锁,防止重复命令 - bool _switchingTrack = false; +class AudioPlayerHandler extends audio_service.BaseAudioHandler { + final PlayerController _player = PlayerController(); + final PlaybackStateManager _state = PlaybackStateManager(); AudioPlayerHandler() { - _localAudio = local_audio.AudioService(); - - // ⭐ 唯一状态来源:media_kit 的 playing 流 - _playback.player.stream.playing.listen((playing) { - debugPrint('🎵 [stream] playing changed: $playing'); - _updatePlaybackState(playing: playing); + _player.playingStream.listen((playing) { + _state.updatePlaying(playing); + _publishState(); }); - // ⭐ 播放完成 → 触发下一首(不直接广播状态) - _playback.player.stream.completed.listen((_) { - debugPrint('🎵 [stream] completed, auto next'); - _localAudio.next(); // 触发切歌,playing 流会自然变化 - // 不调用 _updatePlaybackState() - }); - - // ⭐ 歌曲变化 → 更新媒体信息(独立于播放状态) - // 这里用 duration 变化作为“歌曲已切换”的信号 - _playback.player.stream.duration.listen((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('🎵 [sync] media item: ${song.title}'); - mediaItem.add(MediaItem( - id: song.id, - title: song.title, - artist: song.artist, - duration: _playback.player.state.duration, + _player.positionStream.listen((position) { + _state.updatePosition(position); + final current = playbackState.value; + playbackState.add(audio_service.PlaybackState( + controls: current.controls, + processingState: current.processingState, + playing: current.playing, + androidCompactActionIndices: current.androidCompactActionIndices, + updateTime: DateTime.now(), )); - } + }); + + _player.durationStream.listen((duration) { + _state.updateDuration(duration); + _publishState(); + }); + + _player.completedStream.listen((_) { + debugPrint('⏭️ [handler] completed, auto next'); + AudioService().next(); + }); + } + + void _publishState() { + final state = _state.playbackState; + + debugPrint( + '📡 [publish] ' + 'playing=${state.playing}, ' + 'controls=${state.controls.map((e) => e.action).toList()}', + ); + + playbackState.add(state); } void updateNotification({ @@ -80,115 +55,77 @@ class AudioPlayerHandler extends BaseAudioHandler { required String title, required String artist, }) { - mediaItem.add(MediaItem( + debugPrint('📢 [handler] updateNotification: $title'); + mediaItem.add(audio_service.MediaItem( id: id, title: title, artist: artist, - duration: _playback.player.state.duration, + duration: _player.duration, )); - _updatePlaybackState(); } - // ============================================================ - // 控制命令(只发命令,不宣布状态) - // ============================================================ + // ---- 控制命令(系统回调) ---- @override Future play() async { - debugPrint('🎵 [audio_service] play() called'); - await _playback.resume(); - // ⭐ 不调用 _updatePlaybackState() - // 等待 playing 流触发 + debugPrint('▶️ [handler] play() called'); + await _player.play(); } @override Future pause() async { - debugPrint('🎵 [audio_service] pause() called'); - await _playback.pause(); - // ⭐ 不调用 _updatePlaybackState() + debugPrint('⏸️ [handler] pause() called'); + await _player.pause(); } @override Future stop() async { - debugPrint('🎵 [audio_service] stop() called'); - await _playback.stop(); - // ⭐ 不调用 _updatePlaybackState() + debugPrint('⏹️ [handler] stop() called'); + _player.stop(); } @override Future seek(Duration position) async { - debugPrint('🎵 [audio_service] seek() called: $position'); - await _playback.seek(position); + debugPrint('⏩ [handler] seek() called: $position'); + _player.seek(position); } @override Future skipToNext() async { - 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; - } + debugPrint('⏭️ [handler] skipToNext() called'); + AudioService().next(); } @override Future skipToPrevious() async { - 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; - } + debugPrint('⏮️ [handler] skipToPrevious() called'); + AudioService().previous(); } - // ============================================================ - // click():薄路由层,不操作播放器 - // ============================================================ + // ⭐ 统一点击处理(仅使用 MediaButton 存在的枚举) @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(); - - // 路由到标准方法 - if (name.contains('next') || name == 'next') { - await skipToNext(); - return; - } - if (name.contains('previous') || name == 'previous') { - await skipToPrevious(); - return; - } - if (name.contains('pause')) { - await pause(); - return; - } - if (name.contains('play') || name.contains('media')) { - await play(); - return; - } - - // fallback: 根据当前播放状态 toggle - debugPrint('🎵 → fallback toggle'); - if (_playback.player.state.playing) { - await pause(); - } else { - await play(); + Future click( + [audio_service.MediaButton button = + audio_service.MediaButton.media]) async { + debugPrint('🎯 [handler] click() called with button: ${button.name}'); + switch (button) { + case audio_service.MediaButton.next: + await skipToNext(); + break; + case audio_service.MediaButton.previous: + await skipToPrevious(); + break; + case audio_service.MediaButton.media: + // 播放/暂停切换 + if (_player.isPlaying) { + await pause(); + } else { + await play(); + } + break; + default: + // 其他按钮(fastForward, rewind等)忽略或fallback + debugPrint('⚠️ [handler] unhandled MediaButton: $button'); + break; } } -} \ No newline at end of file +} diff --git a/lib/services/playback_service.dart b/lib/services/playback_service.dart index 803c6cf..02674c3 100644 --- a/lib/services/playback_service.dart +++ b/lib/services/playback_service.dart @@ -44,27 +44,27 @@ class PlaybackService { } // ---------- 控制 ---------- - void pause() { + Future pause() async { if (_initialized && _player != null) { - _player!.pause(); + await _player!.pause(); } } - void resume() { + Future resume() async { if (_initialized && _player != null) { - _player!.play(); + await _player!.play(); } } - void stop() { + Future stop() async { if (_initialized && _player != null) { - _player!.stop(); + await _player!.stop(); } } - void seek(Duration position) { + Future seek(Duration position) async { if (_initialized && _player != null) { - _player!.seek(position); + await _player!.seek(position); } }