just_audio icon indicating copy to clipboard operation
just_audio copied to clipboard

Here is why Android playback stops 5 to 7 minutes after a phone call

Open johnmollaghan opened this issue 10 months ago • 19 comments

Which API doesn't behave as documented, and how does it misbehave? just_audio, playback stops around 5 to 7 minutes after a phone call ends. ALSO well done with these audio libraries for Flutter, they are great and thanks for the time and effort spent on them.

Minimal reproduction project

Checking to see if it can be reproduced in the example

To Reproduce (i.e. user steps, not code) Steps to reproduce the behavior:

  • Play some audio, my project uses audio_service and just_audio
  • Make a phone call (the audio will pause)
  • Phone call only needs to be a few seconds
  • Hang up the call
  • Audio will resume playing
  • Wait around 10 minutes (the longest I see on my Samsung A52 is 7 to 8 minutes)
  • Audio stops and app crashes
  • As you will see below, the issue is with ExoPlayerImplInternal firing an UnknownHostException
  • In my full logs, I see that the services keeps on playing for a minute or so and then gets killed off.
  • From looking at the latest version of just_audio in GitHub, it is using version 1.4.1 of the player from August 2024. The most recent version is 1.5.1 and was release in Devember 2024. This might be a direction to investigate.
  • I can supply full logs if needed.

Error messages

2025-02-19 18:19:27.787  2080-2706  BufferPoolAccessor2.0   com.xx.xx      D  bufferpool2 0xb4000074738e4a58 : 5(40960 size) total buffers - 1(8192 size) used buffers - 16034/16044 (recycle/alloc) - 14/32072 (fetch/transfer)
2025-02-19 18:19:28.994  2080-2683  ExoPlayerImplInternal   com.xx.xx      E  Playback error
                                                                                                      UnknownHostException (no network)
2025-02-19 18:19:29.003  2080-2706  CCodec                  com.xx.xx       I  [c2.sec.mp3.decoder] state->set(FLUSHING)
2025-02-19 18:19:29.005  2080-2706  CCodec                  com.xx.xx       I  [c2.sec.mp3.decoder] state->set(FLUSHED)
2025-02-19 18:19:29.006  2080-2683  MediaCodec              com.xx.xx       D  keep callback message for reclaim
2025-02-19 18:19:29.007  2080-2706  CCodec                  com.xx.xx       I  [c2.sec.mp3.decoder] state->set(RESUMING)
2025-02-19 18:19:29.012  2080-2706  CCodecConfig            com.xx.xx       I  query failed after returning 7 values (BAD_INDEX)
2025-02-19 18:19:29.012  2080-2706  CCodecBufferChannel     com.xx.xx       I  [c2.sec.mp3.decoder#116] 4 initial input buffers available
2025-02-19 18:19:29.013  2080-2706  Codec2Client            com.xx.xx       W  query -- param skipped: index = 1342179345.
2025-02-19 18:19:29.013  2080-2706  Codec2Client            com.xx.xx       W  query -- param skipped: index = 2415921170.
2025-02-19 18:19:29.014  2080-2706  CCodec                  com.xx.xx       I  [c2.sec.mp3.decoder] state->set(RUNNING)
2025-02-19 18:19:29.019  2080-2706  CCodec                  com.xx.xx       I  [c2.sec.mp3.decoder] state->set(RELEASING)
2025-02-19 18:19:29.020  2080-2706  CCodecBufferChannel     com.xx.xx       D  [c2.sec.mp3.decoder#116] MediaCodec discarded an unknown buffer
2025-02-19 18:19:29.020  2080-2706  CCodecBufferChannel     com.xx.xx       D  [c2.sec.mp3.decoder#116] MediaCodec discarded an unknown buffer
2025-02-19 18:19:29.020  2080-2706  CCodecBufferChannel     com.xx.xx       D  [c2.sec.mp3.decoder#116] MediaCodec discarded an unknown buffer
2025-02-19 18:19:29.020  2080-2706  CCodecBufferChannel     com.xx.xx       D  [c2.sec.mp3.decoder#116] MediaCodec discarded an unknown buffer
2025-02-19 18:19:29.028  2080-6677  CCodec                  com.xx.xx       I  [c2.sec.mp3.decoder] state->set(RELEASED)


`

Expected behavior Audio should play without stopping.

Screenshots If applicable, add screenshots to help explain your problem.

Smartphone (please complete the following information):

  • Device: Samsung A52
  • OS: Android 14

Flutter SDK version 3.29.0

[✓] Flutter (Channel stable, 3.29.0, on macOS 15.3 24D60 darwin-arm64, locale en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0-rc4)
[!] Xcode - develop for iOS and macOS (Xcode 16.2)
    ! CocoaPods 1.15.2 out of date (1.16.2 is recommended).
        CocoaPods is a package manager for iOS or macOS platform code.
        Without CocoaPods, plugins will not work on iOS or macOS.
        For more info, see https://flutter.dev/to/platform-plugins
      To update CocoaPods, see https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.1)
[✓] Android Studio (version 2024.2)
[✓] Connected device (5 available)
    ! Error: Browsing on the local area network for Alice's iPhone . Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resources

Additional context Add any other context about the problem here.

johnmollaghan avatar Feb 19 '25 18:02 johnmollaghan

Your "Minimal reproduction project" section is empty.

ryanheise avatar Feb 20 '25 01:02 ryanheise

OK @ryanheise , I have shared my WIP repo there and have given you collaborator access to it.

johnmollaghan avatar Feb 20 '25 08:02 johnmollaghan

Please follow the instructions on the bug report template for completing that section.

ryanheise avatar Feb 20 '25 08:02 ryanheise

No problem, I am trying to load the example but the gradle settings in the Android project are very old, going to have to install an old version of Android Studio. It would be good of the example was brought up to date a bit more from a Gradle point of view.

johnmollaghan avatar Feb 20 '25 09:02 johnmollaghan

An, I should fix that first.

ryanheise avatar Feb 20 '25 09:02 ryanheise

That would be great @ryanheise . Most developers are probably now using Android Studio Ladybug.

johnmollaghan avatar Feb 20 '25 09:02 johnmollaghan

I am happy to help out with any testing etc, I really want to get my Podcast app out there but need to make sure the audio is rock solid. Are there plans to update the library for ExoPlayer in case that is the cause of the issue I am seeing? If it is not a big thing to bump the version, I can happily check if it makes any difference. Thx

johnmollaghan avatar Feb 20 '25 09:02 johnmollaghan

Hi,

I am having the same issue with my Anytime Player on Android when running on Android 12 and above. When I tested on an Android 11 device it worked absolutely fine.

I think the issue here is with the change in Android 12 that disallows starting foreground services when in the background outside of a specific set of exemptions.

When a call comes in, the audio service is pushed into the background. After the call, when the audio resumes, it attempts to restart the foreground service, which results in the following exception in my app:

2025-02-20 11:54:22.420   569-1293  ActivityManager         system_server                        W  Background started FGS: Disallowed [callingPackage: uk.me.amugofjava.anytime; callingUid: 10212; uidState: SVC ; uidBFSL: n/a; intent: Intent { cmp=uk.me.amugofjava.anytime/com.ryanheise.audioservice.AudioService }; code:DENIED; tempAllowListReason:<null>; targetSdkVersion:34; callerTargetSdkVersion:34; startForegroundCount:2; bindFromPackage:null: isBindService:false]
2025-02-20 11:54:22.420   569-1293  ActivityManager         system_server                        W  startForegroundService() not allowed due to mAllowStartForeground false: service uk.me.amugofjava.anytime/com.ryanheise.audioservice.AudioService
2025-02-20 11:54:22.423  5002-5002  System.err              uk.me.amugofjava.anytime             W  android.app.ForegroundServiceStartNotAllowedException: startForegroundService() not allowed due to mAllowStartForeground false: service uk.me.amugofjava.anytime/com.ryanheise.audioservice.AudioService
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel(ForegroundServiceStartNotAllowedException.java:54)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel(ForegroundServiceStartNotAllowedException.java:50)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.os.Parcel.readParcelableInternal(Parcel.java:4870)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.os.Parcel.readParcelable(Parcel.java:4852)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.os.Parcel.createExceptionOrNull(Parcel.java:3052)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.os.Parcel.createException(Parcel.java:3041)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.os.Parcel.readException(Parcel.java:3024)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.os.Parcel.readException(Parcel.java:2966)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.app.IActivityManager$Stub$Proxy.startService(IActivityManager.java:5984)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1931)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.app.ContextImpl.startForegroundService(ContextImpl.java:1906)
2025-02-20 11:54:22.426  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.content.ContextWrapper.startForegroundService(ContextWrapper.java:830)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at androidx.core.content.ContextCompat$Api26Impl.startForegroundService(ContextCompat.java:1128)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at androidx.core.content.ContextCompat.startForegroundService(ContextCompat.java:700)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at com.ryanheise.audioservice.AudioService.enterPlayingState(AudioService.java:717)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at com.ryanheise.audioservice.AudioService.setState(AudioService.java:571)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at com.ryanheise.audioservice.AudioServicePlugin$AudioHandlerInterface.onMethodCall(AudioServicePlugin.java:915)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:267)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:292)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:319)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(Unknown Source:12)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.os.Handler.handleCallback(Handler.java:958)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.os.Handler.dispatchMessage(Handler.java:99)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.os.Looper.loopOnce(Looper.java:205)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.os.Looper.loop(Looper.java:294)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at android.app.ActivityThread.main(ActivityThread.java:8177)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at java.lang.reflect.Method.invoke(Native Method)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
2025-02-20 11:54:22.427  5002-5002  System.err              uk.me.amugofjava.anytime             W  	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

I believe that once the audio cache is exhausted and an attempt is made to fetch more audio, the app crashes.

amugofjava avatar Feb 20 '25 12:02 amugofjava

Thanks for this extra information, hopefully this will help the team here to resolve the issue.

johnmollaghan avatar Feb 20 '25 12:02 johnmollaghan

Actually, reviewing the setup docs again for audio_service there is a callout for Android > 12 where you need to prompt the user to disable battery optimisation. I'm going to try again and see if this fixes it for me. I know battery optimisation on audio apps is a real pain.

amugofjava avatar Feb 20 '25 12:02 amugofjava

I had a look at the permissions for the Spotify app and don't see anything to do with battery.

Also, Spotify is using the same "optimised" setting for battery, this is what would be changed if we ask the user to grant battery permissions I expect, I might be wrong on this.

I am playing something in my own app now (just made my test phone call) after changing the battery setting to "unrestricted" and I can see if this fixes things.

If it keeps on playing beyond 7 minutes (which seems to be the audio buffer you mentioned), then we know it's a battery issue.

johnmollaghan avatar Feb 20 '25 12:02 johnmollaghan

It seems to be working better now, can't see any issue yet 15 minutes after a phone call, the audio is still playing. Do we REALLY have to get our users to give a battery permission? I don't see Spotify asking for this.

johnmollaghan avatar Feb 20 '25 13:02 johnmollaghan

Right, PocketCasts, one of the most popular Podcast apps, asks for battery permissions. Then if you manually turn them to "Optimised", then EVERY TIME you try to play an episode, it pops up a snack bar trying to get you to give "unrestricted" battery to the app.

If one of the biggest apps out there does it, I think it's OK for the rest of us to!

@ryanheise If there is some way around giving battery permissions, it would be great not to have to ask users, but looks like we might have to BUT how does Spotify do it!!!!

OK - PocketCasts didn't seem to have the issue when I turned it to Optimised, but hard to know without seeing PocketCasts code. Inconclusive

johnmollaghan avatar Feb 20 '25 13:02 johnmollaghan

https://support.pocketcasts.com/knowledge-base/playback-pausing-randomly-or-when-screen-is-locked/

This is how they justify turning off battery optimisation.

johnmollaghan avatar Feb 20 '25 13:02 johnmollaghan

That's a useful link @johnmollaghan and a good idea to add that info to our apps supporting pages. There are packages on pub.dev that can handle checking and prompting the user to disable battery optimisation.

amugofjava avatar Feb 20 '25 14:02 amugofjava

@johnmollaghan I'm sorry I didn't realise from your report that this was a background playback issue, so I guess you are using just_audio_background or audio_service to provide the background service where the issue really lies. This is actually a bug in Android 12 which was reportedly fixed last year in Android 13. If things were working correctly, a music or podcast player should be given a pass as it is one of the exemptions (assuming your manifest indicates that). See below:

https://issuetracker.google.com/issues/235172948?pli=1

However, it took Android 2 years to respond with a fix, and in those 2 years, it has unfortunately become standard practice to change the battery optimisation settings for the app.

PocketCasts' code is open source, so you can confirm that this is indeed what they also do. There are other things that go along with this such as declaring the right permissions and the right foreground service type in your manifest as shown in the audio_service README. The audio_service README also suggests a plugin you can use to interact with the battery optimisation settings, but there are more on pub.dev worth considering also.

Ah, I should fix that first.

This might be delayed since I'm in the middle of working on the next major release which deprecates ConcatenatingAudioSource in line with ExoPlayer's decision to replace that with the playlist API. That will also make it easier to stay up to date with the latest ExoPlayer dependency going forward.

@amugofjava

I believe that once the audio cache is exhausted and an attempt is made to fetch more audio, the app crashes.

My understanding is that the foreground service is necessary to allow your app to continue running for a prolonged period of time, and so if the foreground service is unable to start, the OS will kill your app after 5-10 minutes to save battery. This is why you see network errors around that time - the OS is shutting things down to preserve the battery. This also happens to users who use bare-bones just_audio without setting up either just_audio_background or audio_service to manage that necessary foreground service.

ryanheise avatar Feb 20 '25 14:02 ryanheise

This is actually a bug in Android 12 which was reportedly fixed last year in Android 13

Testing on Android 14 Samsung A52, real device and still having the issue!!

I'll just prompt the user to give the permission, seems to fix things.

johnmollaghan avatar Feb 20 '25 14:02 johnmollaghan

(assuming your manifest indicates that).

Also, what do you mean by this? I followed what is in the audio_service readme, so I should be OK?

johnmollaghan avatar Feb 20 '25 14:02 johnmollaghan

I mean basically the manifest options given in the README. I can't vouch for whether the bug is "really" fixed or whether the fix actually appears in all the different OEM variations of Android, since there are actually differences in how battery optimisation is implemented across these different variations. One of the battery optimisation plugins on pub.dev tries to deal with various special cases for certain manufacturers, but I also can't vouch for that:

https://pub.dev/packages/disable_battery_optimization

The only thing I can recommend is the common approach mentioned in the README. Here is Android's official documentation on the matter:

https://developer.android.com/training/monitoring-device-state/doze-standby#support_for_other_use_cases

Note that there are two different intents that are available, one which requires interactive input from the user, and another which can be executed silently by the program. Maybe Spotify has somehow obtained a special exemption to use this, but I have also heard of other people trying to use this and having their app rejected by the Google Play.

ryanheise avatar Feb 20 '25 14:02 ryanheise