Skip to content

Android background subscription stalls after remote participant rejoins #437

Description

@patrykwos

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

  1. Connect an Android device and another device to the same LiveKit Room.
  2. Start a mediaPlayback foreground service on Android and keep the LiveKit Room connected.
  3. Put the Android app on Home or lock the screen.
  4. Publish microphone audio from the remote device.
  5. Confirm that Android receives the audio while remaining in the background.
  6. Fully disconnect the remote participant from the Room.
  7. Rejoin the same Room from the remote device.
  8. Publish a new microphone track.
  9. Observe that Android receives ParticipantConnected, TrackPublished, and TrackUnmuted, but the publication remains in subscriptionStatus: desired.
  10. 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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions