-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Bug Description
The app crashes with java.lang.IllegalStateException: Session ID must be unique when:
- User plays audio in the app
- The OS (especially Oppo ColorOS, Xiaomi MIUI) aggressively kills the app process WITHOUT calling
onDestroy() - User reopens the app
MusicService.onCreate()attempts to create a newMediaLibrarySessionwhile the old "zombie" session still exists in the Android system
This crash is FATAL and unrecoverable from JavaScript - it occurs in native code during MusicService.onCreate() before any JS error handling can execute.
Impact
- 150+ crashes in our production app
- ~90% on Oppo/ColorOS devices (known for aggressive battery optimization)
- 15 affected users with repetitive crashes (~10 crashes per user on average)
- 100% occur in background (app killed while in background)
- Users cannot use the app until they clear app data or device restarts
Root Cause Analysis
The crash happens because:
- Oppo/ColorOS/MIUI aggressive kill behavior: These OEMs kill background apps using SIGKILL-style termination that bypasses
onDestroy()- the MediaSession is never released - Zombie MediaSession: The old MediaSession remains registered in Android's MediaSessionManager even after the app process dies
- No defensive cleanup in
MusicService.onCreate(): When the app restarts,MediaLibrarySession.Builder.build()throws because a session with the same ID already exists
Normal Android: App killed → onDestroy() → MediaSession.release() → Clean restart ✓
Oppo/ColorOS: App killed (SIGKILL) → onDestroy() SKIPPED → Zombie MediaSession → Restart → CRASH 💥
Stack Trace
FATAL EXCEPTION: main
Process: com.example.myapp, PID: 32617
java.lang.RuntimeException: Unable to create service com.doublesymmetry.trackplayer.service.MusicService: java.lang.IllegalStateException: Session ID must be unique. ID=
at android.app.ActivityThread.handleCreateService(ActivityThread.java:4158)
at android.app.ActivityThread.access$1600(ActivityThread.java:233)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2014)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:241)
at android.app.ActivityThread.main(ActivityThread.java:7582)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:941)
Caused by: java.lang.IllegalStateException: Session ID must be unique. ID=
at androidx.media3.session.MediaSession.<init>(MediaSession.java:726)
at androidx.media3.session.MediaLibraryService$MediaLibrarySession.<init>(MediaLibraryService.java:727)
at androidx.media3.session.MediaLibraryService$MediaLibrarySession$Builder.build(MediaLibraryService.java:705)
at com.doublesymmetry.trackplayer.service.MusicService.onCreate(MusicService.kt:120)
at android.app.ActivityThread.handleCreateService(ActivityThread.java:4146)
... 8 more
Steps to Reproduce
# 1. Start logcat capture
adb logcat -c && adb logcat -s AndroidRuntime:E ReactNativeJS:I
# 2. Open app and play audio (do this manually on device)
# 3. While audio is PLAYING, simulate Oppo-style aggressive kill:
adb shell am crash <your.package.name>
# 4. Immediately reopen the app manually (tap app icon)
# 5. CRASH occurs: "Session ID must be unique"Why adb shell am crash works: This command simulates SIGKILL behavior - the process is terminated immediately without calling any lifecycle methods, leaving the MediaSession as a zombie.
Environment
- react-native-track-player version: 5.0.0-alpha0 (feat/media3-newarch branch)
- React Native version: 0.76.9
- Affected Android versions: Android 10 (~70%), Android 15 (~30%)
- Affected manufacturers:
- Oppo/ColorOS: ~90% of crashes
- Xiaomi/MIUI: ~5%
- Transsion: ~3%
- Others: ~2%
Proposed Solution
Modify MusicService.kt to defensively handle zombie sessions in onCreate():
// MusicService.kt - in onCreate()
@ExperimentalCoroutinesApi
override fun onCreate() {
// ... existing timber setup ...
fakePlayer = ExoPlayer.Builder(this).build()
// ... existing openAppIntent setup ...
// PROPOSED FIX: Wrap session creation in try-catch with cleanup
try {
mediaSession = MediaLibrarySession.Builder(this, fakePlayer,
InnerMediaSessionCallback()
)
.setBitmapLoader(CacheBitmapLoader(CoilBitmapLoader(this)))
.setSessionActivity(
PendingIntent.getActivity(
this,
0,
openAppIntent,
getPendingIntentFlags()
)
)
.build()
} catch (e: IllegalStateException) {
if (e.message?.contains("Session ID must be unique") == true) {
Timber.w("Zombie MediaSession detected, attempting recovery...")
// Option A: Use a unique session ID based on timestamp
mediaSession = MediaLibrarySession.Builder(this, fakePlayer,
InnerMediaSessionCallback()
)
.setId("rntp_${System.currentTimeMillis()}")
.setBitmapLoader(CacheBitmapLoader(CoilBitmapLoader(this)))
.setSessionActivity(
PendingIntent.getActivity(
this,
0,
openAppIntent,
getPendingIntentFlags()
)
)
.build()
Timber.i("Recovery successful with new session ID")
} else {
throw e // Re-throw if it's a different error
}
}
super.onCreate()
}Alternative approaches:
- Always use unique session ID:
.setId("rntp_${System.currentTimeMillis()}")by default - Try to release old session first: Query MediaSessionManager and release stale sessions before creating new one
- Store session ID in SharedPreferences: Increment it on each app launch to ensure uniqueness
Why JS-Level Fixes Don't Work
We attempted to catch this error in JavaScript:
try {
await TrackPlayer.setupPlayer(options);
} catch (error) {
if (error.message.includes('Session ID must be unique')) {
// Recovery logic
}
}This doesn't work because:
- The crash happens in
MusicService.onCreate()which is called during native service creation - The error is thrown BEFORE
setupPlayer()ever returns to JavaScript - The entire process crashes at the native level, not at the JS bridge level
Timeline of crash:
13:24:39.795 │ JS: TrackPlayer.setupPlayer() called
13:24:39.795 │ NATIVE: MusicService starting...
13:24:39.822 │ NATIVE: MusicService.onCreate() → MediaLibrarySession.Builder.build()
│ 💥 CRASH: "Session ID must be unique"
│
│ ⚠️ Only 27ms between JS call and native crash!
│ The error is thrown BEFORE any JS catch block can run
Related Issues
- Crash on feat/media3-newarch (java.lang.IllegalStateException: Session ID must be unique) #2485 - Same crash reported, but marked as closed without a fix
- "Session ID must be unique. ID=" when building a session androidx/media#1115 - Underlying MediaSession issue in androidx.media3
Additional Context
Device kill behavior research (from dontkillmyapp.com):
| Manufacturer | OS | Kill Behavior | % of our crashes |
|---|---|---|---|
| Oppo | ColorOS | Aggressive SIGKILL, no lifecycle | ~90% |
| Xiaomi | MIUI | Similar aggressive kill | ~5% |
| Vivo | FuntouchOS | Similar to Oppo | - |
| OnePlus | OxygenOS | Now merged with ColorOS | - |
These OEMs have a combined market share of ~40% in Asia, making this a critical issue for apps targeting that region.
Workaround for Users (Not Ideal)
Users can temporarily fix by:
- Settings > Apps > [App Name] > Battery > Don't optimize
- Lock app in recent apps (prevents aggressive kill)
But this is not a sustainable solution - the library should handle this gracefully.