Skip to content

Commit cb47aec

Browse files
committed
fix: ensure that audio stream metadata is updated properly on room full reconnect
When the room does a full reconnect, make sure the audiostream metadata gets a new push with the updated track sid
1 parent ea1de09 commit cb47aec

2 files changed

Lines changed: 86 additions & 0 deletions

File tree

livekit-rtc/livekit/rtc/room.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,15 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None:
778778
del self.local_participant._track_publications[previous_sid]
779779
republished._info = event.local_track_republished.info
780780
self.local_participant._track_publications[republished.sid] = republished
781+
if republished.track is not None:
782+
# Keep the local-track invariant (track.sid == publication.sid,
783+
# set at publish_track) intact across republish, then re-push
784+
# metadata so any attached FrameProcessor learns the new
785+
# publication SID / credentials. _set_room with the same room
786+
# is a no-op for the token_refreshed listener but re-fans the
787+
# metadata to every registered AudioStream.
788+
republished.track._info.sid = republished.sid
789+
republished.track._set_room(self)
781790
self.emit("local_track_republished", republished, previous_sid)
782791
elif which == "local_track_subscribed":
783792
sid = event.local_track_subscribed.track_sid

livekit-rtc/tests/test_audio_stream_room_lifecycle.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,20 @@ def _make_remote_publication(sid: str) -> rtc.RemoteTrackPublication:
5555
return pub
5656

5757

58+
def _make_local_participant(identity: str) -> rtc.LocalParticipant:
59+
p = rtc.LocalParticipant.__new__(rtc.LocalParticipant)
60+
p._info = proto_participant.ParticipantInfo(identity=identity)
61+
p._track_publications = {}
62+
return p
63+
64+
65+
def _make_local_publication(sid: str) -> rtc.LocalTrackPublication:
66+
pub = rtc.LocalTrackPublication.__new__(rtc.LocalTrackPublication)
67+
pub._info = proto_track.TrackPublicationInfo(sid=sid)
68+
pub._track = None
69+
return pub
70+
71+
5872
def _make_track(sid: str = "TR_x") -> rtc.Track:
5973
track = rtc.Track.__new__(rtc.Track)
6074
track._info = proto_track.TrackInfo(sid=sid)
@@ -468,3 +482,66 @@ async def test_aclose_leaves_processor_open_when_auto_close_false() -> None:
468482
await stream.aclose()
469483

470484
assert processor.close_calls == 0
485+
486+
487+
# -- local_track_republished regression ---------------------------------------
488+
489+
490+
def test_local_track_republished_updates_track_sid_and_repushes_metadata() -> None:
491+
"""A full-reconnect republish re-issues the publication SID. The handler must
492+
keep the local-track invariant (track.sid == publication.sid) intact and
493+
re-push metadata so attached processors learn the new SID — otherwise the
494+
SID-based lookup in Track._push_processor_metadata_to_stream fails and the
495+
processor receives empty participant_identity / publication_sid.
496+
"""
497+
room = _make_room(name="room-1", token="tok-1", url="wss://r")
498+
local = _make_local_participant("agent")
499+
room._local_participant = local
500+
501+
# Local track published under the OLD sid (mirrors publish_track:
502+
# track.sid == publication.sid).
503+
track = _make_track(sid="OLD")
504+
publication = _make_local_publication(sid="OLD")
505+
publication._track = track
506+
local._track_publications["OLD"] = publication
507+
track._set_room(room)
508+
509+
# Processor attached before the reconnect sees the OLD sid.
510+
processor = _RecordingProcessor()
511+
_stream = _make_stream(track=track, processor=processor) # noqa: F841
512+
assert processor.stream_info_calls[-1] == {
513+
"room_name": "room-1",
514+
"participant_identity": "agent",
515+
"publication_sid": "OLD",
516+
}
517+
518+
# Dispatch a synthetic local_track_republished re-issuing the sid as NEW.
519+
event = proto_room.RoomEvent(
520+
local_track_republished=proto_room.LocalTrackRepublished(
521+
previous_sid="OLD",
522+
info=proto_track.TrackPublicationInfo(sid="NEW"),
523+
)
524+
)
525+
room._on_room_event(event)
526+
527+
# Invariant restored + dict rekeyed.
528+
assert track.sid == "NEW"
529+
assert "NEW" in local._track_publications
530+
assert "OLD" not in local._track_publications
531+
532+
# Existing attached processor was re-pushed with the NEW sid (non-empty).
533+
assert processor.stream_info_calls[-1] == {
534+
"room_name": "room-1",
535+
"participant_identity": "agent",
536+
"publication_sid": "NEW",
537+
}
538+
539+
# Regression guard: a stream created AFTER republish also resolves NEW
540+
# (the exact path from the bug report — stale track.sid would yield "").
541+
processor2 = _RecordingProcessor()
542+
_stream2 = _make_stream(track=track, processor=processor2) # noqa: F841
543+
assert processor2.stream_info_calls[-1] == {
544+
"room_name": "room-1",
545+
"participant_identity": "agent",
546+
"publication_sid": "NEW",
547+
}

0 commit comments

Comments
 (0)