Office Hours Transcript: 2021-12-07
john joined
hello, john!
how can I help you today?
Hello Mark,
I would like to know the proper way to stop a MediaPlayer that is started in a foreground service. Now I am using an intent that I send from a screen intent/viewmodel, but there are two issues:
sometimes, two audio files start playing at the same. When I send the stop intent, only the one that started playing first is stopped
sometimes, I receive an error saying that the media player doesn’t exist
================
in the service
if (intent?.action == "STOP_AUDIO"){
mp.stop()
mp.release()
}
==============
in the viewmodel
fun stopAudio() {
val stopAudioIntent = Intent(app, MusicService::class.java)
stopAudioIntent.action = "STOP_AUDIO"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
app.startForegroundService(stopAudioIntent)
} else {
app.startService(stopAudioIntent)
}
}
you might want to change your approach a bit: instead of having the service "own" the MediaPlayer
instance, have some singleton own the MediaPlayer
instance
the service is just around for the purposes of keeping your process alive longer
this assumes that the service is in the same process as your UI
this will allow your viewmodel to talk to that singleton directly, rather than have to try to send Intent
objects to the service and have the service control the MediaPlayer
I am not sure how to go about this. Where do I create the object? In the ServiceActivity?
are you using any sort of dependency inversion framework? (Koin, Dagger/Hilt, etc.)
Yes, Dagger-Hilt.
then set up some Dagger singleton that manages your MediaPlayer
you can inject that singleton into the viewmodel and into the service
and have each of them talk to the singleton, which manages the MediaPlayer
this will be simpler than using Intent
objects as commands
and, if you still run into double-init/double-free problems, like you described, now everything is simple function calls to try to track down why stuff is happening twice instead of once
I added the mediaplayer to the Hilt module like so:
@Singleton
@Provides
fun provideMediaPlayer() = MediaPlayer()
Injected it in the service activity, and moved the stop function to viewmodel.
It starts playing fine but it crashes when I try to stop it
fun stopAudio() {
mp.stop()
mp.release()
}
==============
java.lang.IllegalStateException
at android.media.MediaPlayer._stop(Native Method)
at android.media.MediaPlayer.stop(MediaPlayer.java:1522)
I was not envisioning directly injecting the MediaPlayer
, but rather some singleton that manages it. Regardless, in terms of your crash, make sure that you are working with the same MediaPlayer
instance (i.e., the instance that you are starting is the instance that you are trying to stop).
someone joined
@john: let me take a question from someone, and I will be back with you shortly
@someone: hello! how can I help you today?
Hi Mark, I’m saddened that you’ll no longer be continuing CommonsWare. Will you be doing anything similar for Android that I can follow?
I will be blogging at https://commonsware.com/blog/
I hope to continue my Jetpack Compose newsletter at https://jetc.dev/
I will be continuing to answer questions at Stack Overflow
and if I get into anything else, I will be sure to blog about it
CommonsWare itself will continue, as a "hobby with a logo", not a business
I hope you flourish wherever you go and with whatever you do. You’ve done all of us a great service. I will always continue following your blog. I wish you all the best.
thanks for the kind words!
someone left
@john: OK, back to you! do you have another question?
Yes, I’m sorry I’m not sure how to implement your suggestions.
but rather some singleton that manages it
I did put the @Singleton annotation, do you mean I should return an object?
make sure that you are working with the same MediaPlayer instance
I don’t know how to do that.
I did put the @Singleton annotation, do you mean I should return an object?
Yes, I was thinking of some class that is responsible for mapping your business rules to MediaPlayer
methods calls.
I don’t know how to do that.
Log.d("YourAwesomeApp", "starting using $mp")
(and similarly for stopping)
I doubt that MediaPlayer
has a custom toString()
implementation, so that will log the object ID of the MediaPlayer
instance
or, if you prefer, set breakpoints and examine the object ID values in the debugger
Yes, I was thinking of some class that is responsible for mapping your business rules to MediaPlayer methods calls.
@Singleton
@Provides
fun provideMediaPlayerObject() = object { val mp = MediaPlayer() }
Like that? Then how do I access it without inject it in the Service/ViewModel?
well, I was expecting a somewhat more elaborate class
so fun providedHeyThisIsWhatMarkWasTalkingAbout() = SomethingThatKnowsAboutMediaInYourApp()
Oh, you mean have start, stop, release, … in that class?
correct!
I am assuming that there are other things that may wind up belonging there, helping to map what your app does to things that MediaPlayer
knows how to do
and that class can have your listeners for MediaPlayer
events and stuff
I see, but then it’s not a (Kotlin) object you’re talking about? And still, how do I access it without inject it?
then it’s not a (Kotlin) object you’re talking about?
if you mean a Kotlin object
, then no, just an instance of a Kotlin class
how do I access it without inject it?
no, you would inject, but inject that SomethingThatKnowsAboutMediaInYourApp
instance, not a MediaPlayer
instance directly
Now it’s clear!
sorry if I muddled the explanation earlier!
Not at all. Thanks to you, I feel I made tremendous progress, but I’m still only at the end of the beginning.
Thank you for your help today!
john left