import 'package:flutter/material.dart'; import 'package:audio_service/audio_service.dart'; import 'playback_service.dart'; import 'audio_service.dart' as local_audio; class AudioPlayerHandler extends BaseAudioHandler { final PlaybackService _playback = PlaybackService(); late final local_audio.AudioService _localAudio; AudioPlayerHandler() { _localAudio = local_audio.AudioService(); _playback.player.stream.playing.listen((playing) { _updatePlaybackState(playing: playing); _syncMediaItem(); }); _playback.player.stream.completed.listen((_) { debugPrint('🎵 [handler] playback completed, auto next'); _localAudio.next(); _updatePlaybackState(); _syncMediaItem(); }); _playback.player.stream.duration.listen((duration) { final current = mediaItem.value; if (current != null && current.duration != duration) { mediaItem.add(current.copyWith(duration: duration)); } }); } void _syncMediaItem() { final song = _localAudio.currentSong; debugPrint( '🎵 [handler] _syncMediaItem: currentSong = ${song?.title ?? "null"}'); if (song != null) { mediaItem.add(MediaItem( id: song.id, title: song.title, artist: song.artist, duration: _playback.player.state.duration, )); } } void updateNotification({ required String id, required String title, required String artist, }) { mediaItem.add(MediaItem( id: id, title: title, artist: artist, duration: _playback.player.state.duration, )); _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(); } @override Future pause() async { debugPrint('🎵 [audio_service] pause() called'); _playback.pause(); _updatePlaybackState(playing: false); } @override Future stop() async { debugPrint('🎵 [audio_service] stop() called'); _playback.stop(); _updatePlaybackState(playing: false); } @override Future seek(Duration position) async { debugPrint('🎵 [audio_service] seek() called: $position'); _playback.seek(position); } @override Future skipToNext() async { debugPrint('🎵 [audio_service] skipToNext() called'); _localAudio.next(); _updatePlaybackState(); _syncMediaItem(); } @override Future skipToPrevious() async { debugPrint('🎵 [audio_service] skipToPrevious() called'); _localAudio.previous(); _updatePlaybackState(); _syncMediaItem(); } @override Future click([MediaButton button = MediaButton.media]) async { debugPrint( '🎵 [audio_service] click(): ${button.name} (index: ${button.index})'); final name = button.name.toLowerCase(); if (name.contains('play') || name.contains('media')) { debugPrint('🎵 → play() via click'); await play(); return; } if (name.contains('pause')) { debugPrint('🎵 → pause() via click'); await pause(); return; } if (name.contains('next') || name.contains('skip_next')) { debugPrint('🎵 → skipToNext() via click'); await skipToNext(); return; } if (name.contains('previous') || name.contains('skip_previous')) { debugPrint('🎵 → skipToPrevious() via click'); await skipToPrevious(); return; } debugPrint('🎵 → fallback toggle'); if (_playback.player.state.playing) { await pause(); } else { await play(); } } }