Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions livekit-rtc/livekit/rtc/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,13 @@ async def unpublish_track(self, track_sid: str) -> None:
Raises:
UnpublishTrackError: If there is an error in unpublishing the track.
"""
# Capture the publication before the FFI round-trip. The
# local_track_unpublished room event races this async response and may
# remove it from _track_publications first; holding our own reference
# guarantees the track is cleared once unpublish completes, regardless
# of which path removes the publication from the dict.
publication = self._track_publications.get(track_sid)

req = proto_ffi.FfiRequest()
req.unpublish_track.local_participant_handle = self._ffi_handle.handle
req.unpublish_track.track_sid = track_sid
Expand All @@ -799,8 +806,11 @@ async def unpublish_track(self, track_sid: str) -> None:
if cb.unpublish_track.error:
raise UnpublishTrackError(cb.unpublish_track.error)

publication = self._track_publications.pop(track_sid)
publication._track = None
# Remove defensively: the room-event handler may already have done
# so when it processed local_track_unpublished first.
self._track_publications.pop(track_sid, None)
if publication is not None:
publication._track = None
queue.task_done()
finally:
self._room_queue.unsubscribe(queue)
Expand Down
15 changes: 13 additions & 2 deletions livekit-rtc/livekit/rtc/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,9 +735,20 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None:
ltrack = lpublication.track
self.emit("local_track_published", lpublication, ltrack)
elif which == "local_track_unpublished":
# During teardown the publication may already have been removed
# from the participant's dict by LocalParticipant.unpublish_track
# (the FFI event races that async response), so the SID can be gone
# by the time this event is dispatched. Look it up defensively and
# skip the emit when it is no longer tracked, mirroring the
# local_track_republished and remote track_unpublished handlers,
# instead of raising a KeyError that _listen_task logs as an error.
sid = event.local_track_unpublished.publication_sid
lpublication = self.local_participant.track_publications[sid]
self.emit("local_track_unpublished", lpublication)
unpublished = self.local_participant._track_publications.get(sid)
if unpublished is not None:
del self.local_participant._track_publications[sid]
self.emit("local_track_unpublished", unpublished)
else:
logging.debug("local_track_unpublished for untracked publication sid %s", sid)
elif which == "local_track_republished":
# The SDK auto-republished a local track during a full
# reconnect: the underlying Track (and its bound source) is
Expand Down
Loading