| name | fgs-state-ended-trap |
| description | Suppress the "ended" playback state at a forwarding-player boundary while the underlying player is being replaced, so the media service is not torn down in the gap between one player finishing and the next starting. Use when background playback stops partway through a queue on some devices but never on your development phone, when the playback notification disappears between tracks, or when the app is frozen by the system mid-queue. |
The ended-state trap during a delegate swap
The "FGS" in the name is Android's foreground service — the status that keeps a media app
alive in the background. When each track gets its own player object, the end of a track looks like
this from the outside:
player A reports ended ──┐
│ ~25 ms
player B swapped in, plays ┘
For those few milliseconds the media session is asked for the playback state and answers ended.
Foreground status survives only while the player intends to play and the state is ready or
buffering — ended fails that test, so the service detaches its notification and stops being a
foreground service. (The state field is the one consulted, which is why the fix below remaps the
state rather than faking isPlaying.) On stock builds the app
usually survives long enough for player B to arrive. On the more aggressive power-management builds
some manufacturers ship, the app is frozen in that window and playback simply stops mid-queue.
The fix lives at the forwarding boundary — one flag and one getter:
@Volatile var suppressPlaybackEnded = false
override fun getPlaybackState(): Int {
state = .getPlaybackState()
(state == Player.STATE_ENDED && suppressPlaybackEnded) Player.STATE_BUFFERING state
}