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
- Create a project with
newArchEnabled: true (default for new Expo projects since SDK 51)
- Set up RNTP with a PlaybackService that registers
Event.RemotePlay, Event.RemotePause, etc.
- Play a track
- 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.
Describe the Bug
MusicService.emit()andemitList()hardcode the legacyreactNativeHost.reactInstanceManager.currentReactContextchain to send events to JS. In bridgeless mode (RN 0.76+, New Architecture),currentReactContextreturnsnullbecause the real context lives inReactHost, notReactInstanceManager. 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
newArchEnabled: true(default for new Expo projects since SDK 51)Event.RemotePlay,Event.RemotePause, etc.Root Cause
MusicService.ktlines 743-757:MusicServiceextendsHeadlessJsTaskService, which already provides areactContextproperty that branches correctly between bridged and bridgeless:RNTP bypasses this and hardcodes the legacy path.
Fix
Use the inherited
reactContextinstead of the hardcoded chain in bothemit()andemitList():Two lines changed. Tested and working.
Environment Info
newArchEnabled: true(bridgeless mode)Replicable on Example App?
Haven't tested the example app, but any app with
newArchEnabled: trueshould hit this — the code path is unconditional.How I can Help
Happy to open a PR with this fix if you want.