| 
									
										
										
										
											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!
 | 
					
						
							| 
									
										
										
										
											2020-04-22 10:32:36 +02:00
										 |  |  |   final currentIndex = BehaviorSubject.seeded(0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The currently played track.
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   ///
 | 
					
						
							| 
									
										
										
										
											2020-04-22 10:32:36 +02:00
										 |  |  |   /// This will be null, if there is no  current track.
 | 
					
						
							|  |  |  |   final currentTrack = BehaviorSubject<InternalTrack>.seeded(null); | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							|  |  |  |   /// 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([]); | 
					
						
							| 
									
										
										
										
											2020-04-22 10:32:36 +02:00
										 |  |  |     currentIndex.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
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   /// Update [position] and [normalizedPosition].
 | 
					
						
							| 
									
										
										
										
											2020-04-24 21:49:14 +02:00
										 |  |  |   ///
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   /// Requires [duration] to be up to date
 | 
					
						
							|  |  |  |   void _updatePosition(int positionMs) { | 
					
						
							|  |  |  |     position.add(Duration(milliseconds: positionMs)); | 
					
						
							|  |  |  |     normalizedPosition.add(positionMs / duration.value.inMilliseconds); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Update [position], [duration] and [normalizedPosition].
 | 
					
						
							|  |  |  |   void _updateDuration(int positionMs, int durationMs) { | 
					
						
							|  |  |  |     position.add(Duration(milliseconds: positionMs)); | 
					
						
							|  |  |  |     duration.add(Duration(milliseconds: durationMs)); | 
					
						
							|  |  |  |     normalizedPosition.add(positionMs / durationMs); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Update [currentIndex] and [currentTrack].
 | 
					
						
							| 
									
										
										
										
											2020-04-24 21:49:14 +02:00
										 |  |  |   ///
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   /// Requires [playlist] to be up to date.
 | 
					
						
							|  |  |  |   void _updateCurrentTrack(int index) { | 
					
						
							|  |  |  |     currentIndex.add(index); | 
					
						
							|  |  |  |     currentTrack.add(playlist.value[index]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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(); | 
					
						
							| 
									
										
										
										
											2020-04-24 21:49:14 +02:00
										 |  |  |       receivePort.asBroadcastStream( | 
					
						
							|  |  |  |         onListen: (subscription) { | 
					
						
							|  |  |  |           _playbackServiceStateSubscription = subscription; | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       ).listen((msg) { | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |         // If state is null, the background audio service has stopped.
 | 
					
						
							|  |  |  |         if (msg == null) { | 
					
						
							|  |  |  |           _stop(); | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |           if (msg is _StatusMessage) { | 
					
						
							|  |  |  |             playing.add(msg.playing); | 
					
						
							|  |  |  |           } else if (msg is _PositionMessage) { | 
					
						
							|  |  |  |             _updatePosition(msg.positionMs); | 
					
						
							|  |  |  |           } else if (msg is _TrackMessage) { | 
					
						
							|  |  |  |             _updateCurrentTrack(msg.currentTrack); | 
					
						
							|  |  |  |             _updateDuration(msg.positionMs, msg.durationMs); | 
					
						
							|  |  |  |           } else if (msg is _PlaylistMessage) { | 
					
						
							|  |  |  |             playlist.add(msg.playlist); | 
					
						
							|  |  |  |             _updateCurrentTrack(msg.currentTrack); | 
					
						
							|  |  |  |             _updateDuration(msg.positionMs, msg.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-26 18:54:49 +02:00
										 |  |  |   /// Remove the track at [index] from the playlist.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If the player is not active or an invalid value is provided, this will do
 | 
					
						
							|  |  |  |   /// nothing.
 | 
					
						
							|  |  |  |   Future<void> removeTrack(int index) async { | 
					
						
							|  |  |  |     if (AudioService.running) { | 
					
						
							|  |  |  |       await AudioService.customAction('removeTrack', index); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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.
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   ///
 | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |   /// 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.
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   ///
 | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |   /// 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(); | 
					
						
							| 
									
										
										
										
											2020-04-22 10:32:36 +02:00
										 |  |  |     playlist.close(); | 
					
						
							|  |  |  |     currentIndex.close(); | 
					
						
							|  |  |  |     currentTrack.close(); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     playing.close(); | 
					
						
							|  |  |  |     position.close(); | 
					
						
							|  |  |  |     duration.close(); | 
					
						
							|  |  |  |     normalizedPosition.close(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  | /// A message from the playback service to the UI.
 | 
					
						
							|  |  |  | abstract class _Message {} | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  | /// Playback status update.
 | 
					
						
							|  |  |  | class _StatusMessage extends _Message { | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   /// Whether the player is playing (or paused).
 | 
					
						
							|  |  |  |   final bool playing; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 21:49:14 +02:00
										 |  |  |   /// Playback position in milliseconds.
 | 
					
						
							|  |  |  |   final int positionMs; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   _StatusMessage({ | 
					
						
							|  |  |  |     this.playing, | 
					
						
							| 
									
										
										
										
											2020-04-24 21:49:14 +02:00
										 |  |  |     this.positionMs, | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// The playback position has changed.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// This could be due to seeking or because time progressed.
 | 
					
						
							|  |  |  | class _PositionMessage extends _Message { | 
					
						
							|  |  |  |   /// Playback position in milliseconds.
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   final int positionMs; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   _PositionMessage({ | 
					
						
							|  |  |  |     this.positionMs, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// The current track has changed.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// This also notifies about the playback position, as the old position could be
 | 
					
						
							|  |  |  | /// behind the new duration.
 | 
					
						
							|  |  |  | class _TrackMessage extends _Message { | 
					
						
							|  |  |  |   /// Index of the new track within the playlist.
 | 
					
						
							|  |  |  |   final int currentTrack; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Duration of the new track in milliseconds.
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   final int durationMs; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   /// Playback position in milliseconds.
 | 
					
						
							|  |  |  |   final int positionMs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _TrackMessage({ | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     this.currentTrack, | 
					
						
							|  |  |  |     this.durationMs, | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |     this.positionMs, | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  | /// The playlist was changed.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// This also notifies about the current track, as the old index could be out of
 | 
					
						
							|  |  |  | /// range in the new playlist.
 | 
					
						
							|  |  |  | class _PlaylistMessage extends _Message { | 
					
						
							|  |  |  |   /// The new playlist.
 | 
					
						
							|  |  |  |   final List<InternalTrack> playlist; | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   /// The current track.
 | 
					
						
							|  |  |  |   final int currentTrack; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Duration of the current track in milliseconds.
 | 
					
						
							|  |  |  |   final int durationMs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Playback position in milliseconds.
 | 
					
						
							|  |  |  |   final int positionMs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _PlaylistMessage({ | 
					
						
							|  |  |  |     this.playlist, | 
					
						
							|  |  |  |     this.currentTrack, | 
					
						
							|  |  |  |     this.durationMs, | 
					
						
							|  |  |  |     this.positionMs, | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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() { | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |     _player = MusicusPlayer(onComplete: () async { | 
					
						
							|  |  |  |       if (_currentTrack < _playlist.length - 1) { | 
					
						
							|  |  |  |         await _setCurrentTrack(_currentTrack + 1); | 
					
						
							|  |  |  |         _sendTrack(); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         _playing = false; | 
					
						
							|  |  |  |         _sendStatus(); | 
					
						
							|  |  |  |         _setState(); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2020-04-21 17:37:01 +02:00
										 |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   /// Update the audio service status for the system.
 | 
					
						
							| 
									
										
										
										
											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-24 19:50:03 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Send a message to the UI.
 | 
					
						
							|  |  |  |   void _sendMsg(_Message msg) { | 
					
						
							|  |  |  |     final sendPort = IsolateNameServer.lookupPortByName(_portName); | 
					
						
							|  |  |  |     sendPort?.send(msg); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Notify the UI about the current playback status.
 | 
					
						
							| 
									
										
										
										
											2020-04-24 21:49:14 +02:00
										 |  |  |   Future<void> _sendStatus() async { | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |     _sendMsg(_StatusMessage( | 
					
						
							|  |  |  |       playing: _playing, | 
					
						
							| 
									
										
										
										
											2020-04-24 21:49:14 +02:00
										 |  |  |       positionMs: await _player.getPosition(), | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |     )); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   /// Notify the UI about the current playback position.
 | 
					
						
							|  |  |  |   Future<void> _sendPosition() async { | 
					
						
							|  |  |  |     _sendMsg(_PositionMessage( | 
					
						
							|  |  |  |       positionMs: await _player.getPosition(), | 
					
						
							|  |  |  |     )); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Notify the UI about the current track.
 | 
					
						
							|  |  |  |   Future<void> _sendTrack() async { | 
					
						
							|  |  |  |     _sendMsg(_TrackMessage( | 
					
						
							|  |  |  |       currentTrack: _currentTrack, | 
					
						
							|  |  |  |       durationMs: _durationMs, | 
					
						
							|  |  |  |       positionMs: await _player.getPosition(), | 
					
						
							|  |  |  |     )); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Notify the UI about the current playlist.
 | 
					
						
							|  |  |  |   Future<void> _sendPlaylist() async { | 
					
						
							|  |  |  |     _sendMsg(_PlaylistMessage( | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |       playlist: _playlist, | 
					
						
							|  |  |  |       currentTrack: _currentTrack, | 
					
						
							|  |  |  |       durationMs: _durationMs, | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |       positionMs: await _player.getPosition(), | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     )); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   /// Notify the UI of the new playback position periodically.
 | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |   Future<void> _updatePosition() async { | 
					
						
							|  |  |  |     while (_playing) { | 
					
						
							|  |  |  |       await Future.delayed( | 
					
						
							|  |  |  |           const Duration(milliseconds: positionUpdateInterval)); | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |       _sendPosition(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 21:49:14 +02:00
										 |  |  |   /// Set the current track, update the player and notify the system.
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   Future<void> _setCurrentTrack(int index) async { | 
					
						
							|  |  |  |     _currentTrack = index; | 
					
						
							|  |  |  |     _durationMs = await _player.setUri(_playlist[_currentTrack].uri); | 
					
						
							|  |  |  |     _setState(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Add [tracks] to the playlist.
 | 
					
						
							|  |  |  |   Future<void> _addTracks(List<InternalTrack> tracks) async { | 
					
						
							|  |  |  |     final play = _playlist.isEmpty; | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |     _playlist.addAll(tracks); | 
					
						
							|  |  |  |     if (play) { | 
					
						
							|  |  |  |       await _setCurrentTrack(0); | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     _sendPlaylist(); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-26 18:54:49 +02:00
										 |  |  |   /// Remove the track at [index] from the playlist.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If it was the current track, the next track will be played.
 | 
					
						
							|  |  |  |   Future<void> _removeTrack(int index) async { | 
					
						
							|  |  |  |     if (index >= 0 && index < _playlist.length) { | 
					
						
							|  |  |  |       _playlist.removeAt(index); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (_playlist.isEmpty) { | 
					
						
							|  |  |  |         onStop(); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         if (_currentTrack == index) { | 
					
						
							|  |  |  |           await _setCurrentTrack(index); | 
					
						
							|  |  |  |         } else if (_currentTrack > index) { | 
					
						
							|  |  |  |           _currentTrack--; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         _sendPlaylist(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 21:49:14 +02:00
										 |  |  |   /// Jump to the beginning of the track with the index [index].
 | 
					
						
							|  |  |  |   Future<void> _skipTo(int index) async { | 
					
						
							|  |  |  |     if (index >= 0 && index < _playlist.length) { | 
					
						
							|  |  |  |       await _setCurrentTrack(index); | 
					
						
							|  |  |  |       _sendTrack(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-26 18:54:49 +02:00
										 |  |  |     // skipTo and removeTrack expect an integer as their 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()); | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |       _addTracks(tracks); | 
					
						
							| 
									
										
										
										
											2020-04-26 18:54:49 +02:00
										 |  |  |     } else if (name == 'removeTrack') { | 
					
						
							|  |  |  |       final index = arguments as int; | 
					
						
							|  |  |  |       _removeTrack(index); | 
					
						
							|  |  |  |     } else if (name == 'skipTo') { | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |       final index = arguments as int; | 
					
						
							| 
									
										
										
										
											2020-04-24 21:49:14 +02:00
										 |  |  |       _skipTo(index); | 
					
						
							| 
									
										
										
										
											2020-04-21 19:50:18 +02:00
										 |  |  |     } else if (name == 'sendState') { | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |       _sendPlaylist(); | 
					
						
							|  |  |  |       _sendStatus(); | 
					
						
							| 
									
										
										
										
											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-24 19:50:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     _sendStatus(); | 
					
						
							| 
									
										
										
										
											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; | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     _sendStatus(); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     _setState(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   Future<void> onSeekTo(int position) async { | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |     super.onSeekTo(position); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |     await _player.seekTo(position); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     _sendPosition(); | 
					
						
							|  |  |  |     _setState(); | 
					
						
							| 
									
										
										
										
											2020-04-18 13:50:38 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |   @override | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   Future<void> onSkipToNext() async { | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |     super.onSkipToNext(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (_playlist.length > 1 && _currentTrack < _playlist.length - 1) { | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |       await _setCurrentTrack(_currentTrack + 1); | 
					
						
							|  |  |  |       _sendTrack(); | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |   Future<void> onSkipToPrevious() async { | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |     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) { | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |       await _setCurrentTrack(_currentTrack); | 
					
						
							|  |  |  |       _sendTrack(); | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |     } else if (_playlist.length > 1 && _currentTrack > 0) { | 
					
						
							| 
									
										
										
										
											2020-04-24 19:50:03 +02:00
										 |  |  |       await _setCurrentTrack(_currentTrack - 1); | 
					
						
							|  |  |  |       _sendTrack(); | 
					
						
							| 
									
										
										
										
											2020-04-22 10:01:50 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |