Skip to content

Unpublishing local tracks is causing a participant_left and participant_joined cycle #958

@Daltron

Description

@Daltron

Describe the bug
After calling localParticipant.unpublishAll() on an iOS client, the LiveKit SDK is disconnecting and reconnecting to the room. This fires a participant_left webhook (with disconnectReason: DUPLICATE_IDENTITY) followed by a participant_joined webhook for the same participant. The participant never intentionally left the room, they only unpublished their tracks. This is confirmed with our logs as we see the didUnpublishTrack (both video and audio) get called and then immediately after, we see the didSubscribeToTrack (both video and audio) get called right after.

This causes problems for our application as we use the participant_left webhook to trigger cleanup logic (e.g., removing a guest from a cohost list, revoking permissions).

SDK Version
2.12.1

iOS/macOS Version
iOS 18 and iOS 26

Xcode Version
The Xcode version which the issue occurs.
(Please provide the Swift version if you know it)

Steps to Reproduce

  1. Connect a participant to a room (iOS SDK, autoSubscribe: true)
  2. Grant the participant canPublish: true via server-side updateParticipant
  3. Publish video and audio tracks (localParticipant.publish(videoTrack:) and localParticipant.publish(audioTrack:))
  4. Call localParticipant.unpublishAll()
  5. Observe server-side webhooks

Expected behavior

  • Two track_unpublished webhook events (one for video, one for audio)
  • Participant remains connected to the room
  • No participant_left or participant_joined events

Actual behavior
Actual behavior

  • Two track_unpublished webhook events fire correctly
  • ~10-15 seconds later, a participant_left event fires with disconnectReason: DUPLICATE_IDENTITY
  • Immediately after, a participant_joined event fires for the same participant
  • The participant's remote tracks (subscriptions) are temporarily lost during the reconnect cycle

Logs

📹 📡 didUnpublishTrack liveId=EEKGFSLAPSbcBBsaKzLa userId=LkRsnLBcw0bZGcCoLqcyt8l4rVJ3 kind=video remote=false // The guest unpublished their video track
📹 📡 didUnpublishTrack liveId=EEKGFSLAPSbcBBsaKzLa userId=LkRsnLBcw0bZGcCoLqcyt8l4rVJ3 kind=audio remote=false // The guest unpublished their audio track

10-15 seconds later:

📹 📡 didUnpublishTrack liveId=EEKGFSLAPSbcBBsaKzLa userId=wncKZz2TH1OMuiuyT6sUR8KaKMc2 kind=video remote=true // The creator of the room's video track is unpublished for some reason according to the guest's device
📹 📡 didUnpublishTrack liveId=EEKGFSLAPSbcBBsaKzLa userId=wncKZz2TH1OMuiuyT6sUR8KaKMc2 kind=audio remote=true // The creator of the room's audio track is unpublished for some reason according to the guest's device

📹 did subscribe to track room=EEKGFSLAPSbcBBsaKzLa particpant=wncKZz2TH1OMuiuyT6sUR8KaKMc2 kind=video
📹 did subscribe to track room=EEKGFSLAPSbcBBsaKzLa particpant=wncKZz2TH1OMuiuyT6sUR8KaKMc2 kind=audio

I also noticed that the reconnect delegate methods (.quick) were getting called but didn't attach logs for those.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions