|
33 | 33 | from ._proto.rpc_pb2 import RpcMethodInvocationEvent |
34 | 34 | from ._utils import BroadcastQueue |
35 | 35 | from .e2ee import E2EEManager, E2EEOptions |
| 36 | +from .log import logger |
36 | 37 | from .participant import ( |
37 | 38 | LocalParticipant, |
38 | 39 | Participant, |
@@ -821,11 +822,22 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None: |
821 | 822 | rparticipant._track_publications[rpublication.sid] = rpublication |
822 | 823 | self.emit("track_published", rpublication, rparticipant) |
823 | 824 | elif which == "track_unpublished": |
824 | | - rparticipant = self._remote_participants[event.track_unpublished.participant_identity] |
825 | | - rpublication = rparticipant._track_publications.pop( |
826 | | - event.track_unpublished.publication_sid |
827 | | - ) |
828 | | - self.emit("track_unpublished", rpublication, rparticipant) |
| 825 | + # The participant or publication may already have been removed by a |
| 826 | + # racing disconnect or a duplicate event, so both lookups are done |
| 827 | + # defensively and the emit is skipped when the entry is gone, |
| 828 | + # mirroring the local_track_unpublished handler, instead of raising a |
| 829 | + # KeyError that _listen_task logs as an error. |
| 830 | + identity = event.track_unpublished.participant_identity |
| 831 | + sid = event.track_unpublished.publication_sid |
| 832 | + rp = self._remote_participants.get(identity) |
| 833 | + if rp is not None: |
| 834 | + rpub = rp._track_publications.pop(sid, None) |
| 835 | + if rpub is not None: |
| 836 | + self.emit("track_unpublished", rpub, rp) |
| 837 | + else: |
| 838 | + logger.debug("track_unpublished for untracked publication sid %s", sid) |
| 839 | + else: |
| 840 | + logger.debug("track_unpublished for untracked participant %s", identity) |
829 | 841 | elif which == "track_subscribed": |
830 | 842 | owned_track_info = event.track_subscribed.track |
831 | 843 | track_info = owned_track_info.info |
|
0 commit comments