Skip to content

[Android] FATAL: "Session ID must be unique" crash when app restarts after aggressive OS kill (Oppo/Xiaomi/ColorOS) - MusicService.onCreate() needs defensive session cleanup #2567

@sahilchouksey

Description

@sahilchouksey

Bug Description

The app crashes with java.lang.IllegalStateException: Session ID must be unique when:

  1. User plays audio in the app
  2. The OS (especially Oppo ColorOS, Xiaomi MIUI) aggressively kills the app process WITHOUT calling onDestroy()
  3. User reopens the app
  4. MusicService.onCreate() attempts to create a new MediaLibrarySession while 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:

  1. Oppo/ColorOS/MIUI aggressive kill behavior: These OEMs kill background apps using SIGKILL-style termination that bypasses onDestroy() - the MediaSession is never released
  2. Zombie MediaSession: The old MediaSession remains registered in Android's MediaSessionManager even after the app process dies
  3. 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:

  1. Always use unique session ID: .setId("rntp_${System.currentTimeMillis()}") by default
  2. Try to release old session first: Query MediaSessionManager and release stale sessions before creating new one
  3. 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

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:

  1. Settings > Apps > [App Name] > Battery > Don't optimize
  2. Lock app in recent apps (prevents aggressive kill)

But this is not a sustainable solution - the library should handle this gracefully.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions