audio_service
audio_service copied to clipboard
Missing documentation on working with queue
It is not very easy to figure out how to correctly work with the queue. Example code doesn't cover this, neither does the existing documentation.
Shall we create our own List<MediaItem> myQueue
within MyBackgroundPlayer
and override all queue related methods to operate myQueue
? How do we then tie this to AudioService.queue
?
Shall we instead update AudioService.queue
object from MyBackgroundPlayer
queue method overrides? etc.
Thanks!
You can use AudioServiceBackground.setQueue to perform queue operations.
@secretwpn Did you figure out how to work with queue?
@rohansohonee this does not explain anything nor does it answer any of the raised questions.
@alexjuniodev yes, sort of. At least I have my take on it, not sure if it is the correct one.
What I've ended up doing is overriding all queue related methods in my BackgroundAudioPlayer
class (this is the one that extends the library's BackgroundAudioTask
)
And I've basically just created my own List<MediaItem> _queue
to hold queue items and int _queueIndex
to determine my position in the queue. I guess the idea is clear, right?
In onAddQueueItem
you could just add an item to the _queue
In onSkipToNext
you manipulate the _queueIndex
And whenever you actually setMediaItem
- you take the one from the queue like _queue[_queueIndex]
and so on.
One thing I don't quite get yet, how AudioService.queue
now relates to my own queue. I guess it doesn't, which makes me think that the lib authors had a bit different idea in mind when creating the lib. Well, that's exactly why I was asking for some docs.
Hi @secretwpn
AudioService.queue
is to be used to retrieve your queue in UI.
AudioServiceBackground.setQueue
is to be used to set your queue in the background.
The AudioService
class is only for UI code.
Hi @secretwpn
You should not use AudioService
class in your BackgroundAudioPlayer class.
Your queue logic should reside in BackgroundAudioPlayer class.
The lib author @ryanheise has created a client-server architecture. I hope this is useful.
@rohansohonee yea, thanks. I was actually not using AudioService
in the background class
The thing that got me puzzled is that in BackgroundAudioTask
there is no queue object, so if you override some queue related methods - which list/set/array do you work with? This has initially put me on the way of having my own list as a _queue
and then working with it in overrides.
However, in my case, things are even more complicated, due to some limitations my _queue
is not even a list of MediaItem
s but instead a list of objects of my own class that are then converted to MediaItem just before playing. So @alexjuniodev I might be a wrong guy to explain the correct way of using the queue.
Thanks everyone for the helpful comments. @rohansohonee 's answer is correct and I would just add that audio_service doesn't implement the queue logic, but it also doesn't implement the audio playing logic, the fast forwarding logic, or any of the logic for that matter.
All of the methods on AudioService simply pass through to the background isolate but nothing will happen unless you override the corresponding methods on the "server side" to implement whatever logic you need.
The architecture is rather like the MediaBrowserService and MediaBrowser Android APIs because that is what this package delegates to under the hood.
It is completely fine to use your own internal representation for your queue. When transmitting queue information through the client/server channel, you need to convert it to a standard representation that can be broadcast not only to your flutter view but also to other clients such as Google Auto. However, each queue item (a MediaItem) contains an ID which you can use as a key into your own queue data structure if you have one.
I've been thinking about this for a few hours now and can't really find a satisfying solution.
I think, I get the general idea behind this architecture, but getting information other than MediaItem
s seems to be overly difficult (I might be missing a major point here though).
My concrete problem is that the audioplayer in the background task needs a url to play a song, but MediaItem
doesn't have one. So I'd need some sort of map there to get from ids to urls.
Is there a best practice to solve this issue? Making an extended example with this would be a great help I think.
Edit: Would it be suitable to just add an optional String url
property to MediaItem
?
Hi @moritz-weber
There is actually no rule against using the song's URL as your media ID which would make having a map redundant in your case.
I'll see about expanding on the example.
@moritz-weber I've just updated the example to demonstrate queue management. Let me know if that answers your question or if you have any more questions.
Thanks everyone for the helpful comments. @rohansohonee 's answer is correct and I would just add that audio_service doesn't implement the queue logic, but it also doesn't implement the audio playing logic, the fast forwarding logic, or any of the logic for that matter.
All of the methods on AudioService simply pass through to the background isolate but nothing will happen unless you override the corresponding methods on the "server side" to implement whatever logic you need.
The architecture is rather like the MediaBrowserService and MediaBrowser Android APIs because that is what this package delegates to under the hood.
It is completely fine to use your own internal representation for your queue. When transmitting queue information through the client/server channel, you need to convert it to a standard representation that can be broadcast not only to your flutter view but also to other clients such as Google Auto. However, each queue item (a MediaItem) contains an ID which you can use as a key into your own queue data structure if you have one.
@ryanheise, this really clarified for me how this plugin is supposed to be used. I think that this information should be added to the README
@yringler agreed, and I'll keep this issue open until I've written more documentation.
when I add AudioServiceBackground.setQueue
I am getting error.
my function to get dynamic list from api ;
await RadioProvider().getRadios().then((list){
if(list.length>0){
otherRadioList.addAll(list);
latestItem.value =list[0];
}
startService();
});
my function to start service
RxList<MediaItem> otherRadioList = RxList<MediaItem>([]);
Rx<MediaItem> latestItem = Rx<MediaItem> ();
startService([Function function]) async {
AudioService.start(
backgroundTaskEntrypoint: audioPlayerTaskEntrypoint,
androidNotificationChannelName: 'hayirliradyo',
androidStopForegroundOnPause: true,
androidNotificationColor: 0xFF2196f3,
androidNotificationIcon: 'mipmap/ic_launcher_white',
androidEnableQueue: true,
).then((value){
AudioServiceBackground.setQueue(otherRadioList.toList());
loadingString.value = 'tamam';
if(function!=null){
function();
}
// else
// AudioService.play();
} );
}
I am getting error below
E/flutter (28259): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: NoSuchMethodError: The method 'invokeMethod' was called on null.
E/flutter (28259): Receiver: null
E/flutter (28259): Tried calling: invokeMethod<dynamic>("setQueue", Instance(length:7) of '_GrowableList')
E/flutter (28259): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
E/flutter (28259): #1 AudioServiceBackground.setQueue
package:audio_service/audio_service.dart:1538
E/flutter (28259): #2 startService.<anonymous closure>
package:hayirliradyo/utils/audio_services.dart:32
E/flutter (28259): #3 _rootRunUnary (dart:async/zone.dart:1198:47)
E/flutter (28259): #4 _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter (28259): #5 _FutureListener.handleValue (dart:async/future_impl.dart:143:18)
E/flutter (28259): #6 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:696:45)
E/flutter (28259): #7 Future._propagateToListeners (dart:async/future_impl.dart:725:32)
E/flutter (28259): #8 Future._completeWithValue (dart:async/future_impl.dart:529:5)
E/flutter (28259): #9 _AsyncAwaitCompleter.complete (dart:async-patch/async_patch.dart:40:15)
E/flutter (28259): #10 _completeOnAsyncReturn (dart:async-patch/async_patch.dart:311:13)
which means ``` _backgroundChannel = const MethodChannel('ryanheise.com/audioServiceBackground');
my question is in which step I should call `AudioServiceBackground.setQueue()` method ? I am using example app. thanks
@xyzbilal , to quote @rohansohonee above, this library uses a client/server architecture. As per the README, you shouldn't actually use the AudioService.*
and AudioServiceBackground.*
APIs together in the same block of code or even the same isolate. See this paragraph:
Note that your UI and background task run in separate isolates and do not share memory. The only way they communicate is via message passing. Your Flutter UI will only use the AudioService API to communicate with the background task, while your background task will only use the AudioServiceBackground API to interact with the UI and other clients.
The documentation for AudioServiceBackground
also says:
Background API to be used by your background audio task.
If you look in the example, you will see that methods in AudioServiceBackground
are only ever called from the background audio task and they are typically used to communicate information to the clients (because this is from the "server's" perspective using the client/server analogy). The documentation of setQueue
says:
Sets the current queue and notifies all clients.
So that means it should not be used FROM the client to talk to the background audio task, but the other way around. The AudioService.*
API is the one that you should use from the client to talk to the background audio task.
Perhaps the documentation was unclear? Which of the above documentation did you find was the most confusing or unclear?
Alright. I am not so much familiar with isolates or backround services and I could'nt get the whole point of what written before but, I solved my problem with logic below.
- I use Getx and Get storage for state management and saving preferences. and I have two variable that list for ui and determine the latest mediaitem to hold latest played item and if I stop and start again not to start from 0 index of queue
RxList<MediaItem> otherRadioList = RxList<MediaItem>([]);
Rx<MediaItem> latestItem = Rx<MediaItem> ();
- when My Widget is build GetX controller class initilaze and in onStart I call
startServices()
method then I add AudioService.queue to otherRadioList and I guess the magic trick is AudioService.queue. it immediately gives the queue items when service start and it makes me uptade ui accordingly.
void startService() async {
AudioService.start(
backgroundTaskEntrypoint: audioPlayerTaskEntrypoint,
androidNotificationChannelName: 'hayirliradyo',
androidStopForegroundOnPause: true,
androidNotificationColor: 0xFF2196f3,
androidNotificationIcon: 'mipmap/ic_launcher_white',
androidEnableQueue: true,
).then((value){
otherRadioList.addAll(AudioService.queue);
loadingString.value = 'ok';
} );
}
here I listen otherRadioList and I set latest Item if it is saved to preferences.
ever(otherRadioList, (s) {
durations.clear();
otherRadioList.forEach((element) {
final dur = Rx<Duration>();
durations.add(dur);
});
if (box != null && box.hasData('index') && box.read('index') != -1) {
final index = box.read('index');
//AudioService.skipToQueueItem(otherRadioList[index].id);
latestItem.value = otherRadioList[index];
AudioService.playFromMediaId(latestItem.value.id);
} else {
latestItem.value = otherRadioList[0];
box.write('index', 0);
AudioService.playFromMediaId(latestItem.value.id);
}
});
then I moved my network request to update AudioServiceBackground.queue onStart method of BackroundTask and it worked as I expected. I put here that can be reference for anyone needed .
.....
.....
@override
Future<void> onStart(Map<String, dynamic> params) async {
if(queue == null)
queue = await RadioProvider().getRadios();
_player.currentIndexStream.listen((index) {
if (index != null)
AudioServiceBackground.setMediaItem(queue[index]);
});
.....
.....
}
@ryanheise is this issue still valid for the one-isolate
? I believe currently we have it documented and shown in example
Technically it is documented now although I would like to polish it further.