| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  | import 'dart:async'; | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  | import 'dart:convert'; | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  | import 'dart:isolate'; | 
					
						
							|  |  |  | import 'dart:ui'; | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import 'package:audio_service/audio_service.dart'; | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  | import 'package:musicus_player/musicus_player.dart'; | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  | import 'package:rxdart/rxdart.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  | import 'music_library.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  | const _portName = 'playbackService'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  | /// Entrypoint for the playback service.
 | 
					
						
							|  |  |  | void _playbackServiceEntrypoint() { | 
					
						
							|  |  |  |   AudioServiceBackground.run(() => _PlaybackService()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Player { | 
					
						
							|  |  |  |   /// Whether the player is active.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// This means, that there is at least one item in the queue and the playback
 | 
					
						
							|  |  |  |   /// service is ready to play.
 | 
					
						
							|  |  |  |   final active = BehaviorSubject.seeded(false); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |   /// The current playlist.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If the player is not active, this will be an empty list.
 | 
					
						
							|  |  |  |   final playlist = BehaviorSubject.seeded(<InternalTrack>[]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Index of the currently played (or paused) track within the playlist.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// This will be zero, if the player is not active!
 | 
					
						
							|  |  |  |   final currentTrack = BehaviorSubject.seeded(0); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   /// Whether we are currently playing or not.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// This will be false, if the player is not active.
 | 
					
						
							|  |  |  |   final playing = BehaviorSubject.seeded(false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Current playback position.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If the player is not active, this will default to zero.
 | 
					
						
							|  |  |  |   final position = BehaviorSubject.seeded(const Duration()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Duration of the current track.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If the player is not active, the duration will default to 1 s.
 | 
					
						
							|  |  |  |   final duration = BehaviorSubject.seeded(const Duration(seconds: 1)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Playback position normalized to the range from zero to one.
 | 
					
						
							|  |  |  |   final normalizedPosition = BehaviorSubject.seeded(0.0); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   StreamSubscription _playbackServiceStateSubscription; | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   /// Update [position] and [normalizedPosition] from position in milliseconds.
 | 
					
						
							|  |  |  |   void _updatePosition(int positionMs) { | 
					
						
							|  |  |  |     position.add(Duration(milliseconds: positionMs)); | 
					
						
							|  |  |  |     normalizedPosition.add(positionMs / duration.value.inMilliseconds); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Set everything to its default because the playback service was stopped.
 | 
					
						
							|  |  |  |   void _stop() { | 
					
						
							|  |  |  |     active.add(false); | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |     playlist.add([]); | 
					
						
							|  |  |  |     currentTrack.add(0); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     playing.add(false); | 
					
						
							|  |  |  |     position.add(const Duration()); | 
					
						
							|  |  |  |     duration.add(const Duration(seconds: 1)); | 
					
						
							|  |  |  |     normalizedPosition.add(0.0); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Start playback service.
 | 
					
						
							|  |  |  |   Future<void> start() async { | 
					
						
							|  |  |  |     if (!AudioService.running) { | 
					
						
							|  |  |  |       await AudioService.start( | 
					
						
							|  |  |  |         backgroundTaskEntrypoint: _playbackServiceEntrypoint, | 
					
						
							|  |  |  |         androidNotificationChannelName: 'Musicus playback', | 
					
						
							|  |  |  |         androidNotificationChannelDescription: | 
					
						
							|  |  |  |             'Keeps Musicus playing in the background', | 
					
						
							|  |  |  |         androidNotificationIcon: 'drawable/ic_notification', | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |       active.add(true); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Connect listeners and initialize streams.
 | 
					
						
							|  |  |  |   void setup() { | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     if (_playbackServiceStateSubscription == null) { | 
					
						
							|  |  |  |       // We will receive updated state information from the playback service,
 | 
					
						
							|  |  |  |       // which runs in its own isolate, through this port.
 | 
					
						
							|  |  |  |       final receivePort = ReceivePort(); | 
					
						
							|  |  |  |       _playbackServiceStateSubscription = receivePort.listen((msg) { | 
					
						
							|  |  |  |         // If state is null, the background audio service has stopped.
 | 
					
						
							|  |  |  |         if (msg == null) { | 
					
						
							|  |  |  |           _stop(); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           final state = msg as PlaybackServiceState; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // TODO: Consider checking, whether values have actually changed.
 | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |           playlist.add(state.playlist); | 
					
						
							|  |  |  |           currentTrack.add(state.currentTrack); | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |           playing.add(state.playing); | 
					
						
							|  |  |  |           position.add(Duration(milliseconds: state.positionMs)); | 
					
						
							|  |  |  |           duration.add(Duration(milliseconds: state.durationMs)); | 
					
						
							|  |  |  |           normalizedPosition.add(state.positionMs / state.durationMs); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |       IsolateNameServer.registerPortWithName(receivePort.sendPort, _portName); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     if (AudioService.running) { | 
					
						
							|  |  |  |       active.add(true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Instruct the background service to send its current state. This will
 | 
					
						
							|  |  |  |       // by handled in the listeners, that were already set in the constructor.
 | 
					
						
							|  |  |  |       AudioService.customAction('sendState'); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Toggle whether the player is playing or paused.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If the player is not active, this will do nothing.
 | 
					
						
							|  |  |  |   Future<void> playPause() async { | 
					
						
							|  |  |  |     if (active.value) { | 
					
						
							|  |  |  |       if (playing.value) { | 
					
						
							|  |  |  |         await AudioService.pause(); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         await AudioService.play(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |   /// Add a list of tracks to the players playlist.
 | 
					
						
							|  |  |  |   Future<void> addTracks(List<InternalTrack> tracks) async { | 
					
						
							|  |  |  |     if (!AudioService.running) { | 
					
						
							|  |  |  |       await start(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await AudioService.customAction('addTracks', jsonEncode(tracks)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   /// Seek to [pos], which is a value between (and including) zero and one.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If the player is not active or an invalid value is provided, this will do
 | 
					
						
							|  |  |  |   /// nothing.
 | 
					
						
							|  |  |  |   Future<void> seekTo(double pos) async { | 
					
						
							|  |  |  |     if (active.value && pos >= 0.0 && pos <= 1.0) { | 
					
						
							|  |  |  |       final durationMs = duration.value.inMilliseconds; | 
					
						
							|  |  |  |       await AudioService.seekTo((pos * durationMs).floor()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |   /// Play the previous track in the playlist.
 | 
					
						
							|  |  |  |   /// 
 | 
					
						
							|  |  |  |   /// If the player is not active or there is no previous track, this will do
 | 
					
						
							|  |  |  |   /// nothing.
 | 
					
						
							|  |  |  |   Future<void> skipToNext() async { | 
					
						
							|  |  |  |     if (AudioService.running) { | 
					
						
							|  |  |  |       await AudioService.skipToNext(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Skip to the next track in the playlist.
 | 
					
						
							|  |  |  |   /// 
 | 
					
						
							|  |  |  |   /// If the player is not active or there is no next track, this will do
 | 
					
						
							|  |  |  |   /// nothing. If more than five seconds of the current track have been played,
 | 
					
						
							|  |  |  |   /// this will go back to its beginning instead.
 | 
					
						
							|  |  |  |   Future<void> skipToPrevious() async { | 
					
						
							|  |  |  |     if (AudioService.running) { | 
					
						
							|  |  |  |       await AudioService.skipToPrevious(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Switch to the track with the index [index] in the playlist.
 | 
					
						
							|  |  |  |   Future<void> skipTo(int index) async { | 
					
						
							|  |  |  |     if (AudioService.running) { | 
					
						
							|  |  |  |       await AudioService.customAction('skipTo', index); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   /// Tidy up.
 | 
					
						
							|  |  |  |   void dispose() { | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     _playbackServiceStateSubscription.cancel(); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     active.close(); | 
					
						
							|  |  |  |     playing.close(); | 
					
						
							|  |  |  |     position.close(); | 
					
						
							|  |  |  |     duration.close(); | 
					
						
							|  |  |  |     normalizedPosition.close(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  | /// Bundle of the current state of the playback service.
 | 
					
						
							|  |  |  | class PlaybackServiceState { | 
					
						
							|  |  |  |   /// The current playlist.
 | 
					
						
							|  |  |  |   final List<InternalTrack> playlist; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The index of the currentTrack.
 | 
					
						
							|  |  |  |   final int currentTrack; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Whether the player is playing (or paused).
 | 
					
						
							|  |  |  |   final bool playing; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The current playback position in milliseconds.
 | 
					
						
							|  |  |  |   final int positionMs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The duration of the currently played track in milliseconds.
 | 
					
						
							|  |  |  |   final int durationMs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   PlaybackServiceState({ | 
					
						
							|  |  |  |     this.playlist, | 
					
						
							|  |  |  |     this.currentTrack, | 
					
						
							|  |  |  |     this.playing, | 
					
						
							|  |  |  |     this.positionMs, | 
					
						
							|  |  |  |     this.durationMs, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   factory PlaybackServiceState.fromJson(Map<String, dynamic> json) => | 
					
						
							|  |  |  |       PlaybackServiceState( | 
					
						
							|  |  |  |         playlist: json['playlist'] | 
					
						
							|  |  |  |             .map<InternalTrack>((j) => InternalTrack.fromJson(j)) | 
					
						
							|  |  |  |             .toList(), | 
					
						
							|  |  |  |         currentTrack: json['currentTrack'], | 
					
						
							|  |  |  |         playing: json['playing'], | 
					
						
							|  |  |  |         positionMs: json['positionMs'], | 
					
						
							|  |  |  |         durationMs: json['durationMs'], | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Map<String, dynamic> toJson() => { | 
					
						
							|  |  |  |         'playlist': playlist.map((t) => t.toJson()), | 
					
						
							|  |  |  |         'currentTrack': currentTrack, | 
					
						
							|  |  |  |         'playing': playing, | 
					
						
							|  |  |  |         'positionMs': positionMs, | 
					
						
							|  |  |  |         'durationMs': durationMs, | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  | class _PlaybackService extends BackgroundAudioTask { | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   /// The interval between playback position updates in milliseconds.
 | 
					
						
							|  |  |  |   static const positionUpdateInterval = 250; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   static const playControl = MediaControl( | 
					
						
							|  |  |  |     androidIcon: 'drawable/ic_play', | 
					
						
							|  |  |  |     label: 'Play', | 
					
						
							|  |  |  |     action: MediaAction.play, | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static const pauseControl = MediaControl( | 
					
						
							|  |  |  |     androidIcon: 'drawable/ic_pause', | 
					
						
							|  |  |  |     label: 'Pause', | 
					
						
							|  |  |  |     action: MediaAction.pause, | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static const stopControl = MediaControl( | 
					
						
							|  |  |  |     androidIcon: 'drawable/ic_stop', | 
					
						
							|  |  |  |     label: 'Stop', | 
					
						
							|  |  |  |     action: MediaAction.stop, | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static const dummyMediaItem = MediaItem( | 
					
						
							|  |  |  |     id: 'dummy', | 
					
						
							|  |  |  |     album: 'Johannes Brahms', | 
					
						
							|  |  |  |     title: 'Symphony No. 1 in C minor, Op. 68: 1. Un poco sostenuto — Allegro', | 
					
						
							|  |  |  |     duration: 10000, | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   final _completer = Completer(); | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |   final List<InternalTrack> _playlist = []; | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |   MusicusPlayer _player; | 
					
						
							|  |  |  |   int _currentTrack = 0; | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   bool _playing = false; | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   int _durationMs = 1000; | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |   _PlaybackService() { | 
					
						
							|  |  |  |     _player = MusicusPlayer(onComplete: () { | 
					
						
							|  |  |  |       // TODO: Go to next track.
 | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   Future<void> _sendMsg(dynamic msg) { | 
					
						
							|  |  |  |     final sendPort = IsolateNameServer.lookupPortByName(_portName); | 
					
						
							|  |  |  |     sendPort?.send(msg); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   Future<void> _setState() async { | 
					
						
							|  |  |  |     final positionMs = await _player.getPosition() ?? 0; | 
					
						
							|  |  |  |     final updateTime = DateTime.now().millisecondsSinceEpoch; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     AudioServiceBackground.setState( | 
					
						
							|  |  |  |       controls: | 
					
						
							|  |  |  |           _playing ? [pauseControl, stopControl] : [playControl, stopControl], | 
					
						
							|  |  |  |       basicState: | 
					
						
							|  |  |  |           _playing ? BasicPlaybackState.playing : BasicPlaybackState.paused, | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |       position: positionMs, | 
					
						
							|  |  |  |       updateTime: updateTime, | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     AudioServiceBackground.setMediaItem(dummyMediaItem); | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     _sendMsg(PlaybackServiceState( | 
					
						
							|  |  |  |       playlist: _playlist, | 
					
						
							|  |  |  |       currentTrack: _currentTrack, | 
					
						
							|  |  |  |       playing: _playing, | 
					
						
							|  |  |  |       positionMs: positionMs, | 
					
						
							|  |  |  |       durationMs: _durationMs, | 
					
						
							|  |  |  |     )); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Future<void> _updatePosition() async { | 
					
						
							|  |  |  |     while (_playing) { | 
					
						
							|  |  |  |       await Future.delayed( | 
					
						
							|  |  |  |           const Duration(milliseconds: positionUpdateInterval)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // TODO: Consider seperating position updates from general state updates
 | 
					
						
							|  |  |  |       // and/or estimating the position instead of asking the player.
 | 
					
						
							|  |  |  |       _setState(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Future<void> onStart() async { | 
					
						
							|  |  |  |     _setState(); | 
					
						
							|  |  |  |     await _completer.future; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |   @override | 
					
						
							|  |  |  |   void onCustomAction(String name, dynamic arguments) { | 
					
						
							|  |  |  |     super.onCustomAction(name, arguments); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // addTracks expects a List<Map<String, dynamic>> as its argument.
 | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |     // skipTo expects an integer as its argument.
 | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |     if (name == 'addTracks') { | 
					
						
							|  |  |  |       final tracksJson = jsonDecode(arguments); | 
					
						
							|  |  |  |       final List<InternalTrack> tracks = List.castFrom( | 
					
						
							|  |  |  |           tracksJson.map((j) => InternalTrack.fromJson(j)).toList()); | 
					
						
							|  |  |  |       _playlist.addAll(tracks); | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |       _player.setUri(tracks.first.uri).then((newDurationMs) { | 
					
						
							|  |  |  |         _durationMs = newDurationMs; | 
					
						
							|  |  |  |         _setState(); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (name == 'skipTo') { | 
					
						
							|  |  |  |       final index = arguments as int; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (index >= 0 && index < _playlist.length) { | 
					
						
							|  |  |  |         _currentTrack = index; | 
					
						
							|  |  |  |         _player.setUri(_playlist[index].uri); | 
					
						
							|  |  |  |         _setState(); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     } else if (name == 'sendState') { | 
					
						
							|  |  |  |       // Send the current state to the main isolate.
 | 
					
						
							|  |  |  |       _setState(); | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   @override | 
					
						
							|  |  |  |   void onPlay() { | 
					
						
							|  |  |  |     super.onPlay(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |     _player.play(); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     _playing = true; | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     _updatePosition(); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     _setState(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void onPause() { | 
					
						
							|  |  |  |     super.onPause(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |     _player.pause(); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     _playing = false; | 
					
						
							|  |  |  |     _setState(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void onSeekTo(int position) { | 
					
						
							|  |  |  |     super.onSeekTo(position); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     _player.seekTo(position).then((_) { | 
					
						
							|  |  |  |       _setState(); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |   @override | 
					
						
							|  |  |  |   void onSkipToNext() { | 
					
						
							|  |  |  |     super.onSkipToNext(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (_playlist.length > 1 && _currentTrack < _playlist.length - 1) { | 
					
						
							|  |  |  |       _currentTrack++; | 
					
						
							|  |  |  |       _player.setUri(_playlist[_currentTrack].uri); | 
					
						
							|  |  |  |       _setState(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void onSkipToPrevious() async { | 
					
						
							|  |  |  |     super.onSkipToPrevious(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // If more than five seconds of the current track have been played, go back
 | 
					
						
							|  |  |  |     // to its beginning, else, switch to the previous track.
 | 
					
						
							|  |  |  |     if (await _player.getPosition() > 5000) { | 
					
						
							|  |  |  |       await _player.setUri(_playlist[_currentTrack].uri); | 
					
						
							|  |  |  |       _setState(); | 
					
						
							|  |  |  |     } else if (_playlist.length > 1 && _currentTrack > 0) { | 
					
						
							|  |  |  |       _currentTrack--; | 
					
						
							|  |  |  |       _player.setUri(_playlist[_currentTrack].uri); | 
					
						
							|  |  |  |       _setState(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   @override | 
					
						
							|  |  |  |   void onStop() { | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |     _player.stop(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     AudioServiceBackground.setState( | 
					
						
							|  |  |  |       controls: [], | 
					
						
							|  |  |  |       basicState: BasicPlaybackState.stopped, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     _sendMsg(null); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     // This will end onStart.
 | 
					
						
							|  |  |  |     _completer.complete(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |