@@ -468,6 +468,101 @@ TEST_F(DataTrackE2ETest, UnpublishUpdatesPublishedStateEndToEnd) {
468468 << " Remote track did not report unpublished state" ;
469469}
470470
471+ // Verifies that an auto-wired data callback (Room::addOnDataFrameCallback)
472+ // follows a republished track: after unpublish + republish under the same
473+ // (participant, track name) but a new SID, the previous reader is torn down and
474+ // a fresh reader delivers frames from the new publication.
475+ TEST_F (DataTrackE2ETest, RepublishRewiresDataCallbackToNewPublication) {
476+ const auto track_name = makeTrackName (" republish" );
477+
478+ std::vector<TestRoomConnectionOptions> room_configs (2 );
479+ room_configs[0 ].room_options .single_peer_connection = false ;
480+ room_configs[1 ].room_options .single_peer_connection = false ;
481+
482+ DataTrackPublishedDelegate subscriber_delegate;
483+ room_configs[1 ].delegate = &subscriber_delegate;
484+
485+ auto rooms = testRooms (room_configs);
486+ auto & publisher_room = rooms[0 ];
487+ auto & subscriber_room = rooms[1 ];
488+ const auto publisher_identity = lockLocalParticipant (*publisher_room)->identity ();
489+
490+ std::atomic<int > frames_received{0 };
491+ std::mutex payload_mutex;
492+ std::vector<std::uint8_t > last_payload;
493+ subscriber_room->addOnDataFrameCallback (publisher_identity, track_name,
494+ [&](const std::vector<std::uint8_t >& payload, std::optional<std::uint64_t >) {
495+ {
496+ const std::scoped_lock<std::mutex> lock (payload_mutex);
497+ last_payload = payload;
498+ }
499+ frames_received.fetch_add (1 );
500+ });
501+
502+ auto publish_with_retry = [&](const std::string& name) -> std::shared_ptr<LocalDataTrack> {
503+ std::shared_ptr<LocalDataTrack> track;
504+ waitForCondition (
505+ [&]() {
506+ auto result = lockLocalParticipant (*publisher_room)->publishDataTrack (name);
507+ if (result) {
508+ track = result.value ();
509+ return true ;
510+ }
511+ return false ;
512+ },
513+ kTrackWaitTimeout );
514+ return track;
515+ };
516+
517+ // First publication.
518+ auto first_track = publish_with_retry (track_name);
519+ ASSERT_NE (first_track, nullptr ) << " Failed to publish first data track" ;
520+ auto first_remote = subscriber_delegate.waitForTrack (kTrackWaitTimeout );
521+ ASSERT_NE (first_remote, nullptr ) << " Timed out waiting for first remote data track" ;
522+ const std::string first_sid = first_remote->info ().sid ;
523+
524+ DataTrackFrame first_frame;
525+ first_frame.payload .assign (64 , 0xA1 );
526+ ASSERT_TRUE (waitForCondition (
527+ [&]() {
528+ requirePushSuccess (first_track->tryPush (first_frame), " Failed to push first-publication frame" );
529+ return frames_received.load () > 0 ;
530+ },
531+ kTransportFrameTimeout ))
532+ << " Auto-wired callback never received a frame from the first publication" ;
533+
534+ // Unpublish: the reader for the first publication must be torn down.
535+ first_track->unpublishDataTrack ();
536+ ASSERT_TRUE (waitForCondition ([&]() { return !first_remote->isPublished (); }, kTrackWaitTimeout ))
537+ << " First remote track did not report unpublished state" ;
538+ const int frames_before_republish = frames_received.load ();
539+
540+ // Republish under the same name; the server assigns a new SID.
541+ auto second_track = publish_with_retry (track_name);
542+ ASSERT_NE (second_track, nullptr ) << " Failed to republish data track" ;
543+ auto remotes = subscriber_delegate.waitForTracks (2 , kTrackWaitTimeout );
544+ ASSERT_EQ (remotes.size (), 2u ) << " Timed out waiting for republished remote data track" ;
545+ const std::string second_sid = remotes.back ()->info ().sid ;
546+ EXPECT_NE (first_sid, second_sid) << " Republish should produce a new SID" ;
547+
548+ DataTrackFrame second_frame;
549+ second_frame.payload .assign (64 , 0xB2 );
550+ ASSERT_TRUE (waitForCondition (
551+ [&]() {
552+ requirePushSuccess (second_track->tryPush (second_frame), " Failed to push republished frame" );
553+ return frames_received.load () > frames_before_republish;
554+ },
555+ kTransportFrameTimeout ))
556+ << " Auto-wired callback did not re-wire to the republished track" ;
557+
558+ {
559+ const std::scoped_lock<std::mutex> lock (payload_mutex);
560+ EXPECT_EQ (last_payload, second_frame.payload ) << " Callback delivered stale payload after republish" ;
561+ }
562+
563+ second_track->unpublishDataTrack ();
564+ }
565+
471566TEST_F (DataTrackE2ETest, SubscribeAfterUnpublishReportsTerminalError) {
472567 const auto track_name = makeTrackName (" subscribe_after_unpublish" );
473568
0 commit comments