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:

  1. sometimes, two audio files start playing at the same. When I send the stop intent, only the one that started playing first is stopped

  2. 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 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