5

I have build an audioplayer which is deployed in android google playstore. I'm using crashlytics to monitor crashes and ANRs. Recently I have been getting a lot of crashes MediaButtonReceiver. The headset clicks work fine in many devices. But some devices are giving this problem.

Crashlytics report -

Fatal Exception: java.lang.RuntimeException: Unable to start receiver android.support.v4.media.session.MediaButtonReceiver: java.lang.IllegalStateException: Could not find any Service that handles android.intent.action.MEDIA_BUTTON or implements a media browser service.
       at android.app.ActivityThread.handleReceiver(ActivityThread.java:2866)
       at android.app.ActivityThread.access$1700(ActivityThread.java:182)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1551)
       at android.os.Handler.dispatchMessage(Handler.java:111)
       at android.os.Looper.loop(Looper.java:194)
       at android.app.ActivityThread.main(ActivityThread.java:5706)
       at java.lang.reflect.Method.invoke(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1033)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:828)

MediaSession code -

private void initMediaSession() throws RemoteException {
        if (mediaSessionManager != null) return; //mediaSessionManager exists

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mediaSessionManager = (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE);
        }
        // Create a new MediaSession
        mediaSession = new MediaSessionCompat(this, "AudioPlayer");
        //Get MediaSessions transport controls
        transportControls = mediaSession.getController().getTransportControls();
        //set MediaSession -> ready to receive media commands
        mediaSession.setActive(true);
        //indicate that the MediaSession handles transport control commands
        // through its MediaSessionCompat.Callback.
        mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS|MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);


        //Set mediaSession's MetaData
        updateMetaData();


        mediaSession.setCallback(new MediaSessionCompat.Callback() {
            @Override
            public void onPlay() {
                super.onPlay();

                resumeMedia();
            }

            @Override
            public void onPause() {
                super.onPause();

                pauseMedia();
            }

            @Override
            public void onSkipToNext() {
                super.onSkipToNext();

            }

            @Override
            public void onSkipToPrevious() {
                super.onSkipToPrevious();

            }

            @Override
            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {

                if (su.getHeadsetEnableSwitch()) {

                    String intentAction = mediaButtonIntent.getAction();
                    if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
                        KeyEvent event = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);

                        if (event != null) {


                            int action = event.getAction();
                            Log.e("Headset key: ", String.valueOf(action));
                            if (action == KeyEvent.ACTION_DOWN) {

                                Log.e("Headset: ", "Action down");

                                headsetClickCount++;

                                new Handler().postDelayed(new Runnable() {
                                    @Override
                                    public void run() {

                                        if (headsetClickCount == 1) {

                                            if (isPng()) pauseMedia();
                                            else resumeMedia();

                                            headsetClickCount = 0;

                                        } else if (headsetClickCount == 2) {

                                            if (su.getDoubleClickAction() == 0) {
                                            } else if (su.getDoubleClickAction() == 1)
                                                skipToPrevious();
                                            else if (su.getDoubleClickAction() == 2) skipToNext();
                                            headsetClickCount = 0;
                                        } else if (headsetClickCount == 3) {

                                            if (su.getTripleClickAction() == 0) {
                                            } else if (su.getTripleClickAction() == 1)
                                                skipToPrevious();
                                            else if (su.getTripleClickAction() == 2) skipToNext();
                                            headsetClickCount = 0;
                                        }
                                    }
                                }, 750);
                            }

                            if (action == KeyEvent.FLAG_LONG_PRESS) {

                                if (su.getLongClickAction() == 0) {
                                } else if (su.getLongClickAction() == 1) skipToPrevious();
                                else if (su.getLongClickAction() == 2) skipToNext();

                            }


                            if (action == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {

                                Log.e("Headset: ", "headset sethook");
                                if (isPng()) pauseMedia();
                                else resumeMedia();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_NEXT) {

                                skipToNext();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_PREVIOUS) {

                                skipToPrevious();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_PAUSE) {

                                pauseMedia();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_PLAY) {

                                resumeMedia();
                            }


                        }
                    }

                    return true;

                }

            return true;
            }
        });



    }

What could be the problem and how to solve this?

My thoughts - Maybe this happens because user opens other music apps that has this feature while my app is still playing.

Rektirino
  • 582
  • 5
  • 24

2 Answers2

7

You have to create your own media button receiver class, say MyMediaButtonReceiver.java, that extends MediaButtonReceiver, and it will be empty except for the onReceive method that you have to override, calling super.onReceive(...) between a try-catch that captures the IllegalStateException:

public class MyMediaButtonReceiver extends MediaButtonReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            super.onReceive(context, intent);
        } catch (IllegalStateException e) {
            Log.d(this.getClass().getName(), e.getMessage());
        }
    }
}

Then you have to declare that receiver class in your Manifest (or replace your previous MediaButtonReceiver class declaration, if you had one), like:

<receiver android:name=".MyMediaButtonReceiver" >
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</receiver>
Tom11
  • 2,419
  • 8
  • 30
  • 56
Ramiro
  • 948
  • 2
  • 9
  • 22
  • So what is received in OnReceive() ? Intents from button clicks? – Rektirino Aug 24 '18 at 14:15
  • Yes, it will capture the clicks from physical buttons, and if you implement it as indicated, your app will no longer crash with that exception. Trutst me, it works, I had the very same problem in my own app ;) It is wonderful to see that problem gone from crashlytics console. – Ramiro Aug 24 '18 at 14:25
  • I'll try this solution and get back to you. Might take some time as the app is already on playstore. – Rektirino Aug 24 '18 at 14:28
  • Sure, I did the same, published a new version of my app with that fix. Do it and you will see your crashlytics crash-free statistics graph go really up! – Ramiro Aug 24 '18 at 14:31
  • No problem at all. I wish to add that this implementation will not interfere with your current handling of the buttons (the code you posted), because it just intercepts the exception that the original extended class (MediaButtonReceiver) throws, in a way that it only prevents the crash but then it calls super, thus not interfering with your intended buttons behaviour. The "secondary effect" wil be that once in a while (when the crash occured), the volume button won't work, just for that click, and that, from an user experience point of view, is better than an app total crash. – Ramiro Aug 24 '18 at 14:50
  • Okay. Got it. Thanks – Rektirino Aug 24 '18 at 14:58
  • Sorry just uploaded the update and it looks like it worked. Any workaround for the volume control though? Some users are complaining that they cannot adjust the volume. – Rektirino Aug 28 '18 at 06:22
  • @Rektirino Any update on controlling physical keys, as we catch the exception, it will not crash but keys may not work. – Smeet Sep 26 '19 at 09:55
  • In my app volume keys work fine. Don´t work very few times, when exception is thrown. But most of the time they work. Don't know if it is related, but I'm using exoplayer, not the standard mediaplayer. – Ramiro Sep 26 '19 at 15:39
  • @Ramiro: I will try this to my app. I hope this crash will be gone. In addition do you have crash on "android.app.RemoteServiceException "? If yes please suggest appropriate solution. Thanks. – user1090751 Oct 03 '20 at 16:24
0
class MyMediaButtonReceiver : MediaButtonReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == Intent.ACTION_MEDIA_BUTTON) {
            val event = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
            if (event.action == KeyEvent.ACTION_UP || event.action == 
KeyEvent.ACTION_DOWN) {
            when (event.keyCode) {
                // handle cancel button
                KeyEvent.KEYCODE_MEDIA_STOP -> context.sendIntent(ACTION_FINISH)
               // handle play button
                KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PAUSE -> context.sendIntent(ACTION_PLAY_PAUSE)
                }
            }
        }
    }
}

kotlin extension for send event to media service

fun Context.sendIntent(action: String) {
    Intent(this, MediaPlayerService::class.java).apply {
        this.action = action
        try {
            if (isOreoPlus()) {
                startForegroundService(this)
            } else {
                startService(this)
            }
        } catch (ignored: Exception) {
        }
    }
}

add Receiver in manifest

 <receiver android:name=".player.receivers.MyMediaButtonReceiver" >
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_BUTTON" />
        </intent-filter>
    </receiver>
Erfan Eghterafi
  • 4,344
  • 1
  • 33
  • 44