换上全新的通知中心控制逻辑,晚上按3commit的方式进行测试
This commit is contained in:
@@ -7,36 +7,65 @@ class AudioPlayerHandler extends BaseAudioHandler {
|
|||||||
final PlaybackService _playback = PlaybackService();
|
final PlaybackService _playback = PlaybackService();
|
||||||
late final local_audio.AudioService _localAudio;
|
late final local_audio.AudioService _localAudio;
|
||||||
|
|
||||||
|
// ⭐ 切歌锁,防止重复命令
|
||||||
|
bool _switchingTrack = false;
|
||||||
|
|
||||||
AudioPlayerHandler() {
|
AudioPlayerHandler() {
|
||||||
_localAudio = local_audio.AudioService();
|
_localAudio = local_audio.AudioService();
|
||||||
|
|
||||||
// 监听播放状态
|
// ⭐ 唯一状态来源:media_kit 的 playing 流
|
||||||
_playback.player.stream.playing.listen((playing) {
|
_playback.player.stream.playing.listen((playing) {
|
||||||
|
debugPrint('🎵 [stream] playing changed: $playing');
|
||||||
_updatePlaybackState(playing: playing);
|
_updatePlaybackState(playing: playing);
|
||||||
_syncMediaItem();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听播放完成 → 自动下一首
|
// ⭐ 播放完成 → 触发下一首(不直接广播状态)
|
||||||
_playback.player.stream.completed.listen((_) {
|
_playback.player.stream.completed.listen((_) {
|
||||||
debugPrint('🎵 [handler] playback completed, auto next');
|
debugPrint('🎵 [stream] completed, auto next');
|
||||||
_localAudio.next();
|
_localAudio.next(); // 触发切歌,playing 流会自然变化
|
||||||
_updatePlaybackState();
|
// 不调用 _updatePlaybackState()
|
||||||
_syncMediaItem();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听时长变化 → 更新 MediaItem
|
// ⭐ 歌曲变化 → 更新媒体信息(独立于播放状态)
|
||||||
|
// 这里用 duration 变化作为“歌曲已切换”的信号
|
||||||
_playback.player.stream.duration.listen((duration) {
|
_playback.player.stream.duration.listen((duration) {
|
||||||
final current = mediaItem.value;
|
if (duration.inMilliseconds > 0) {
|
||||||
if (current != null && current.duration != duration) {
|
_syncMediaItem();
|
||||||
mediaItem.add(current.copyWith(duration: duration));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 状态同步链(只能由 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() {
|
void _syncMediaItem() {
|
||||||
final song = _localAudio.currentSong;
|
final song = _localAudio.currentSong;
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
debugPrint('🎵 [handler] syncing media item: ${song.title}');
|
debugPrint('🎵 [sync] media item: ${song.title}');
|
||||||
mediaItem.add(MediaItem(
|
mediaItem.add(MediaItem(
|
||||||
id: song.id,
|
id: song.id,
|
||||||
title: song.title,
|
title: song.title,
|
||||||
@@ -60,105 +89,101 @@ class AudioPlayerHandler extends BaseAudioHandler {
|
|||||||
_updatePlaybackState();
|
_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
|
@override
|
||||||
Future<void> play() async {
|
Future<void> play() async {
|
||||||
debugPrint('🎵 [audio_service] play() called');
|
debugPrint('🎵 [audio_service] play() called');
|
||||||
_playback.resume();
|
await _playback.resume();
|
||||||
_updatePlaybackState(playing: true);
|
// ⭐ 不调用 _updatePlaybackState()
|
||||||
_syncMediaItem();
|
// 等待 playing 流触发
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> pause() async {
|
Future<void> pause() async {
|
||||||
debugPrint('🎵 [audio_service] pause() called');
|
debugPrint('🎵 [audio_service] pause() called');
|
||||||
_playback.pause();
|
await _playback.pause();
|
||||||
_updatePlaybackState(playing: false);
|
// ⭐ 不调用 _updatePlaybackState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> stop() async {
|
Future<void> stop() async {
|
||||||
debugPrint('🎵 [audio_service] stop() called');
|
debugPrint('🎵 [audio_service] stop() called');
|
||||||
_playback.stop();
|
await _playback.stop();
|
||||||
_updatePlaybackState(playing: false);
|
// ⭐ 不调用 _updatePlaybackState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> seek(Duration position) async {
|
Future<void> seek(Duration position) async {
|
||||||
debugPrint('🎵 [audio_service] seek() called: $position');
|
debugPrint('🎵 [audio_service] seek() called: $position');
|
||||||
_playback.seek(position);
|
await _playback.seek(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> skipToNext() async {
|
Future<void> skipToNext() async {
|
||||||
|
if (_switchingTrack) {
|
||||||
|
debugPrint('🎵 skipToNext ignored: already switching');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_switchingTrack = true;
|
||||||
|
try {
|
||||||
debugPrint('🎵 [audio_service] skipToNext() called');
|
debugPrint('🎵 [audio_service] skipToNext() called');
|
||||||
_localAudio.next();
|
await _localAudio.next();
|
||||||
_updatePlaybackState();
|
// ⭐ 不调用 _updatePlaybackState()
|
||||||
_syncMediaItem();
|
// playing 流和 duration 流会自然触发状态更新
|
||||||
|
} finally {
|
||||||
|
// ⭐ 用真实状态释放锁,不用时间
|
||||||
|
await Future.delayed(const Duration(milliseconds: 50));
|
||||||
|
_switchingTrack = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> skipToPrevious() async {
|
Future<void> skipToPrevious() async {
|
||||||
|
if (_switchingTrack) {
|
||||||
|
debugPrint('🎵 skipToPrevious ignored: already switching');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_switchingTrack = true;
|
||||||
|
try {
|
||||||
debugPrint('🎵 [audio_service] skipToPrevious() called');
|
debugPrint('🎵 [audio_service] skipToPrevious() called');
|
||||||
_localAudio.previous();
|
await _localAudio.previous();
|
||||||
_updatePlaybackState();
|
} finally {
|
||||||
_syncMediaItem();
|
await Future.delayed(const Duration(milliseconds: 50));
|
||||||
|
_switchingTrack = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⭐ 核心修复:正确区分 play 和 pause
|
// ============================================================
|
||||||
|
// click():薄路由层,不操作播放器
|
||||||
|
// ============================================================
|
||||||
@override
|
@override
|
||||||
Future<void> click([MediaButton button = MediaButton.media]) async {
|
Future<void> click([MediaButton button = MediaButton.media]) async {
|
||||||
debugPrint('🎵 [audio_service] click(): ${button.name} (index: ${button.index})');
|
debugPrint('🎵 [audio_service] click(): ${button.name} (index: ${button.index})');
|
||||||
|
|
||||||
|
// ⭐ 用 button.name 判断,不用 index
|
||||||
final name = button.name.toLowerCase();
|
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')) {
|
if (name.contains('pause')) {
|
||||||
debugPrint('🎵 → pause()');
|
|
||||||
await pause();
|
await pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理 play(包括 media 类型的按钮)
|
|
||||||
if (name.contains('play') || name.contains('media')) {
|
if (name.contains('play') || name.contains('media')) {
|
||||||
debugPrint('🎵 → play()');
|
|
||||||
await play();
|
await play();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.contains('next') || name.contains('skip_next')) {
|
// fallback: 根据当前播放状态 toggle
|
||||||
debugPrint('🎵 → skipToNext()');
|
|
||||||
await skipToNext();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.contains('previous') || name.contains('skip_previous')) {
|
|
||||||
debugPrint('🎵 → skipToPrevious()');
|
|
||||||
await skipToPrevious();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallback:根据当前播放状态 toggle
|
|
||||||
debugPrint('🎵 → fallback toggle');
|
debugPrint('🎵 → fallback toggle');
|
||||||
if (_playback.player.state.playing) {
|
if (_playback.player.state.playing) {
|
||||||
await pause();
|
await pause();
|
||||||
|
|||||||
Reference in New Issue
Block a user