diff --git a/buildSrc/src/main/kotlin/ProjectConfig.kt b/buildSrc/src/main/kotlin/ProjectConfig.kt index 416d3abc5..0dbaed3a2 100644 --- a/buildSrc/src/main/kotlin/ProjectConfig.kt +++ b/buildSrc/src/main/kotlin/ProjectConfig.kt @@ -9,6 +9,14 @@ const val STRAW_SDK_TARGET = 35 // Sulkta fork — Straw // +// vc=79 / 0.1.0-CM — perf-audit pass-2 (app-side slam-dunks): +// * FIX a wrong-thread crash: the headphone-disconnect settings watcher +// ran on the IO scope and touched the ExoPlayer (thread-affine to the +// Main thread it was built on) → "Player accessed on the wrong thread" +// latent on every session. Collector now runs on Dispatchers.Main. +// * recordSearch (json-encode + SharedPrefs write) moved off the Main +// thread into withContext(Dispatchers.IO). +// // vc=78 / 0.1.0-CL — perf-audit batch 1 (app-side): // * Autoplay-next no longer drops the user's max-resolution cap. The // enter-video track-selection reset built params from scratch, wiping @@ -100,6 +108,6 @@ const val STRAW_SDK_TARGET = 35 // vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via // strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no // NewPipeExtractor in the runtime path. -const val STRAW_VERSION_CODE = 78 -const val STRAW_VERSION_NAME = "0.1.0-CL" +const val STRAW_VERSION_CODE = 79 +const val STRAW_VERSION_NAME = "0.1.0-CM" const val STRAW_APPLICATION_ID = "com.sulkta.straw" diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/PlaybackService.kt b/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/PlaybackService.kt index 10e2c6876..bd50ab546 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/PlaybackService.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/PlaybackService.kt @@ -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) } diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt b/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt index aab235395..21bba571c 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt @@ -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