media: MediaLibraryService throws ForegroundServiceDidNotStartInTimeException

Media3 Version

1.0.0-beta02

Devices that reproduce the issue

Firebase crashlytics report these devices:

  • Galaxy Note20 running Android 12
  • Galaxy S21 FE 5G running Android 12
  • Xiaomi 11T Pro running Android 12
  • Redmi Note 9 Pro running Android 11

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Not tested

Reproduction steps

It’s happened on some devices. I think It has related to MediaController not properly handling the service.

Expected result

not crash?

Actual result

Fatal Exception: android.app.ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{ac0645b u0 player.PlaybackService}
       at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:2147)
       at android.app.ActivityThread.access$2900(ActivityThread.java:310)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2376)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loopOnce(Looper.java:226)
       at android.os.Looper.loop(Looper.java:313)
       at android.app.ActivityThread.main(ActivityThread.java:8669)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)

Media

Bug Report

  • You will email the zip file produced by adb bugreport to dev.exoplayer@gmail.com after filing this issue.

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 16
  • Comments: 148 (48 by maintainers)

Commits related to this issue

Most upvoted comments

I’m on version 1.0.2. Seeing more than 100 of these crashes per day on Firebase and also getting user reports about playback randomly stopping when app is in background or phone is locked.

Mostly Samsung devices on android 13 but also seeing it for android 11 and 12.

Is there any plan to fix this?

And could the playback stopping be due to “battery optimization” settings killing the app on some of these devices? If so is there a solution for that?

Thanks everyone for the provided information. @marcelpallares Looks like your app is doing the right thing.

I can repro this with the demo app on a Samsung Galaxy A51.

21:36:45.832 25471-25471 AndroidRuntime      E  FATAL EXCEPTION: main
Process: androidx.media3.demo.session, PID: 25471
android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1b1ee80 u0 androidx.media3.demo.session/.PlaybackService}
	at android.app.ActivityThread.generateForegroundServiceDidNotStartInTimeException(ActivityThread.java:2245)
	at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:2216)
	at android.app.ActivityThread.-$$Nest$mthrowRemoteServiceException(Unknown Source:0)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2508)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loopOnce(Looper.java:226)
	at android.os.Looper.loop(Looper.java:313)
	at android.app.ActivityThread.main(ActivityThread.java:8757)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

Repro steps with session demo app:

  1. User starts playback of a media item.
  2. user pauses playback and removes app from recent tasks.
  3. In onTaskRemoved() the app stops the service with stopSelf() which triggers onDestroy() where resources are released.
  4. System stops the process. All fine.
  5. User opens the notification drawer and selects Media output
  6. System shows a panel appears with device ouput selction and a mini player with a play button for the session demo (see screenshot at the end)
  7. User presses play
  8. Media3 tries playback resumption but app doesn’t have implemented MediaSession.Callback.onPlaybackResumption(). Playback doesn’t start and service doesn’t go to the foreground.
  9. App crashed after a while with stack trace from above.

The interesting part is what shows up on this device when looking at the sysdump after the app was terminated.

# adb shell dumpsys media_session
[...]
Media key listener: null
  Media key listener package: 
  OnMediaKeyEventDispatchedListener: added 0 listener(s)
  OnMediaKeyEventSessionChangedListener: added 3 listener(s)
    from com.sec.android.app.bluetoothagent
    from com.android.systemui
    from com.android.systemui
  Last MediaButtonReceiver: MBR {pi=PendingIntent{21872e3: PendingIntentRecord{5fc44e0 androidx.media3.demo.session startForegroundService}}, componentName=ComponentInfo{androidx.media3.demo.session/androidx.media3.demo.session.PlaybackService}, type=3, pkg=androidx.media3.demo.session}
  High priority mediakey receiver: null
  Volume key long-press receiver: null
  Media button session is null
  Sessions Stack - have 0 sessions:
[...]

While on a Pixel 6 API 33 it shows:

# adb shell dumpsys media_session
[...]
  Media key listener: null
  Media key listener package: 
  OnMediaKeyEventDispatchedListener: added 0 listener(s)
  OnMediaKeyEventSessionChangedListener: added 2 listener(s)
    from com.android.bluetooth
    from com.android.bluetooth
  Last MediaButtonReceiver: null
  Media button session is null
  Sessions Stack - have 4 sessions:

[...]

The latter is the expected output as per these lines in MediaSessionLegacyStub of Media3 that I verified is executed on both devices before the player is released and the app terminates.

Looks like a different behavior than expected after setting the MBR to null. We will investigate some further.

Fixing

The fix is to achieve that the Last MediaButtonReceiver is set to null on all devices and all API levels as intended by Media3. Mdeia3 must make sure this works. We investigate on our end and file a bug and talk to Samsung if required for a solution.

I wouldn’t recommend other fixes suggested above. It may remove the stack traces from the statistics, but the user sees the same play button that doesn’t work.

Besides the unfortunate fact that there is this play button that doesn’t work, this should not affect the proper functioning of your app in other areas. It is also restricted to these devices, although this is certainly an important case to take care of for this important vendor.

Available workaround

The only workaround I know for devices that exhibit this behavior with the current released Media3 version is implementing MediaSession.Callback.onPlaybackResumption() to return at least one playable item. Then playback starts, the service goes into the foreground and the exception is not thrown.

Screenshot of the button from the notification drawer:

image

For fixing crash: If you are using MediaSessionService, try overiding onUpdateNotification and using startInForegroundRequired always true

override fun onUpdateNotification(session: MediaSession, startInForegroundRequired: Boolean) { super.onUpdateNotification(session, true) }

fixes the crash.

I think the reason is that, if you start your media and keep it is pause state and move your app in background and then kill the app. The service will be created but startForegroundService() will not be called until you play the media or it will remove your service from foreground state but will not remove the notification. I tried the same on Sample Media Session app, and issue was reproducible with the same steps.

startForegroundService makes an implicit promise that the Service will call startForeground(int, android.app.Notification) once it begins running otherwise this ANR will be triggered.

On reading the internal implementation, using this flag true here works because:

Screenshot 2023-08-08 at 1 52 31 AM

Yours media controller is set and and playback state might be ready, but video is not playing so getPlayWhenReady() will be false. Hence, this will remove your service from foreground state but will not remove notification if you don’t send startInForegroundRequired to true.

Screenshot 2023-08-08 at 1 55 17 AM

However, I think media3 should provide a proper fix for this case. Let’s wait for @marcbaechinger 's inputs on this.

We should do something about this or at least state in the docs about recommended way of handling the issue.

As currently it leads to dozens of crashes out of blue once an app hits production and devs have no way of expecting this behavior if they are not thinking about rare cases such as this.

Run adb shell am kill androidx.media3.demo.session in your terminal

Well, this is the way we can emulate how system kills the process due to some period of inactivity, nobody really expects users to do that.

@yebonkim @s6joui @ziem

I tried solution of @agnihotriayush . It worked good. I’m testing more. Thanks @agnihotriayush

override fun onUpdateNotification(session: MediaSession, startInForegroundRequired: Boolean) {
        super.onUpdateNotification(session, true)
    }

@marcbaechinger Hi I want to apply this commit to my application. but it seems version 1.2.0-ahpha1 and 1.1.1 does not include this commit. So can you create a new version? Thank you

Seeing the same issue here. 100% Android 13, and mostly Samsung devices.

image

@jackyhieu1211 Thanks for your report and the evidence of problems on some devices. Very useful.

According to this doc, a notification of a foreground service that is having a foregroundServiceType of mediaPlayback is always posted immediately. So setting the foreground service behaviour on the notification seems not to be required.

An app needs to set this in it’s manifest, Media3 can’t do this for an app. The session demo app has set it here.

However, according to your report this is not sufficent on some devices. We will add setting the foreground service behaviour accordingly in DefaultMediaNotificationProvider. This shouldn’t harm in any case and if we can do this for users on the library side we should. The change will land on the main branch soon.

Out of curiousity: Did you set the foreground service type to mediaPlayback in the manifest when you’ve seen this problem? And did you actually use DefaultMediaNotifciationProvider?

@yebonkim @s6joui @ziem

I tried solution of @agnihotriayush . It worked good. I’m testing more. Thanks @agnihotriayush

override fun onUpdateNotification(session: MediaSession, startInForegroundRequired: Boolean) {
        super.onUpdateNotification(session, true)
    }

After release. This solution reduces crash rate ~10%

Thanks for reporting and precise repro steps including API version! I can repro this with an emulator API 30/Android.

For context, API 30/Android 11 introduced media controls including playback resumption with a notification provided by System UI. See sections Media controls and Playback Resumption of this blog post for the legacy API. This applies if your service is exported with action android.media.browse.MediaBrowserService in the app manifest for backward compatibility.

Looks like this is an API 30 specific misbehavior that the PlaybackService of the demo app does not properly handle the attempt of System UI to provide a playback resumption notification. The ‘stale notification’ is actually not a notification from the demo app or the Media3 library, but a notification provided by System UI.

After adding some additional log statements I can see that System UI attempts with package com.android.systemui to initiate playback resumption:

04-23 18:03:27.970  4532  4532 I ExoPlayerImpl: Init ceb9f18 [AndroidXMedia3/1.0.1] [generic_x86, Android SDK built for x86, unknown, 30]
04-23 18:03:28.171  4532  4532 D ghi167  : onConnect: androidx.media3.demo.session
04-23 18:03:28.215  4532  4532 D ghi167  : onConnect: com.android.systemui
04-23 18:03:28.216  4532  4532 D ghi167  : onGetLibraryRoot: com.android.systemui with isRecent true
04-23 18:03:28.230  4532  4532 D ghi167  : onConnect: com.android.systemui
04-23 18:05:07.261  4742  4742 I ExoPlayerImpl: Init 44a157f [AndroidXMedia3/1.0.1] [generic_x86, Android SDK built for x86, unknown, 30]
04-23 18:05:08.289  4742  4742 D ghi167  : onConnect: androidx.media3.demo.session
04-23 18:05:08.305  4742  4742 D ghi167  : onConnect: androidx.media3.demo.session
04-23 18:05:08.312  4742  4742 D ghi167  : onGetLibraryRoot: androidx.media3.demo.session with isRecent null
04-23 18:05:54.844  4742  4742 D ghi167  : onConnect: androidx.media3.demo.session
04-23 18:05:54.851  4742  4742 D ghi167  : onGetItem: androidx.media3.demo.session with mediaId [artist]Kai Engel
04-23 18:05:57.786  4742  4742 D ghi167  : onConnect: androidx.media3.demo.session
04-23 18:05:57.881  4742  4742 D ghi167  : onConnect: com.android.systemui
04-23 18:05:57.882  4742  4742 D ghi167  : onGetLibraryRoot: com.android.systemui with isRecent true
04-23 18:05:57.902  4742  4742 D ghi167  : onConnect: com.android.systemui
04-23 18:05:57.908  4742  4742 D ghi167  : onGetLibraryRoot: com.android.systemui with isRecent true
04-23 18:06:02.443  4742  4742 I ExoPlayerImpl: Release 44a157f [AndroidXMedia3/1.0.1] [generic_x86, Android SDK built for x86, unknown, 30] [media3.common, media3.exoplayer, media3.decoder, media3.session, media3.datasource, media3.ui, media3.extractor]

// app terminated after step 5

04-23 18:06:13.500  4877  4877 I ExoPlayerImpl: Init ceb9f18 [AndroidXMedia3/1.0.1] [generic_x86, Android SDK built for x86, unknown, 30]
04-23 18:06:13.709  4877  4877 D ghi167  : onConnect: androidx.media3.demo.session
04-23 18:06:13.743  4877  4877 D ghi167  : onConnect: com.android.systemui
04-23 18:06:13.744  4877  4877 D ghi167  : onGetLibraryRoot: com.android.systemui with isRecent true
04-23 18:06:13.764  4877  4877 D ghi167  : onConnect: com.android.systemui
04-23 18:06:13.769  4877  4877 D ghi167  : receive play command from com.android.systemui
04-23 18:06:13.777  4877  4877 D ghi167  : receive play command from com.android.systemui

We can verify a few things (sorry for oversharing, it may be helpful for readers).

The log shows that the PlaybackService has been accessed by system UI to get ‘recently played information’. It also shows that after the click on the play button of the ‘stale notification’, the system attempts a restart by creating a MediaBrowserCompat connecting against our service.

adb -s emulator-5560 shell dumpsys notification | grep -e "NotificationRecord" before and after step 5 of the repro steps to see whether the notification of the demo app package got removed.

adb -s emulator-5560 shell dumpsys media_session before and after step 5 shows that the session was released and media button receiver intent have been cleared.

Handling System UI playback resumption

To avoid problems with the observed attempts of System UI, the demo app and other apps needs to properly handle playback resumption attempts from System UI.

Currently, the session demo app does not support playback resumption and should not accept System UI’s attempts to access the PlaybackService. There are various ways to do that, some that work for me with the session demo are:

  1. Apps that do not allow Android Auto or other apps to access their MediaLibraryService can set export="false" in the service in the manifest. The service is then not visible to SystemUI and it won’t even try to connect:
<service
        android:name=".PlaybackService"
        android:foregroundServiceType="mediaPlayback"
        android:exported="false">
      <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
      </intent-filter>
    </service>
  1. Remove action android.media.browse.MediaBrowserService from service in manifest if you do not want backwards compatibility with MediaBrowserServiceCompat (for apps that only need to support Media3 controllers).

Alternatively, apps can fully implement playback resumption as documented for the legacy API or implemented by UAMP in the Media3 branch like here and here. This includes implementing resuming playback when the service is started as described fro 1.0.0 in the comment above.

We are currently working on supporting playback resumption with BT headsets and System UI notification with library support and the session demo app for 1.1.0. and documenting this on DAC.

I’m seeing this issue, and have never declared. <action android:name="android.intent.action.MEDIA_BUTTON" />

Feels like there are multiple aspects to this issue, this closes the door on one of them.

Thanks for the explanation. I don’t know the details why the framework decides to not call any lifecycle method when this flag is turned on. I don’t find this a very useful behavior to be honest. Said this, I’m unclear about the reasons, so I may be just me being ignorant and uneducated.

The framework, btw, also doesn’t call onDestroy() in such a case. This means that an app (or the library) can’t really properly clean up the service when this flag is set.

I will investigate some further but as it looks like, the recommendation for now is to just not set this attribute to true. I’ll make sure this is documented accordingly.

We plan to provide a default implementation of MediaSessionService.onTaskRemove(). Then the app doesn’t need to override onTaskRemoved(), if the default implementation provides the desired behavior. Still the stopWithTask attribute can’t be used.

I have to stop the MediaSessionService when the app is swiped out of the recent apps.

The implementation of onTaskRemoved() to achieve this is as follows:

@Override
public void onTaskRemoved() {
  mediaSession.getPlayer().pause();
  stopSelf();
}

You need to make sure the player is paused, because if playing, the service is in the foreground in which case calling stopSelf() is a no-op. The above snippet assumes you have implemented onDestroy() to release session and player. onDestroy() will be called when the service is stop self’d.

@Override
public void onDestroy() {
  mediaSession.getPlayer().release();
  mediaSession.release();
  super.onDestroy();
}

@jackyhieu1211 Thank you for your information. I will release new version with that solution and share the result here

I use PlaybackService same as demo.

91% crashes on Samsung device 99%~100% crashes on android 12 and android 13

This is firebase crashlytics

Screen Shot 2023-07-09 at 12 19 58 Screen Shot 2023-07-09 at 12 23 49

I have reproduced the crash on my Samsung device

I have tried doing the debug as below and I am not getting any more crashes. You can try

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            val foregroundServiceBehavior = Notification.FOREGROUND_SERVICE_IMMEDIATE
            notificationBuilder.foregroundServiceBehavior = foregroundServiceBehavior
        }

Full my code

 val notificationBuilder = NotificationCompat.Builder(context, channelId)
            .setSmallIcon(R.mipmap.icon_player_notification)
            .setColor(ContextCompat.getColor(context, R.color.colorV2BlackFilter))
            .setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE)
            .setContentTitle(title)
            .setContentText(HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_LEGACY))
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setDefaults(Notification.DEFAULT_ALL)
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setContentIntent(pendingIntent)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            val foregroundServiceBehavior = Notification.FOREGROUND_SERVICE_IMMEDIATE
            notificationBuilder.foregroundServiceBehavior = foregroundServiceBehavior
        }
        val notificationManager = context.getSystemService(NotificationManager::class.java)
        val channel = NotificationChannel(
            channelId, title, NotificationManager.IMPORTANCE_HIGH
        )
        notificationManager?.createNotificationChannel(channel)
        notificationManager?.notify(notificationId, notificationBuilder.build())

I will release on my app soon. I will update the latest information later Hope it’s useful

@s6joui I’m on 1.1.0-beta01 and I have a few crashes due to this. This is a background crash, the user interacts with the notification but the application can’t start playback, and the crash happens. In this comment from one of contributors there is a solution for playback resumption which helped me on some devices. But I discovered that for some Samsung devices, We have to use WakeLock to wake the device to playback actually starts playing. I don’t have Samsung devices with Android 13, and 99% of crashes happen on that devices and most of them are 5G. So I couldn’t test and fix this for my app.

And Yes, Battery Optimization is the reason to kill your app. You have to check with PowerManager.isIgnoringBatteryOptimizations(packageName) and if not, show a dialog and ask the user to disable Battery Optimization for your app.

Not sure if someone mentioned it before, but here are the steps to reproduce ForegroundServiceDidNotStartInTimeException/ ANR: Context.startForegroundService() did not then call Service.startForeground()… in the demo-session app:

  1. Open demo-session app
  2. Click “Artist folder” then “The Kyoto Collection”
  3. Press the play button
  4. Press the pause button
  5. Hit the home button
  6. Run adb shell am kill androidx.media3.demo.session in your terminal
  7. Press play on the media notification
  8. Wait
  9. 💥

Thanks for the logs.

I’m not sure whether changing the media button intent to use startService is the right approach, because in the play case we actually want to start the service in the foreground. We would then run into another exception in such a case.

For your reported case documented by the logs above I think the service has been crashed by the system:

2023-01-16 12:37:01.563  1529-4408  ActivityManager         pid-1529                             W  Scheduling restart of crashed service androidx.media3.demo.session/.PlaybackService in 1000ms for start-requested

This happens because the service wasn’t terminated when the user has removed the app from the list of tasks (aka. the user has killed the app). If the user does this while the service is not playing (aka the service is not in the foreground), the service needs to be properly terminated which you can do in onTaskRemoved.

I think the log line from above tells us that the user has removed the app from the list of recent apps and the ActivityManager crashes the service and restarts the service. I think then your service re-creates the session which puts the MediaButtonReceiver back in place. When the user then uses the media button on the BT headset, the session tries to start playback. The session exists but the player is still in STATE_IDLE, meaning it has an empty playlist and is not yet prepared. I conclude this from the logs at the moment when the BT command arrives that reports the session state:

2023-01-16 12:37:30.194 18892-18892 AudioMediaPlayerWrapper pid-18892                            V  onPlaybackStateChanged(): androidx.media3.demo.session : PlaybackState {state=1, position=0, buffered position=0, speed=0.0, updated=77569394, actions=3669967, custom actions=[Action:mName='Désactiver le mode aléatoire, mIcon=2131230853, mExtras=Bundle[EMPTY_PARCEL]], active item id=0, error=null}

state=1 => STOPPED, speed=0.0 => paused

So, the BT event now arrives and tries to start playback. If this would work, then the service is put in the foreground and we are all happy. However, the player is in state STATE_IDLE and can’t start playback and accordingly the service is not put to foreground and produces the ForegroundServiceDidNotStartInTimeException that you report. I would expect this being a problem not only on Android 13 but since Android 8.

So unfortunately the answer to this report is that it’s a bit more complicated and a bit more work for apps that want to support resuming playback after the app has been terminated by the user. The reasons is not that Media3 folks wants it like that, the reason is that Media3 needs to follow the platform restrictions to not make problems for apps. Full playback resumption support includes these three things:

  1. When the user terminates the app while playback being paused (while the service is not in Foreground), the service needs to be terminated.

If you see this line

2023-01-16 12:37:01.563  1529-4408  ActivityManager         pid-1529                             W  Scheduling restart of crashed service androidx.media3.demo.session/.PlaybackService in 1000ms for start-requested

something isn’t well.

  1. An app that wants to support resuming playback by BT headset media button after termination, must use a MediaButtonReceiver to enable playback resumption.
  2. An app that want to support resuming playback from the notification after termination, must implement playback resumption with a MediaLibraryService/MediaBrowserService.

Summarizing this investigation I don’t think there is a change in the service (like moving to an Intent from getService()) that would help. The current implementation works including playback resumption as explained above and there is no change that could make things easier for developers without running into exceptions that come from the restrictions around foreground services.

If there is a chance to make life easier for developers we are very happy to do that but as of now I don’t know of such a case. Please tell me if you think differently.

***** EDIT ***** I found the reason. when you play a song and then pause it and close the app the notification remains If you click on play, after a few seconds it will crash. service is not foreground but notification is not dismissed and the problem is when pressing the play button it will crash


Hi, I reproduce the crash with media session demo app here is the stack trace. I don’t know if It’s the exact same, but in bottom of crash it contains the ForegroundServiceDidNotStartInTimeException exception. It happens in the background playing when pressing play/pause a few times On both the Lock screen and unlocked. Also, the crash is insanely reporting in crashlytics for me!

2022-09-23 11:59:43.721 7087-7087/androidx.media3.demo.session E/AbstractFuture: RuntimeException while executing runnable androidx.media3.demo.session.MainActivity$$ExternalSyntheticLambda2@51ce0f3 with executor MoreExecutors.directExecutor()
    java.util.concurrent.CancellationException: Task was cancelled.
        at com.google.common.util.concurrent.AbstractFuture.cancellationExceptionWithCause(AbstractFuture.java:1496)
        at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:586)
        at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:547)
        at androidx.media3.demo.session.MainActivity.getBrowser(MainActivity.kt:42)
        at androidx.media3.demo.session.MainActivity.pushRoot(MainActivity.kt:160)
        at androidx.media3.demo.session.MainActivity.initializeBrowser$lambda-3(MainActivity.kt:108)
        at androidx.media3.demo.session.MainActivity.$r8$lambda$zcr2almQqmFxiCBoT1PwekCQLVg(Unknown Source:0)
        at androidx.media3.demo.session.MainActivity$$ExternalSyntheticLambda2.run(Unknown Source:2)
        at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:31)
        at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1277)
        at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:1038)
        at com.google.common.util.concurrent.AbstractFuture.cancel(AbstractFuture.java:665)
        at androidx.media3.session.MediaController.releaseFuture(MediaController.java:479)
        at androidx.media3.demo.session.MainActivity.releaseBrowser(MainActivity.kt:112)
        at androidx.media3.demo.session.MainActivity.onStop(MainActivity.kt:97)
        at android.app.Instrumentation.callActivityOnStop(Instrumentation.java:1496)
        at android.app.Activity.performStop(Activity.java:8502)
        at android.app.ActivityThread.callActivityOnStop(ActivityThread.java:5298)
        at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:5278)
        at android.app.ActivityThread.handleStopActivity(ActivityThread.java:5343)
        at android.app.servertransaction.StopActivityItem.execute(StopActivityItem.java:43)
        at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2282)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:210)
        at android.os.Looper.loop(Looper.java:299)
        at android.app.ActivityThread.main(ActivityThread.java:8250)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:556)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1045)
2022-09-23 11:59:43.800 7087-7087/androidx.media3.demo.session E/AbstractFuture: RuntimeException while executing runnable androidx.media3.demo.session.PlayerActivity$$ExternalSyntheticLambda3@82c155b with executor MoreExecutors.directExecutor()
    java.util.concurrent.CancellationException: Task was cancelled.
        at com.google.common.util.concurrent.AbstractFuture.cancellationExceptionWithCause(AbstractFuture.java:1496)
        at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:586)
        at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:547)
        at androidx.media3.demo.session.PlayerActivity.getController(PlayerActivity.kt:44)
        at androidx.media3.demo.session.PlayerActivity.setController(PlayerActivity.kt:128)
        at androidx.media3.demo.session.PlayerActivity.initializeController$lambda-4(PlayerActivity.kt:120)
        at androidx.media3.demo.session.PlayerActivity.$r8$lambda$_NhsfsieB1wweFfLvjToeliIJGM(Unknown Source:0)
        at androidx.media3.demo.session.PlayerActivity$$ExternalSyntheticLambda3.run(Unknown Source:2)
        at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:31)
        at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1277)
        at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:1038)
        at com.google.common.util.concurrent.AbstractFuture.cancel(AbstractFuture.java:665)
        at androidx.media3.session.MediaController.releaseFuture(MediaController.java:479)
        at androidx.media3.demo.session.PlayerActivity.releaseController(PlayerActivity.kt:124)
        at androidx.media3.demo.session.PlayerActivity.onStop(PlayerActivity.kt:102)
        at android.app.Instrumentation.callActivityOnStop(Instrumentation.java:1496)
        at android.app.Activity.performStop(Activity.java:8502)
        at android.app.ActivityThread.callActivityOnStop(ActivityThread.java:5298)
        at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:5278)
        at android.app.ActivityThread.handleStopActivity(ActivityThread.java:5343)
        at android.app.servertransaction.StopActivityItem.execute(StopActivityItem.java:43)
        at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2282)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:210)
        at android.os.Looper.loop(Looper.java:299)
        at android.app.ActivityThread.main(ActivityThread.java:8250)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:556)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1045)
2022-09-23 11:59:48.639 8393-8393/androidx.media3.demo.session E/AndroidRuntime: FATAL EXCEPTION: main
    Process: androidx.media3.demo.session, PID: 8393
    android.app.ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{73168c0 u0 androidx.media3.demo.session/.PlaybackService}
        at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:1995)
        at android.app.ActivityThread.access$2800(ActivityThread.java:271)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2220)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:210)
        at android.os.Looper.loop(Looper.java:299)
        at android.app.ActivityThread.main(ActivityThread.java:8250)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:556)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1045)

I’m using MediaSessionService in my app (not really the same with this issue discussion but I think it’s the same issue, sorry about that) and I tried to implement onPlaybackResumption after reading the whole discussion thread. But I still able to reproduce the issue by these steps.

  1. open the app and play some music
  2. pause and clear the app from recent
  3. click play button from media notification in system panel
  4. got the crash android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()

I thought that supporting onPlaybackResumption would prevent the crash but it didn’t. Any idea what I should be doing nex? Because this issue is the top one in crashlytics. Any help would be appreciated.

@marcbaechinger Hi

My AndroidManifest

<service
          android:name=".service.PlaybackService"
          android:exported="true"
          android:foregroundServiceType="mediaPlayback">
          <intent-filter>
              <action android:name="androidx.media3.session.MediaSessionService" />
              <action android:name="android.media.browse.MediaBrowserService" />
              <action android:name="android.intent.action.MEDIA_BUTTON" />
          </intent-filter>
      </service>

Fatal Exception: android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{a34774e u0 com.music.video.player/com.service.PlaybackService}

Screenshot 2023-09-20 at 09 29 37

I have to stop the MediaSessionService when the app is swiped out of the recent apps.

stopWithTask= true was the first thing I tried but this didn’t remove the notification / stop the service when the playback was paused.

Calling stopSelf() inside onTaskRemoved seems to work but onTaskRemoved is only called if I remove stopWithTask= true from the Manifest first.

Could you maybe explain why there is a difference in the first place? I thought I would only have to use onTaskRemoved if I had to execute more code than just stopSelf(), and for just stopping the service stopWithTask is the better choice.

It’s unrelated to the Jetpack Media library, but one of possible reasons for lots of crashes with “Context.startForegroundService() did not then call Service.startForeground()” on Samsung devices is their battery saving options. I could reproduce such crashes by opening my app in the system settings, selecting “Battery” and unchecking “Allow background activity”. After that, requesting the service to start and immediately folding the app makes this crash happen.

@marcbaechinger

This is the stack trace (we shared it a few months ago and have been following this issue since hoping for a solution).

Fatal Exception: android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{6b77eb u0 com.reveri.reverihealth/com.reveri.core.player.service.PlayerService}
       at android.app.ActivityThread.generateForegroundServiceDidNotStartInTimeException(ActivityThread.java:2245)
       at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:2216)
       at android.app.ActivityThread.-$$Nest$mthrowRemoteServiceException()
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2508)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loopOnce(Looper.java:226)
       at android.os.Looper.loop(Looper.java:313)
       at android.app.ActivityThread.main(ActivityThread.java:8757)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

As with everyone else, most of the devices reported are Samsung (96%) and Android 13/14 only.

Exactly the same issue for me. Android 13, mostly Samsung.

image image

Yes, indeed. Option 2 was rubbish. Sorry. I have removed it from the comment above. The other two options work for me though.

I’ll investigate how to tell System UI not to do a notification on all API levels with the isRecent contract. Will update here when I have news.

In #299 the question was raised how an app can start playback when started by a BT headset play command with a media button receiver in place. In such a case the service is started with an Intent with action ACTION_MEDIA_BUTTON and an app needs to start playback to not run into a ForegroundServiceDidNotStartInTimeException.

There is currently no API for this. We are planning to add a media button receiver to Media3. When we do this we will probably add some API that makes the process easier. This is not a preview of that API, but simply a solution how apps can start playback after being started with a pending intent with action ACTION_MEDIA_BUTTON from a BT headset to avoid the ForegroundServiceDidNotStartInTimeException.

What worked for me in the session demo is the following. Roughly this includes:

  1. When the service is destroyed, the app needs to store the currently played item and the category that is playing (and probably the current playing position). It’s up to the app what to store and how this is done. My sample code uses simple SharedPreferences to store and restore a basic media item.
  2. The app overrides onStartCommand and detects when the play key event is sent by the BT headset and the player has an empty playlist.
  3. In this case the app adds the last recent media to the player and returns the result of super.onStartCommand. The library will then prepare the player and call play with the first item. When this play command succeeds, the service is started in the foreground that prevents the ForegroundServiceDidNotStartInTimeException exception when started from BT headsets.
  4. Then the app can, optionally, start an async load all the items that were in the last playlist. In the code below for the demo session, the album of the lat played item is loaded. Once loaded, the items of the last playlist are added to the player (the currently playing item is untouched to not interrupt playback).

The code below shows how I implemented this as a prototype with the PlaybackService of the session demo app. This is not a recommendation of the best way to do it, but a proof of concept that worked well for me when I tested with the session demo app. If you experience the ForegroundServiceDidNotStartInTimeException when started from BT headsets, this can be a way to avoid that.

This is a minimal sample that show the idea only. You may want to persist more data like the category, the current position and the like. You also want to sanitize (eg. null-checks when restoring data) the code better than this example 😃

  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    if (isMediaButtonPlayEvent(intent) && player.mediaItemCount == 0) {
      val mediaItem = restoreLastRecentPlayedItem()
      // Set a media item here to not fall into ENDED state when the player is prepared with an empty playlist
      player.setMediaItem(mediaItem)
      scope.launch {
        // load category async
        val mediaItems = loadCategory("[artist]${mediaItem.mediaMetadata.artist}")
        // add all items of the category except the playing one is untouched to not interrupt playback
        var currentIndex = 0
        for (item in mediaItems!!) {
          if (item.mediaId == player.currentMediaItem?.mediaId) {
            break
          }
          currentIndex++
        }
        if (currentIndex > 0) {
          player.addMediaItems(0, mediaItems.subList(0, currentIndex))
        }
        if (currentIndex < mediaItems.size - 1) {
          player.addMediaItems(
            currentIndex + 1,
            mediaItems.subList(currentIndex + 1, mediaItems.size)
          )
        }
      }
    }
    return super.onStartCommand(intent, flags, startId)
  }

  private fun isMediaButtonPlayEvent(intent: Intent?): Boolean {
    intent ?: return false
    if (intent.action == Intent.ACTION_MEDIA_BUTTON) {
      val keyEvent = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
      if (
        keyEvent!!.keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
          keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_PLAY
      ) {
        return true
      }
    }
    return false
  }

  private fun saveLastRecentPlayedItem(mediaItem: MediaItem?) {
    mediaItem ?: return
    val preferences = getSharedPreferences(RECENT_ITEM_SHARED_PREF_NAME, Context.MODE_PRIVATE)
    val editor = preferences.edit()
    editor.putString(RECENT_ITEM_SHARED_PREF_KEY_MEDIA_ID, mediaItem.mediaId)
    editor.putString(RECENT_ITEM_SHARED_PREF_KEY_URI, mediaItem.localConfiguration!!.uri.toString())
    editor.putString(RECENT_ITEM_SHARED_PREF_KEY_TITLE, mediaItem.mediaMetadata.title.toString())
    editor.putString(RECENT_ITEM_SHARED_PREF_KEY_ARTIST, mediaItem.mediaMetadata.artist.toString())
    editor.putString(
      RECENT_ITEM_SHARED_PREF_KEY_ARTWORK_URI,
      mediaItem.mediaMetadata.artworkUri.toString()
    )
    editor.apply()
  }

  private fun restoreLastRecentPlayedItem(): MediaItem {
    val preferences = getSharedPreferences(RECENT_ITEM_SHARED_PREF_NAME, Context.MODE_PRIVATE)
    val mediaUri = Uri.parse(preferences.getString(RECENT_ITEM_SHARED_PREF_KEY_URI, ""))
    val artworkUri = Uri.parse(preferences.getString(RECENT_ITEM_SHARED_PREF_KEY_ARTWORK_URI, ""))
    val mediaId =
      preferences.getString(RECENT_ITEM_SHARED_PREF_KEY_MEDIA_ID, UUID.randomUUID().toString())
    val mediaMetadata =
      MediaMetadata.Builder()
        .setTitle(preferences.getString(RECENT_ITEM_SHARED_PREF_KEY_TITLE, ""))
        .setArtist(preferences.getString(RECENT_ITEM_SHARED_PREF_KEY_ARTIST, ""))
        .setArtworkUri(artworkUri)
        .build()
    return MediaItem.Builder()
      .setUri(mediaUri)
      .setMediaId(mediaId!!)
      .setMediaMetadata(mediaMetadata)
      .build()
  }

  private suspend fun loadCategory(parentId: String): List<MediaItem>? {
    return withContext(Dispatchers.IO) {
      Thread.sleep(1_000L)
      MediaItemTree.getChildren(parentId)
    }
  }

@AndrazP @tfighieraFreebox

Is you app using a androidx.media.session.MediaButtonReceiver? If your app is having a media button receiver, then the system may start your service and then sends an Intent with action android.intent.action.MEDIA_BUTTON.

I’m asking because in such a case the service is allowed to be started as a foreground service (ContextCompat.startForegroundService(...)) as per exemption. Your service then needs to handle that intent and immediately start playback to make the MediaNotificationManager start in the foreground.

This is a potential source for a ForegroundServiceDidNotStartInTimeException that I was just able to repro. Just adding this as for your information and investigation.

@yschimke This issue is about a ForegroundServiceDidNotStartInTimeException of the MediaSessionService. My understanding/hypothesis is that this is related to the first command arriving in onStartCommand as a pending intent when the service is not yet running (onCreate called immediately before onStartCommand). My understanding is based on a possibly faulty code path that I found and described above and that the exception is observed on Android 12 and 11 where the notification still sends pending intents (as opposed to Android 13). Where do you see the risk of a race condition with another intent or media session command in such a case when the service is not yet started?

By design, the service is started by creating a controller/browser and binding to the service. The service shouldn’t be started by a pending intent (not Context.startForegroundService(intent)) that are only coming from the notification - which needs a running service.

For this reason, I agree that this is about a race condition or an invalid state but elsewhere (stale notification?). I couldn’t repro, because I do not understand from where a pending intent for PAUSE arrives when the service is not running. When the service is stopped, the notification is removed and there shouldn’t be a way to send a PAUSE intent in this case. If such an intent is sent by an app, the app is just not using the service correctly I think. Not saying this is the case, as I have no evidence that this is an app problem.

I agree that I’m not sure that this fixes the problem, but I think it removes the ForegroundServiceDidNotStartInTimeException (admittedly to the expense of another Exception that we hopefully better understand). Because in such a case of a PAUSE command when the service is not yet started, we probably now are at risk of a ForegroundStartNotAllowedException.

If you have any way to repro this ForegroundServiceDidNotStartInTimeException please let me know. I’m thankful for any input that makes me understand better how this exception is produced.

Hello, thank you for your quick answer. I looked into the demo session automotive app and I figured out that the MediaSessionServiceListener overrides onForegroundServiceStartNotAllowedException as follows:

    override fun onForegroundServiceStartNotAllowedException() {
      if (
        Build.VERSION.SDK_INT >= 33 &&
          checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) !=
            PackageManager.PERMISSION_GRANTED
      ) {
        // Notification permission is required but not granted
        return
      }
      val notificationManagerCompat = NotificationManagerCompat.from(this@DemoPlaybackService)
      ensureNotificationChannel(notificationManagerCompat)
      val builder =
        NotificationCompat.Builder(this@DemoPlaybackService, CHANNEL_ID)
          .setSmallIcon(R.drawable.media3_notification_small_icon)
          .setContentTitle(getString(R.string.notification_content_title))
          .setStyle(
            NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_content_text))
          )
          .setPriority(NotificationCompat.PRIORITY_DEFAULT)
          .setAutoCancel(true)
          .also { builder -> getBackStackedActivity()?.let { builder.setContentIntent(it) } }
      notificationManagerCompat.notify(NOTIFICATION_ID, builder.build())
    }

Can you please explain why you override this exception and why you create a notification inside?

FYI: I tried to reproduce the issue in the demo automotive application and I was unable to reproduce it.

Thank you!

Hello,

I’m facing the same issue when I start a media service from an automotive application. I’m using an automotive emulator API 32. The compile and the target sdk are the same 34. I’m using media3 1.2.0 version.

Steps to reproduce the ForegroundServiceDidNotStartInTimeException:

  1. Launch application from automotive OS
  2. Application launches the home screen then crash android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground()
screenshot_crash_automotive

The startForegroundService is called from onCreate() in application. Is there something that I’m doing wrong?

I remain available if you need more information about this issue.

Thank you!

@myolwin00

  1. click play button from media notification in system panel

In step 3 there should not be a notification. I have done these steps two or three times with the demo app in the last months (probably four times even 😉) and there is no stale notification.

However, your report is not really actionable I’m afraid. Can you confirm these steps work to repro the problem with the demo app? If this is the case can you give me some more details around the API level and what device you are using?

If you can not repro with the demo app, please review whether your app terminates the service in onTaskRemoved in a way that is sensible for your use case (See this comment also).

This is what I got as a support request.

Screenshot_20231011_202252_Device care~2

@marcbaechinger Hi. Thanks for your suggestion. After remove <action android:name="android.intent.action.MEDIA_BUTTON" />

My application has DAU ~60K users

The error has completely disappeared on Android 13. This is wonderful ❤️️❤️️❤️️❤️️

But the error still appears in Android 12 and lower. I will try to reproduce the error and send it to you

Screenshot 2023-09-22 at 09 26 38 Screenshot 2023-09-22 at 09 22 00 Screenshot 2023-09-22 at 09 29 42 Screenshot 2023-09-22 at 09 32 24 Screenshot 2023-09-22 at 09 39 23

@marcbaechinger

Not at all! I’m happy to provide as many insights as I can to get this resolved. Even if needed, happy to schedule a quick call to walk you through the codebase. We have never been able to reproduce the issue ourselves btw.

How is your app designed to stop the service (with stopSelf())? Are you using onTaskRemoved() to stop the service conditionally or unconditionally? If your app decides to stop the service, what APIs methods (Media3 session and service) does your app call and where (for instance in onTaskRemoved() vs. onDestroy())?

We have use stopSelf() to stop the service. We also call player.stop() inside the onTaskRemoved() method. We don’t manually stop the service in any other way at any point, we call controller.stop() using the MediaController when an audio session is finished, and if we need to start another session we set up the new mediaItems and call prepare and play. So the service is always around unless the app is killed. Is this a bad practice?

    /**
     * Takes care of stopping the player and destroying the service [stopSelf] when the app
     * is dismissed from recent apps.
     */
    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        player.stop()
        stopSelf()
    }

We also use onDestory() to release the player and mediaSession.

    override fun onDestroy() {
        player.release()
        mediaSession.release()
        super.onDestroy()
    }

From one of the life-cycle methods onTaskRemoved/onDestroy() do you call any asynchronous methods that return later and stop/destroy the service?

No, we don’t. See above both methods.

Do you override onStartCommand() or use a custom MediaButtonReceiver?

None of them.

@marcbaechinger in my case, both solutions (android:exported=“false” and super.onUpdateNotification(session, true)) did not help.

After looking at the two commits that helped you. One change makes the service always run in the foreground. The other does not export the service anymore.

From this, the crashes could be from external apps that start your service, which makes the service create a session that is returned by MediaSessionService.Calback.onGetSession(controllerInfo) . This then calls onUpdateNotification(session, boolean startInForeground) that you override by always passing true some further.

If my reasoning is correct (which isn’t sure with the available inormation), then the reason for your crashes can be an external app that starts the service but does not call play when starting. This, by design, leads to a ForegroundServiceDidNotStartInTimeException.

Both of these commits prevent this. Not exporting prevents external apps from seeing and starting your service. Always passing true into super.onUpdateNotification(session, true) prevents the service to not be put into the foreground when started with something else than play.

Yes. That’s interesting.

If you don’t export the service, then no other app than your own app sees the service. I’m not sure for users with system-privilege (like System UI), but other external apps would not see the service in the PackageManager nor would they be able to crash your app with a ForegroundServiceDidNotStartInTimeException in such a case. So given the reason for your crashes were external apps, it seems evident that not exporting the service works, the question is if the implications with this are intended and also, why external apps were trying to start your service without you knowing it and giving consent.

“Crash free-users” rate from 90% -> ~98%. How wonderful.

That’s amazing. I’m excited for you as well. If your app has a lower crash-rate by not exporting the service, then is it likely that those users that produced the crash before, where trying to access your service from external apps?

If you don’t have such external users, then I would definitely not export the service. You need to test whether System UI still works though. I would guess the notification works without a problem because the notification only uses the MediaControllerCompat. If you are using the resumption notification of System UI starting with API 30 that is using MediaBrowserCompat, then I would test this thoroughly on all API level when not exporting the service.

If you not export the service, you can probably also remove

<action android:name="android.media.browse.MediaBrowserService" />

because internally I’d guess you have Media3 controllers only.

Unfortunately, we can’t be sure because your fix seems to do several things. This change around not exporting the service though, is either not required (as in not fixing anything), or then the actual reason for the crashes you are seeing were external apps.

@whitipet I have reproduced the problem. If you want to try, follow the instructions here. LINK HERE

@jackyhieu1211 Strangely, this does not match the documentation. Either the documentation is wrong, or some manufacturers have decided to change something. I can’t reproduce this crash on my Pixel 7 Pro android 13, but according to Crashlytics, 99% of users are Samsung. In any case, I will wait for the release with the fix and try it. If it doesn’t work, I’ll add a request for notification permission.

for 2days, although we didn’t release hotfix version fully, Our service don’t have this crash! Thanks @agnihotriayush

For additional information, our product use mediaSession with exoplayer and don’t use mediaController

@marcbaechinger @tianyif We are still waiting for an input on this, to completely fix this issue. Initially I thought that my solution will keep the service in foreground and we will not experience this issue. We still have some users experiencing the issue. What should be the proper fix?

Hi @jackyhieu1211 I have same crash. Did you fix your crash by above code?

Thanks again for reporting @MaximillianLeonov . This is a bug on API 30 where the contract described in [1] is slightly different for rejecting the System UI playback resumption notification.

I have created #355 to track this.

[1] https://developer.android.com/guide/topics/media/media-controls#mediabrowserservice_implementation

I have not checked with the fix solution yet, but I will try it and let you know.

To me this is not really a solution because this is assuming that all apps that have a Browser Service and Library Service. Want apps to resume playback even when, in our case, it’s a news app that also does podcasts.

If the player has never discovered the podcast feature of our app and he would press a BT Button. It doesn’t make sense at all to start playing those.

I’m not sure if i get what you’re saying about ‘registered a receiver’ afaik there is no way of registering a button receiver or deregistering it for that matter. Because if we could do that i would love to do that.

Do you know of a way to remove the registered intent from mediassession that comes up in

adb shell dumpsys media_session

Additionaly this error mainly occurs when targeting 33.

Note: for now we’re investigating reverting the media3 investment because we have way to many users in production that are expierencing these crashes. 5k on a 5% rollout, which is unacceptable for a library that is marked as stable.

I found two way to reproduce the crash. With demo-session from this project (100%) :

  • Play track
  • Pause track
  • Kill app
  • Use a bluetooth command (e.g. play/pause from headphone)
  • The app crashes after around 30 seconds.

Since onTaskRemoved is called but onDestroy is not from PlaybackService, the mediaLibrarySession is not released and mediaButtonIntent is not canceled from MediaSessionImpl.

With uamp, branch media 3 :

  • Open app
  • Kill app (don’t play track, it’s important to not trigger onStartCommand)
  • Use a bluetooth command (e.g. play/pause from headphone)
  • The app crashes after around 30 seconds.

This case is different, onTaskRemoved and onDestroy are not called. Since uamp doesn’t release MediaBrowser in Activity onStop, the service is not destroyed.

A possible solution could be a combination of the two projects. In the demo-session app, you can release the mediaLibrarySession in onTaskRemoved, and in uamp, you can release the MediaBrowserService in onStop from the Activity.

However, this solution doesn’t meet my needs and seems quite fragile.

Do you see any restrictions to stop using getForegroundService in MediaSessionImpl ?

I would sugest :

Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, sessionUri);
intent.setComponent(mbrComponent);
mediaButtonIntent = PendingIntent.getService(context, 0, intent, pendingIntentFlagMutable);
broadcastReceiver = null;

Or same approach as before media 3 :

Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, sessionUri);
intent.setComponent(mbrComponent);
mediaButtonIntent = PendingIntent.getBroadcast(context, 0/* requestCode, ignored */, intent, pendingIntentFlagMutable);
broadcastReceiver = null;

@marcbaechinger let me know if you need a more detailed bug report.

same here with beta3 and target sdk 33. Devices are 100% samsung with android 12 and 13

for Android 13 got this stacktrace:

Fatal Exception: android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException
Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{4d9015f u0 com.package.name/.feature.player.service.PlayerService}
android.app.ActivityThread.generateForegroundServiceDidNotStartInTimeException (ActivityThread.java:2242)
android.app.ActivityThread.throwRemoteServiceException (ActivityThread.java:2213)
android.app.ActivityThread.-$$Nest$mthrowRemoteServiceException (ActivityThread.java)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2505)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:226)
android.os.Looper.loop (Looper.java:313)
android.app.ActivityThread.main (ActivityThread.java:8741)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:571)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)

and for android 12:

Fatal Exception: android.app.ForegroundServiceDidNotStartInTimeException
Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{d5a5909 u0 com.package.name/.feature.player.service.PlayerService}
android.app.ActivityThread.throwRemoteServiceException (ActivityThread.java:2147)
android.app.ActivityThread.access$2900 (ActivityThread.java:310)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2376)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:226)
android.os.Looper.loop (Looper.java:313)
android.app.ActivityThread.main (ActivityThread.java:8663)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:567)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1135)

It occurs 100% in background and considered as a repeated crash by firebase

Thanks for digging some further. That give me some further data points.

when you play a song and then pause it and close the app the notification remains.

Yes, indeed, these cases are always about closing the app and having only the service playing in the background. As soon as your app is in the foreground with a controller connected you are bound to the service, and you will never see such an exception.

If you click on play, after a few seconds it will crash.

With the default behaviour of the MediaSessionService (your app does not override updateNotification()), I don’t think that play should produce a ForegroundServiceDidNotStartInTimeException, because after you pressed play, playWhenReady is true and the player is not STATE_IDLE which results in service.startForeground() being called. With play I would only expect a ForegroundServiceStartNotAllowedException in some cases but not when pressing PLAY on a notification on Android 12 (PendingIntent with exception) and Android 13 (MediaSession with exemption).

I still think it’s the PAUSE case for which we will provide a fix.