Skip to content

MusicService.emit() broken in bridgeless mode (New Architecture) — all native-to-JS events silently dropped #2593

@bil9148

Description

@bil9148

Describe the Bug

MusicService.emit() and emitList() hardcode the legacy reactNativeHost.reactInstanceManager.currentReactContext chain to send events to JS. In bridgeless mode (RN 0.76+, New Architecture), currentReactContext returns null because the real context lives in ReactHost, not ReactInstanceManager. The ?. safe-call chain silently no-ops, so every event from the native service to JS is dropped — playback state, track changes, remote media controls, progress, metadata, errors.

Audio plays fine and the notification appears (ExoPlayer runs natively), but no JS event handler ever fires. Notification play/pause/seek/next/previous all do nothing. No crash, no log, no warning.

Likely the same underlying cause as #2543.

Steps To Reproduce

  1. Create a project with newArchEnabled: true (default for new Expo projects since SDK 51)
  2. Set up RNTP with a PlaybackService that registers Event.RemotePlay, Event.RemotePause, etc.
  3. Play a track
  4. Tap any notification control — nothing happens

Root Cause

MusicService.kt lines 743-757:

@MainThread
private fun emit(event: String, data: Bundle? = null) {
    reactNativeHost.reactInstanceManager.currentReactContext
        ?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
        ?.emit(event, data?.let { Arguments.fromBundle(it) })
}

MusicService extends HeadlessJsTaskService, which already provides a reactContext property that branches correctly between bridged and bridgeless:

// HeadlessJsTaskService (RN core)
protected val reactContext: ReactContext?
    get() {
      if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
        return checkNotNull(reactHost) { ... }.currentReactContext
      } else {
        return reactNativeHost.reactInstanceManager.currentReactContext
      }
    }

RNTP bypasses this and hardcodes the legacy path.

Fix

Use the inherited reactContext instead of the hardcoded chain in both emit() and emitList():

@MainThread
private fun emit(event: String, data: Bundle? = null) {
    reactContext
        ?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
        ?.emit(event, data?.let { Arguments.fromBundle(it) })
}

@MainThread
private fun emitList(event: String, data: List<Bundle> = emptyList()) {
    val payload = Arguments.createArray()
    data.forEach { payload.pushMap(Arguments.fromBundle(it)) }

    reactContext
        ?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
        ?.emit(event, payload)
}

Two lines changed. Tested and working.

Environment Info

  • react-native-track-player: 4.1.2
  • react-native: 0.81.5
  • Expo SDK: 54
  • newArchEnabled: true (bridgeless mode)
  • Android 15, physical device

Replicable on Example App?

Haven't tested the example app, but any app with newArchEnabled: true should hit this — the code path is unconditional.

How I can Help

Happy to open a PR with this fix if you want.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions