import 'package:audio_service/audio_service.dart'; import 'package:just_audio/just_audio.dart'; import '../models/track.dart'; class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { MinstrelAudioHandler() { _player.playbackEventStream.listen(_broadcastState); } final AudioPlayer _player = AudioPlayer(); String _baseUrl = ''; String? _token; void configure({required String baseUrl, required String? token}) { _baseUrl = baseUrl; _token = token; } Future setQueueFromTracks(List tracks, {int initialIndex = 0}) async { final items = tracks.map(_toMediaItem).toList(); queue.add(items); if (items.isNotEmpty) { mediaItem.add(items[initialIndex.clamp(0, items.length - 1)]); } final headers = _token == null ? null : {'Authorization': 'Bearer $_token'}; final sources = tracks.map((t) { final url = t.streamUrl.isNotEmpty ? (Uri.tryParse(t.streamUrl)?.hasScheme == true ? t.streamUrl : '$_baseUrl${t.streamUrl}') : '$_baseUrl/api/tracks/${t.id}/stream'; return AudioSource.uri(Uri.parse(url), headers: headers); }).toList(); await _player.setAudioSource( ConcatenatingAudioSource(children: sources), initialIndex: initialIndex, ); } MediaItem _toMediaItem(TrackRef t) => MediaItem( id: t.id, title: t.title, artist: t.artistName, album: t.albumTitle, duration: Duration(seconds: t.durationSec), ); @override Future play() => _player.play(); @override Future pause() => _player.pause(); @override Future seek(Duration p) => _player.seek(p); @override Future skipToNext() => _player.seekToNext(); @override Future skipToPrevious() => _player.seekToPrevious(); void _broadcastState(PlaybackEvent event) { final playing = _player.playing; playbackState.add(PlaybackState( controls: [ MediaControl.skipToPrevious, if (playing) MediaControl.pause else MediaControl.play, MediaControl.skipToNext, ], systemActions: const {MediaAction.seek}, processingState: switch (_player.processingState) { ProcessingState.idle => AudioProcessingState.idle, ProcessingState.loading => AudioProcessingState.loading, ProcessingState.buffering => AudioProcessingState.buffering, ProcessingState.ready => AudioProcessingState.ready, ProcessingState.completed => AudioProcessingState.completed, }, playing: playing, updatePosition: _player.position, bufferedPosition: _player.bufferedPosition, speed: _player.speed, queueIndex: event.currentIndex, )); } }