Conversation
The native client can exit on its own and nothing noticed: start() returned normally, the launching coroutine completed, and snapclientProcess stayed non-null so even startLocalSnapclient refused to act. On the rig that left a broadcaster silent with a healthy server and no UI trace. The supervisor keys on the JOB, not the process. stopLocalSnapclient stops the client with destroyForcibly(), which from inside start() looks exactly like a crash, so a supervisor keyed on "the process is gone" would restart it the instant AudioFocusController paused it and play over whatever app just took the speaker. Every intentional stop cancels the coroutine before destroying the process, so isActive is already false when start() returns. Rig-verified both directions: SIGKILL to libsnapclient.so is back in ~3 s; a focus loss to Spotify leaves it stopped for as long as Spotify holds the speaker, then it returns ~6 s after Spotify pauses. The state mirror moves inside the job so it dies with it. In snapclient mode it used to be launched separately on every focus regain, one live collector per regain; the broadcaster had none at all, so it could not show its own client's health.
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.
App-side half of capullo-tech/capullo-audio#7. That PR fixes why a focus loss never recovered; this one fixes the case where the native client simply dies.
The bug
libsnapclient.socan exit on its own and nothing noticed.SnapclientProcess.start()returned normally, the launching coroutine completed, andsnapclientProcessstayed non-null so evenstartLocalSnapclient()refused to act on itsif (snapclientProcess != null) returnguard. The result on the rig was a broadcaster with a healthy server, a healthy app, a stream still reaching web listeners, and no local audio and no UI trace. Recovery meant a manual--es dbg snaptestor a broadcast restart, which is why this has read as a rig quirk rather than a bug.The trap this avoids
stopLocalSnapclient()stops the client withdestroyForcibly(), which from insidestart()looks exactly like a crash. A supervisor keyed on "the process is gone" would restart the client the instantAudioFocusControllerpaused it, and QuantumCast would play over whatever app just took the speaker, which is precisely the behaviour that controller exists to prevent.So the signal is the job, not the process. Every intentional stop cancels the coroutine before destroying the process, so
isActiveis already false whenstart()returns and the loop ends instead of restarting.One invariant worth stating, now recorded in a comment: the supervisor leaves
snapclientProcessset while it sleeps in backoff, but that cannot collide withstartLocalSnapclient()'s early return. That method has exactly one caller, the focus controller'sonResume, which only fires after a loss has runonPause->stopLocalSnapclient(), and that cancels the supervisor and nulls the field first.Verification
Rig-verified on an OPPO PFFM10 (Android 15) broadcasting with a local snapclient, both directions:
kill -9onlibsnapclient.soexited on its own (code=137)->restarting in 1000msstopped as asked (code=0), stays stopped for as long as Spotify holds the speakerspotlessCheckand:app:assembleDebuggreen.Pin
No pin bump in this branch. The supervisor only calls
sc.start()andsc.connectionState, both of which already exist in the currently pinned library, so CI should be green as-is and this can be reviewed independently.Merging this alone gives only half the fix: the client would self-heal after a native exit, but a focus loss to another app would still leave it stopped until the app is foregrounded. The full behaviour needs capullo-audio#7 merged, jitpack built, and a follow-up pin bump.
Also here
The
connectionStatemirror moves inside the supervisor job so it dies with it. In snapclient mode it used to be launched separately on every focus regain, one live collector per regain; the broadcaster had none at all, so it could not show its own client's health. Nothing in the UI readssnapclientStateyet, so this is plumbing rather than a visible change.