import 'dart:async'; import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/loop_mode.dart'; class MobileAudioService extends BaseAudioHandler { AudioSession? session; final ProxyPlaylistNotifier playlistNotifier; ProxyPlaylist get playlist => playlistNotifier.state; MobileAudioService(this.playlistNotifier) { AudioSession.instance.then((s) { session = s; session?.configure(const AudioSessionConfiguration.music()); s.interruptionEventStream.listen((event) async { switch (event.type) { case AudioInterruptionType.duck: await audioPlayer.setVolume(event.begin ? 0.5 : 1.0); break; case AudioInterruptionType.pause: case AudioInterruptionType.unknown: await audioPlayer.pause(); break; } }); }); audioPlayer.playerStateStream.listen((state) async { playbackState.add(await _transformEvent()); }); audioPlayer.positionStream.listen((pos) async { playbackState.add(await _transformEvent()); }); audioPlayer.bufferedPositionStream.listen((pos) async { playbackState.add(await _transformEvent()); }); } void addItem(MediaItem item) { session?.setActive(true); mediaItem.add(item); } @override Future play() => audioPlayer.resume(); @override Future pause() => audioPlayer.pause(); @override Future seek(Duration position) => audioPlayer.seek(position); @override Future setShuffleMode(AudioServiceShuffleMode shuffleMode) async { await super.setShuffleMode(shuffleMode); audioPlayer.setShuffle(shuffleMode == AudioServiceShuffleMode.all); } @override Future setRepeatMode(AudioServiceRepeatMode repeatMode) async { super.setRepeatMode(repeatMode); audioPlayer.setLoopMode( PlaybackLoopMode.fromAudioServiceRepeatMode(repeatMode), ); } @override Future stop() async { await playlistNotifier.stop(); } @override Future skipToNext() async { await playlistNotifier.next(); await super.skipToNext(); } @override Future skipToPrevious() async { await playlistNotifier.previous(); await super.skipToPrevious(); } @override Future onTaskRemoved() async { await playlistNotifier.stop(); return super.onTaskRemoved(); } Future _transformEvent() async { final position = (await audioPlayer.position) ?? Duration.zero; return PlaybackState( controls: [ MediaControl.skipToPrevious, audioPlayer.isPlaying ? MediaControl.pause : MediaControl.play, MediaControl.skipToNext, MediaControl.stop, ], systemActions: { MediaAction.seek, }, androidCompactActionIndices: const [0, 1, 2], playing: audioPlayer.isPlaying, updatePosition: position, bufferedPosition: await audioPlayer.bufferedPosition ?? Duration.zero, shuffleMode: await audioPlayer.isShuffled == true ? AudioServiceShuffleMode.all : AudioServiceShuffleMode.none, repeatMode: (await audioPlayer.loopMode).toAudioServiceRepeatMode(), processingState: playlist.isFetching == true ? AudioProcessingState.loading : AudioProcessingState.ready, ); } }