I am using flutter audio_service and just_audio package for music player. I want to play specific queue position media item from playlist when I initialize the music player. It is always playing first item of the playlist when I called AudioService.start() method. How can I pass and play specific queue position media item from playlist when I start the audio service?
AudioService start
AudioService.start(
backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
androidNotificationChannelName: 'Zenmind',
androidNotificationColor: 0xFF2196f3,
androidNotificationIcon: 'mipmap/ic_launcher',
androidEnableQueue: true,
params: params); // [params contains playlist ]
_audioPlayerTaskEntrypoint code
void _audioPlayerTaskEntrypoint() async {
AudioServiceBackground.run(() => AudioPlayerTask());
}
AudioPlayerTask class
class AudioPlayerTask extends BackgroundAudioTask {
var _queue = <MediaItem>[];
AudioPlayer _player = new AudioPlayer();
AudioProcessingState _skipState;
Seeker _seeker;
StreamSubscription<PlaybackEvent> _eventSubscription;
List<MediaItem> get queue => _queue;
int get index => _player.currentIndex;
MediaItem get mediaItem => index == null ? null : queue[index];
@override
Future<void> onStart(Map<String, dynamic> params) async {
_queue.clear();
List mediaItems = params['data'];
// print(params['data']);
for (int i = 0; i < mediaItems.length; i++) {
MediaItem mediaItem = MediaItem.fromJson(mediaItems[i]);
_queue.add(mediaItem);
}
_player.currentIndexStream.listen((index) {
print("index value is $index");
if (index != null) {
AudioServiceBackground.setMediaItem(queue[index]);
}
});
_eventSubscription = _player.playbackEventStream.listen((event) {
_broadcastState();
});
_player.processingStateStream.listen((state) {
switch (state) {
case ProcessingState.completed:
onStop();
break;
case ProcessingState.ready:
_skipState = null;
break;
default:
break;
}
});
AudioServiceBackground.setQueue(queue);
try {
await _player.setAudioSource(ConcatenatingAudioSource(
children:
queue.map((item) => AudioSource.uri(Uri.parse(item.id))).toList(),
));
onSkipToQueueItem(queue[1].id);
onPlay();
} catch (e) {
print("Error: $e");
onStop();
}
}
@override
Future<void> onSkipToQueueItem(String mediaId) async {
final newIndex = queue.indexWhere((item) => item.id == mediaId);
if (newIndex == -1) return;
_skipState = newIndex > index
? AudioProcessingState.skippingToNext
: AudioProcessingState.skippingToPrevious;
_player.seek(Duration.zero, index: newIndex);
AudioServiceBackground.sendCustomEvent('skip to $newIndex');
}
@override
Future<void> onPlay() => _player.play();
@override
Future<void> onPause() => _player.pause();
@override
Future<void> onSeekTo(Duration position) => _player.seek(position);
@override
Future<void> onFastForward() => _seekRelative(fastForwardInterval);
@override
Future<void> onRewind() => _seekRelative(-rewindInterval);
@override
Future<void> onSeekForward(bool begin) async => _seekContinuously(begin, 1);
@override
Future<void> onSeekBackward(bool begin) async => _seekContinuously(begin, -1);
@override
Future<void> onStop() async {
await _player.dispose();
_eventSubscription.cancel();
await _broadcastState();
await super.onStop();
}
Future<void> _seekRelative(Duration offset) async {
var newPosition = _player.position + offset;
if (newPosition < Duration.zero) newPosition = Duration.zero;
if (newPosition > mediaItem.duration) newPosition = mediaItem.duration;
// if (newPosition > _player.duration) newPosition = _player.duration;
await _player.seek(newPosition);
}
void _seekContinuously(bool begin, int direction) {
_seeker?.stop();
if (begin) {
_seeker = Seeker(
_player,
Duration(seconds: 10 * direction),
// Duration(seconds: 1), mediaItem)
Duration(seconds: 1),
queue[_player.currentIndex])
..start();
}
}
Future<void> _broadcastState() async {
await AudioServiceBackground.setState(
controls: [
MediaControl.skipToPrevious,
if (_player.playing) MediaControl.pause else MediaControl.play,
MediaControl.stop,
MediaControl.skipToNext,
],
systemActions: [
MediaAction.seekTo,
MediaAction.seekForward,
MediaAction.seekBackward,
],
androidCompactActions: [0, 1, 3],
processingState: _getProcessingState(),
playing: _player.playing,
position: _player.position,
bufferedPosition: _player.bufferedPosition,
speed: _player.speed,
);
}
AudioProcessingState _getProcessingState() {
if (_skipState != null) return _skipState;
switch (_player.processingState) {
case ProcessingState.idle:
return AudioProcessingState.stopped;
case ProcessingState.loading:
return AudioProcessingState.connecting;
case ProcessingState.buffering:
return AudioProcessingState.buffering;
case ProcessingState.ready:
return AudioProcessingState.ready;
case ProcessingState.completed:
return AudioProcessingState.completed;
default:
throw Exception("Invalid state: ${_player.processingState}");
}
}
}
In audio_service 0.17, the params
passed into start()
were only intended for simple data types, not for lists of MediaItem
s. In fact there are other methods in the API specifically designed for that.
I suggest the following startup sequence instead:
// Set the playlist
await AudioService.updateQueue(playlist);
// Jump to the right item
await AudioService.skipToQueueItem(...);
// Play
AudioService.play(); // don't await!
Note: Replace AudioService.
by audioHandler.
if you use version 0.18.0 or later.
The await
keyword above is important. These methods are asynchronous, and the later methods should not be called until the earlier ones have completed. For example, you don't want to skip to a particular queue item until after the queue has actually been set. But note the lack of await
on the last step: you don't await the play
call unless you want to wait for playback to complete.
In your background audio task (0.17) or audio handler (0.18), add the callback for updateQueue
:
// 0.17 solution:
Future<void> onUpdateQueue(List<MediaItem> queue) async {
AudioServiceBackground.setQueue(_queue = queue);
await _player.setAudioSource(ConcatenatingAudioSource(
children:
queue.map((item) => AudioSource.uri(Uri.parse(item.id))).toList(),
));
// 0.18 solution:
Future<void> updateQueue(List<MediaItem> queue) async {
this.queue.add(_queue = queue);
await _player.setAudioSource(ConcatenatingAudioSource(
children:
queue.map((item) => AudioSource.uri(Uri.parse(item.id))).toList(),
));
}
You already have an onStart
, but remember that using the suggested startup sequence above, the queue will be set in a later step, and the player will skip to the right queue item in a later step, so you can remove those parts from your onStart
, and just keep the code that initialises the event listeners. (In 0.18, that logic would go in your audio handler constructor).