Repository navigation
Three fixes for the cast path - #2
Merged
hypnotoad08 merged 3 commits intoSep 20, 2026
Merged
hypnotoad08 merged 3 commits into
hypnotoad08 merged 3 commits into
Conversation
The clamp on getCurrentTrackIndex throws while the controller holds no items, which it does for a moment after joining a receiver session. Falling back to the position-only seek already used for a session without tracks avoids it.
Playback on a cast device carries on without this app, so removing the task must not close the session. The check has to come before super.onTaskRemoved, which stops the service itself when the player is not playing locally.
The Cast SDK rejoins a running receiver, but the app never learns which of its sessions plays there, so no progress reaches the server. The queue returns with the media ids this app wrote, and those start with the playback session id, so the receiver names the session and the stored one only has to agree.
hypnotoad08
merged commit Sep 20, 2026
86726b7
into
hypnotoad08:feature/Media3-Conversion
1 check passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three fixes for the cast path, found while testing the
media3Debugflavor on aChromecast audio group. Each one is a separate commit on top of b34814d, and
each is backed by a measurement on the device rather than by reasoning alone.
Casting stops when the app is swiped away
Media3PlaybackService.onTaskRemovedcloses playback whenever a session is held,without looking at where it plays, so the receiver stops within a second:
castStatusCode=SUCCESSis the point: the session is ended deliberately, notlost. The final-sync work on this branch makes that shutdown clean, but it still
shuts down - swiping the app away is not a reason to stop a receiver that plays
on its own.
The check has to come before
super.onTaskRemoved, which stops the serviceitself when the player is not playing locally - and while casting it never is:
With the guard in place the same swipe leaves the service running and playback
continued for another 25 minutes until I stopped it myself.
Crash when seeking with an empty queue
session.getCurrentTrackIndex().coerceIn(0, controller.mediaItemCount - 1)throws while the controller holds no items, which it does for a few seconds
after joining a receiver session. Falling back to the position-only seek that is
already there for a session without tracks avoids it:
The window is not exotic: the Cast SDK rejoins a running receiver on its own
(
Connected to device. rejoinedApp: true), and the queue arrives from thereceiver a moment later, so any seek in between hits an empty controller.
Nothing reaches the server after the SDK rejoins a receiver
Cast a book, swipe the app away, open it again while the receiver keeps playing.
The Cast SDK rejoins on its own and playback is controllable, so this looks fine
The player is back but
currentPlaybackSessionis null, so every branch guardedby it is skipped - not one
Emitevent in the whole run. The rejoin happensbelow the app: the SDK restores the connection and
CastPlayerfills its queuefrom the receiver, but nothing maps that back to a session this app knows.
The queue is enough to identify it.
PlaybackSessionMediaAdapterwrites mediaids as
"<sessionId>_<stableId>", and the defaultMediaItemConvertercarriesthem through the receiver, so the returned queue names the session.
PlaybackPositionModel.writeBackToSessionalready checks exactly that andcomputes the position with the track offset, so the whole thing is:
Called from
onEventsonEVENT_TIMELINE_CHANGED, which is when the queuearrives. Nothing is pushed back to the receiver - reloading the queue there
would restart playback - and the server check keeps a session from another
server out. With it in place the same test gives:
How this was tested
On a Pixel 10 Pro XL, GrapheneOS with sandboxed Play Services, casting to a
Chromecast audio group. No automated tests - what is written above is what I
measured on the device:
second (
castStatusCode=SUCCESS); with it the service survives and playbackran on for another 25 minutes. Verified that the build under test really did
or did not contain the guard, by looking for its log string in the dex.
a book, reopened 50s later: adopted 334ms after
rejoinedApp: true, and thefirst sync afterwards carried 10853s - 70s of wall clock, 65s of position, so
the track offset came out right.
cast session was paused, which ends the receiver application
(
onApplicationDisconnected: castStatusCode=SUCCESS). The app came back local,never rejoined, and adopted nothing over the next four minutes. Same result
bringing the app to the foreground three times with no receiver playing.
syncs, every one successful, longest gap between syncs 31s, book position
tracking wall clock at 0.997.
Happy to change any of this, or to drop a commit if you would rather solve it
differently.