Android background subscription stalls after remote participant rejoins
Describe the bug
On Android, while a LiveKit Room is kept connected in the background using a mediaPlayback foreground service, subscribing to a new remote audio track may become permanently stuck after the remote participant fully disconnects and rejoins.
An audio track established before the Android app enters the background continues playing correctly. However, when the remote participant leaves, rejoins, and publishes a new microphone track, Android receives the participant and publication signaling events but never completes the new subscription.
The publication remains in the following state:
subscriptionStatus: desired
isSubscribed: false
publication.track: null
- no
TrackSubscribed
- no WebRTC
ontrack
- no inbound RTP
The subscription completes only after the Android Activity returns to the foreground.
Diagnostic logging shows that, while the app is in the background, LiveKit reaches:
MediaSectionsRequirement received
addTransceiver completed
negotiate requested
but does not continue to:
negotiate started
createOffer
setLocalDescription
answer received
setRemoteDescription
ontrack
Standard JavaScript timers requested at the same time also do not execute until the app returns to the foreground.
As a diagnostic workaround, we bypassed only the short PCTransport negotiation debounce on Android while the app was in the background and invoked the existing negotiation body immediately through a microtask. This restored the complete SDP negotiation, ontrack, TrackSubscribed, and inbound RTP without foregrounding the Activity.
This suggests that signaling wakes JavaScript sufficiently to process synchronous callbacks, but the timer used by the negotiation debounce does not execute while the React Native Timing module is suspended in the background.
To Reproduce
- Connect an Android device and another device to the same LiveKit Room.
- Start a
mediaPlayback foreground service on Android and keep the LiveKit Room connected.
- Put the Android app on Home or lock the screen.
- Publish microphone audio from the remote device.
- Confirm that Android receives the audio while remaining in the background.
- Fully disconnect the remote participant from the Room.
- Rejoin the same Room from the remote device.
- Publish a new microphone track.
- Observe that Android receives
ParticipantConnected, TrackPublished, and TrackUnmuted, but the publication remains in subscriptionStatus: desired.
- Observe that there is no
TrackSubscribed, WebRTC ontrack, or inbound RTP until the Android Activity returns to the foreground.
Expected behavior
When a remote participant rejoins and publishes a new audio track, the Android participant should complete the WebRTC negotiation and subscribe to the new track while remaining in the background.
TrackSubscribed and ontrack should be emitted, and the audio should be received without requiring the user to open the app.
Screenshots
Not applicable. Relevant sanitized diagnostic logs are included below.
Before the workaround:
appState=background
MediaSectionsRequirement received
addTransceiver completed
negotiate requested
subscriptionStatus=desired
isSubscribed=false
hasTrack=false
No negotiate started, createOffer, or ontrack follows.
After bypassing the short negotiation debounce in the background:
background_bypass_started
negotiate started
createOffer
setLocalDescription
answer received
setRemoteDescription
ontrack
subscriptionStatus=subscribed
hasTrack=true
Inbound RTP after the workaround:
bytesReceived: 53 -> 17262 -> 21452
packetsReceived: 1 -> 125 -> 178
packetsLost: 0
Device Info:
- Device:
Samsung Galaxy A16
Dependencies Info (please reference your package-lock.json or yarn.lock file, not just your package.json):
@livekit/react-native: 2.11.1
livekit-client: 2.20.1
react-native-webrtc: 144.1.1
Additional context
Application environment:
- Expo SDK: 54
- React Native:
0.81.5
- LiveKit Server: 1.13.3
- Foreground service type:
mediaPlayback
- Foreground service implementation:
react-native-notify-kit
- One LiveKit
Room instance is maintained in React Native JavaScript.
- The foreground service does not create or maintain a separate LiveKit connection.
- Background behavior is receive-only; microphone publishing is restricted to the foreground.
The issue does not appear to be related to audio playback or Android audio focus:
- already established remote audio tracks play correctly in the background;
- after the failure there is no inbound RTP and no
RemoteAudioTrack;
- restarting the LiveKit
AudioSession does not resolve it;
- bypassing the negotiation debounce immediately restores the subscription.
The workaround was diagnostic and is not intended as a request to expose internal negotiation APIs. It indicates that the short timer used to schedule PCTransport negotiation may need a React Native background-safe implementation or an immediate/event-driven path when triggered by MediaSectionsRequirement.
A possibly related older issue describes a similar problem with debounce timers not executing while a React Native application is in the background:
#158
Android background subscription stalls after remote participant rejoins
Describe the bug
On Android, while a LiveKit Room is kept connected in the background using a
mediaPlaybackforeground service, subscribing to a new remote audio track may become permanently stuck after the remote participant fully disconnects and rejoins.An audio track established before the Android app enters the background continues playing correctly. However, when the remote participant leaves, rejoins, and publishes a new microphone track, Android receives the participant and publication signaling events but never completes the new subscription.
The publication remains in the following state:
subscriptionStatus: desiredisSubscribed: falsepublication.track: nullTrackSubscribedontrackThe subscription completes only after the Android Activity returns to the foreground.
Diagnostic logging shows that, while the app is in the background, LiveKit reaches:
but does not continue to:
Standard JavaScript timers requested at the same time also do not execute until the app returns to the foreground.
As a diagnostic workaround, we bypassed only the short
PCTransportnegotiation debounce on Android while the app was in the background and invoked the existing negotiation body immediately through a microtask. This restored the complete SDP negotiation,ontrack,TrackSubscribed, and inbound RTP without foregrounding the Activity.This suggests that signaling wakes JavaScript sufficiently to process synchronous callbacks, but the timer used by the negotiation debounce does not execute while the React Native Timing module is suspended in the background.
To Reproduce
mediaPlaybackforeground service on Android and keep the LiveKit Room connected.ParticipantConnected,TrackPublished, andTrackUnmuted, but the publication remains insubscriptionStatus: desired.TrackSubscribed, WebRTContrack, or inbound RTP until the Android Activity returns to the foreground.Expected behavior
When a remote participant rejoins and publishes a new audio track, the Android participant should complete the WebRTC negotiation and subscribe to the new track while remaining in the background.
TrackSubscribedandontrackshould be emitted, and the audio should be received without requiring the user to open the app.Screenshots
Not applicable. Relevant sanitized diagnostic logs are included below.
Before the workaround:
No
negotiate started,createOffer, orontrackfollows.After bypassing the short negotiation debounce in the background:
Inbound RTP after the workaround:
Device Info:
Samsung Galaxy A16Dependencies Info (please reference your package-lock.json or yarn.lock file, not just your package.json):
@livekit/react-native:2.11.1livekit-client:2.20.1react-native-webrtc:144.1.1Additional context
Application environment:
0.81.5mediaPlaybackreact-native-notify-kitRoominstance is maintained in React Native JavaScript.The issue does not appear to be related to audio playback or Android audio focus:
RemoteAudioTrack;AudioSessiondoes not resolve it;The workaround was diagnostic and is not intended as a request to expose internal negotiation APIs. It indicates that the short timer used to schedule
PCTransportnegotiation may need a React Native background-safe implementation or an immediate/event-driven path when triggered byMediaSectionsRequirement.A possibly related older issue describes a similar problem with debounce timers not executing while a React Native application is in the background:
#158