Android: keep the stream alive under dialogs, fix hang on leaving #825

Open
aoleg94 wants to merge 3 commits from aoleg94/android-fix-pause into main
aoleg94 commented 2026-10-03 17:41:45 -05:00 (Migrated from github.com)

Summary

  • A dialog on top ended the stream. The session was stopped in onPause and restarted in
    onResume, so anything that pauses without hiding the activity (e.g. the Bluetooth enable
    popup) dropped the stream. The session now runs from onStart to onStop
    (StreamVisibilityObserver).
  • Native deadlock this exposed. android_chiaki_video_decoder_set_surface(NULL) locked
    codec_mutex and then called kill_decoder(), which locks it again, and returned with it held.
    That path never ran before because the session was always gone by the time the surface was
    destroyed; with the session living until onStop, going home hung the main thread.
    kill_decoder() also deleted the codec outside the lock and skipped joining the output thread
    when no input buffer was free for the EOS; both fixed.
  • "Remote is already in use" on coming back. Right after a session stops, the console can
    still report Remote Play as in use for a few seconds, or until its timeout when Battery Saver
    cut the network before the disconnect got out (it blocks background apps immediately, before
    onStop). Within 20 s of the app stopping its own session, that error now retries every 1.5 s
    while showing "connecting". A first connect still reports it right away.

Testing

  • New StreamVisibilityObserverTest (fails with the old onPause/onResume mapping) and
    RemoteInUseRetryTest. ./gradlew testDebugUnitTest passes.
  • Galaxy Z Fold 4 (Android 13, Battery Saver on) + PS5: Bluetooth popup over the stream keeps
    it running; home and back (fast swipe and via recents) reconnects within seconds without an
    error; no hang. The decoder change has no unit test (it drives the hardware decoder).

Notes

I tried onUserLeaveHint to stop before the network is cut, but Samsung's Bluetooth popup
triggers it too, so it can't tell leaving from a dialog; hence the retry.

Developed with the assistance of Claude Code, and tested on real hardware as described above.

🤖 Generated with Claude Code

### Summary - **A dialog on top ended the stream.** The session was stopped in `onPause` and restarted in `onResume`, so anything that pauses without hiding the activity (e.g. the Bluetooth enable popup) dropped the stream. The session now runs from `onStart` to `onStop` (`StreamVisibilityObserver`). - **Native deadlock this exposed.** `android_chiaki_video_decoder_set_surface(NULL)` locked `codec_mutex` and then called `kill_decoder()`, which locks it again, and returned with it held. That path never ran before because the session was always gone by the time the surface was destroyed; with the session living until `onStop`, going home hung the main thread. `kill_decoder()` also deleted the codec outside the lock and skipped joining the output thread when no input buffer was free for the EOS; both fixed. - **"Remote is already in use" on coming back.** Right after a session stops, the console can still report Remote Play as in use for a few seconds, or until its timeout when Battery Saver cut the network before the disconnect got out (it blocks background apps immediately, before `onStop`). Within 20 s of the app stopping its own session, that error now retries every 1.5 s while showing "connecting". A first connect still reports it right away. ### Testing - New `StreamVisibilityObserverTest` (fails with the old `onPause`/`onResume` mapping) and `RemoteInUseRetryTest`. `./gradlew testDebugUnitTest` passes. - Galaxy Z Fold 4 (Android 13, Battery Saver on) + PS5: Bluetooth popup over the stream keeps it running; home and back (fast swipe and via recents) reconnects within seconds without an error; no hang. The decoder change has no unit test (it drives the hardware decoder). ### Notes I tried `onUserLeaveHint` to stop before the network is cut, but Samsung's Bluetooth popup triggers it too, so it can't tell leaving from a dialog; hence the retry. Developed with the assistance of Claude Code, and tested on real hardware as described above. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This pull request is broken due to missing fork information.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin 'aoleg94/android-fix-pause:aoleg94/android-fix-pause'
git switch 'aoleg94/android-fix-pause'

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch 'main'
git merge --no-ff 'aoleg94/android-fix-pause'
git switch 'aoleg94/android-fix-pause'
git rebase 'main'
git switch 'main'
git merge --ff-only 'aoleg94/android-fix-pause'
git switch 'aoleg94/android-fix-pause'
git rebase 'main'
git switch 'main'
git merge --no-ff 'aoleg94/android-fix-pause'
git switch 'main'
git merge --squash 'aoleg94/android-fix-pause'
git switch 'main'
git merge --ff-only 'aoleg94/android-fix-pause'
git switch 'main'
git merge 'aoleg94/android-fix-pause'
git push origin 'main'
Sign in to join this conversation.
No description provided.