Fix ExoPlayer wrong-thread crash + recordSearch off Main (perf-audit pass-2) — vc=79
All checks were successful
build-apk / build-and-publish (push) Successful in 7m11s
gitleaks / scan (push) Successful in 43s

- PlaybackService: the pauseOnHeadphoneDisconnect settings-watcher collector
  ran on globalScope (Dispatchers.IO) and called setHandleAudioBecomingNoisy
  on the ExoPlayer, which is thread-affine to the Main thread it was built on
  → latent IllegalStateException('Player accessed on the wrong thread') that
  fires every service start (the StateFlow emits on first subscription). Run
  the collector on Dispatchers.Main (mirrors resumePollJob).
- SearchViewModel: recordSearch (json-encode + SharedPrefs write) was on Main;
  wrap in withContext(Dispatchers.IO).

Both adversarially verified in the multi-agent perf audit (pass 2).
This commit is contained in:
Cobb 2026-06-21 05:04:39 -07:00
parent 2defbd2925
commit 96bad228ef
3 changed files with 19 additions and 4 deletions

View file

@ -128,7 +128,13 @@ class PlaybackService : MediaSessionService() {
// without requiring a service restart. The initial value was
// baked in via the builder above — this picks up subsequent
// flips.
settingsWatcherJob = StrawApp.globalScope.launch {
// MUST run on Main: globalScope is Dispatchers.IO, but ExoPlayer is
// thread-affine to the Main thread it was built on (onCreate), so
// touching it (setHandleAudioBecomingNoisy) off-Main throws
// "Player is accessed on the wrong thread" — and this hot StateFlow
// emits on first subscription, i.e. every service start. Mirrors
// resumePollJob below.
settingsWatcherJob = StrawApp.globalScope.launch(Dispatchers.Main) {
Settings.get().pauseOnHeadphoneDisconnect.collect { handle ->
player.setHandleAudioBecomingNoisy(handle)
}

View file

@ -237,7 +237,8 @@ class SearchViewModel : ViewModel() {
}
// Record AFTER the search succeeds so mistyped queries
// that error out don't pollute the recent-searches list.
runCatching { History.get().recordSearch(q) }
// Off Main — recordSearch json-encodes + writes SharedPrefs.
withContext(Dispatchers.IO) { runCatching { History.get().recordSearch(q) } }
if (Settings.get().cacheEnabled.value) {
withContext(Dispatchers.IO) {
// Re-check active state after the dispatcher