Skip to content

Keep audio service foreground while paused to fix background playback (#956) - #1643

Open
mjevange wants to merge 1 commit into
finamp-app:redesignfrom
mjevange:fix/bg-playback-foreground-service
Open

Keep audio service foreground while paused to fix background playback (#956)#1643
mjevange wants to merge 1 commit into
finamp-app:redesignfrom
mjevange:fix/bg-playback-foreground-service

Conversation

@mjevange

Copy link
Copy Markdown

Opening this as a proposal / discussion starter for #956 rather than a definitive fix — happy to adjust or drop it.

Root cause

A transient audio-focus loss while Finamp is backgrounded (a notification, an alarm, a nav prompt, or a few seconds of audio from another app) pauses playback. With androidStopForegroundOnPause = true, the audio service then drops out of the foreground. When focus returns and playback tries to resume, Android 12+ blocks restarting the foreground service from the background:

ActivityManager: Background started FGS: Disallowed ... AudioService; code:DENIED
ActivityManager: startForegroundService() not allowed due to mAllowStartForeground false
ActivityManager: Stopping service due to app idle ... AudioService

Playback then stays dead until the app is foregrounded again (foreground starts are allowed — which is why reopening "fixes" it). This is the part the 0.6.27 API-34 manifest fix didn't cover.

On redesign, toggling Enter Low-Priority State on Pause OFF resolves it (some older 0.6.x reports said the toggle made no difference, which may have steered people away from this lever). Before/after with adb logcat on a Pixel 9 Pro / Android 16 / 0.9.23-beta:

  • ON: onAudioFocusChange(-2) (transient loss) → (+1) (gain) → FGS: Disallowed, playback stays dead.
  • OFF: same -2 → +1 focus cycle, no denial, playback resumes immediately.

This change

Default androidStopForegroundOnPause to false, so the service stays in the foreground across a pause and can resume without a restricted background restart. The toggle is kept for anyone who prefers the low-priority behaviour (swipe-away notification, less battery while paused).

Open questions

  • You set true deliberately. If you'd rather keep that default and instead only stay foreground during transient interruptions, that's a deeper change in the audio_service interaction — open to that direction if you prefer it.
  • This only affects new installs; existing users keep their stored value (the whole FinampSettings object persists the field). A one-time migration could flip it for them (with the toggle to opt back in), but it overrides a deliberate choice once, so I left it out pending your call — happy to add it.
  • I couldn't build an APK locally (no Android SDK on my machine), but the behaviour above was verified on-device by toggling the setting, which sets the same runtime value this default produces.

Thanks for Finamp — curious what you think.

…finamp-app#956)

A brief audio-focus loss while Finamp is in the background (a notification,
an alarm, a nav prompt, or a couple seconds of audio from another app)
pauses playback. With androidStopForegroundOnPause defaulting to true, the
audio service leaves the foreground when that happens, and when focus
returns Android 12+ refuses to let it restart the foreground service from
the background (ForegroundServiceStartNotAllowed). Playback then stays dead
until the app is reopened.

Default the setting to false so the service stays in the foreground across a
pause and can resume without that restricted restart. The toggle is kept for
anyone who wants the old dismissable-notification / lower-battery behaviour.
@Chaphasilor

Copy link
Copy Markdown
Member

@mjevange sorry for the late reply. Yes, the choice was made deliberately, because we received more complaints about problematic battery drain than frustration about Finamp not being able to resume. We originally had the setting set to false in the early beta versions. But even with the setting set to false, battery optimizations can still kill Finamp, so it's not a perfect solution either, at least not by default.

I'm wondering if there's a better way to handle this, and especially how apps like Spotify pull this off. Do they get special treatment from Google?
For the upcoming update, I also submitted a video to Google to showcase how Finamp uses the foreground_media_playback permission, something that's recently become required if apps what to stay active in the background. Maybe this somehow affects the battery saver policy, but I not too hopeful.

@mjevange

mjevange commented Jul 2, 2026

Copy link
Copy Markdown
Author

@Chaphasilor thanks for the detailed reply, that context really helps — and the battery-drain tradeoff makes sense given the complaints you were getting.

I think there are two separate failure modes getting bundled together here, and my change is only aimed at one of them:

1. Battery optimization / Doze killing the process. You're right that flipping this setting doesn't fix that — the OS (especially OEM battery savers) can still kill Finamp regardless, and keeping the notification alive only lowers the odds. That one isn't really solvable at the app level by default, and I'm not claiming to fix it.

2. The foreground-service-start denial after a transient audio-focus loss. This is the one I was actually chasing, and it's deterministic rather than probabilistic. When Finamp is backgrounded and playback pauses because something briefly took audio focus (an alarm, Assistant, a nav prompt, another app's notification chime), audio_service tries to re-promote the foreground service when focus comes back. On Android 12+ that's a foreground-service start from the background, so the system denies it (startForegroundService() not allowed due to mAllowStartForeground false), tears the service down, and playback doesn't come back until you reopen the app. I caught it in logcat: same transient interruption, setting on → FGS: Disallowed and the service dies; setting off → resumes instantly, no denial.

The key point is that case 2 isn't the battery-saver kill — it's the API 31 background FGS-start restriction, which is a separate gate. (Funnily enough, users who fully disable battery optimization for Finamp are exempt from that restriction per Google's exemption list, so they wouldn't hit case 2 at all — but that's not the default, and it's a lot to ask of every user.) For anyone on default settings, the transient-interruption case reliably kills playback, and keeping the service foreground on pause avoids it simply because there's no background restart left to deny.

On the Spotify question — as far as I can tell there's no media exemption to the background FGS-start restriction at all (the list is FCM, exact alarms, tapping a notification, etc., nothing for media sessions or audio focus). So it's not special treatment, it's just never demoting the service in a way that needs a background restart. This is actually an open issue in audio_service itself — ryanheise/audio_service#996, same exact error and scenario, no upstream fix yet.

Given your battery concern, I don't think flipping the default globally is the right answer either — you'd pay the persistent-notification cost on every pause, including when someone just hits pause and walks away. A more targeted version might be: keep the current default, but only hold the service in the foreground when the pause came from a transient interruption we expect to recover from, and still demote on a normal user pause. That gets the resume-after-interruption behavior without the always-on battery cost, and it'd help everyone rather than just people who found the setting. The catch is that audio_service reads stop-foreground-on-pause once at init and applies it to every pause, so doing it cleanly probably means a small change to how that decision gets made (possibly upstream in the plugin).

If you're open to it, I'm happy to take a crack at that as a separate PR so you can judge whether it's worth the complexity. And on the foreground_media_playback permission — my read is that's the API 34 requirement to declare the FGS type, which is a different gate than the API 31 background-start restriction, so I wouldn't expect it to change this specific case, but happy to be wrong there. Either way, thanks for taking the time on this.

@Chaphasilor

Copy link
Copy Markdown
Member

@mjevange thanks for the link to the audio_service issue, I've left a comment there. That thread does mention a new AudioService.asyncError listener that can apparently be used to prevent the app from being crashed by the system, so maybe that would allow reactivating the audio session on user interaction?

We could simply set that up in music_player_background_task.dart, for example in the constructor:

// Handle incoming errors originating in AudioService, which could lead to the app being crashed by the system if unhandled
AudioService.asyncError.listen((error) {
  _audioServiceBackgroundTaskLogger.severe("AudioService error: $error");
});

If this really becomes a more serious issue on newer Android versions, we could also think about changing the default setting again. Maybe we could add a timer on pause events, that will shut down the app if the timer expires before playback is unpaused? That should be able to handle shorter interruptions, but properly kill the app if it's no longer being used. The usage of that timer could of course be configured via a setting (potentially even replacing the existing foreground setting).

Also, unrelated, but what's with the AI-generated comments? Is this for translation, grammar, or something else? I have to say, I'm not a fan of it :)

@Chaphasilor

Copy link
Copy Markdown
Member

@mjevange so what are your thoughts here? are you still working on this?

@mjevange

Copy link
Copy Markdown
Author

Just replying to say I haven't forgotten about this. I've been live testing a few debug builds this week to work out some ideas.

Will report back soon as I'm satisfied with testing.

@mjevange

Copy link
Copy Markdown
Author

So I did some testing with the timer idea and made some changes in audio_service and used an alarm to test the interruption, seems to work well.

Instead of leaving foreground when playback pauses, it waits ~60s and cancels the waiting if playback resumes.
That way a short interruption never drops from foreground (no background startForegroundService() on resume), but real pause still demotes after delay, so battery behavior stays basically the same.

A/B tested on my Pixel (Android 16) with logcat, low-priority-on-pause left ON (the default):

On official app: alarm fires → onAudioFocusChange(-2) → dismiss → (1) → Background started FGS: Disallowed ...code:DENIED + mAllowStartForeground false, then about a minute later Stopping service due to app idle, and playback dies.

On debug app with timer enabled: same alarm, no denial, service stays up, playback keeps going. Confirmed via dumpsys that a normal pause still demotes after the delay.

Also, since it's a Handler-based timer and I release the wakelock on pause, so while the screen's off the demotion gets deferred (Doze throttles the timer) - when you pause and screen off it holds the foreground service a bit longer before it demotes. It's frozen and with no wakelock in that window so not a huge deal, but it's not a clean 60s.

Next steps:
-This would replace the default-flip PR I opened (#1643), so I'd close that one if you want to go this route instead.
-Since the change is in audio_service, should we PR to ryanheise (would close audio_service#996 too)? Or would you want Finamp to point at a fork for now?

Let me know I can do a PR if you like, I could also try to add the configurable delay you mentioned.

Best,
Matt

@Chaphasilor

Copy link
Copy Markdown
Member

That sounds promising! Feel free to open the new PR with your changes. We should probably do some internal testing first to ensure any PRs against just_audio are validated. We're currently waiting for two other changes to merged into just_audio as it is, so maybe we'll have to switch to a fork that consolidates all these changes. But the first step is a proof-of-concept PR from you, and then we can decide where to host the just_audio changes :)

@Komodo5197

Copy link
Copy Markdown
Collaborator

I'm wondering if this is related to the pause vs duck on audio interruptions code in some manner? But this issue doesn't seem to reproduce at on my device, so I can't do any testing.

@Chaphasilor

Copy link
Copy Markdown
Member

Maybe? @mjevange can you try changing 🔗 Duck on Audio Interruptions to see if this changes the behavior in any way?

@mjevange

mjevange commented Aug 2, 2026

Copy link
Copy Markdown
Author

I'll give it a shot and do some tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants