Skip to content

Commit 2a21a8a

Browse files
Fix local track publication event ordering
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 80c5591 commit 2a21a8a

6 files changed

Lines changed: 79 additions & 36 deletions

File tree

‎include/livekit/local_participant.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ class LIVEKIT_API LocalParticipant : public Participant {
296296
/// Publication SID → local track (@ref unpublishTrack clears the track’s
297297
/// cached publication). @c mutable so @ref trackPublications() const can
298298
/// prune expired @c weak_ptr entries.
299+
mutable std::mutex published_tracks_mutex_;
299300
mutable TrackMap published_tracks_by_sid_;
300301

301302
std::unordered_map<std::string, RpcHandler> rpc_handlers_;

‎src/ffi_client.cpp‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <cassert>
2020
#include <csignal>
2121
#include <cstdio>
22+
#include <exception>
2223
#include <string>
2324
#include <type_traits>
2425

@@ -694,9 +695,9 @@ std::future<void> FfiClient::simulateScenarioAsync(uintptr_t room_handle, int sc
694695
}
695696

696697
// Participant APIs Implementation
697-
std::future<proto::OwnedTrackPublication> FfiClient::publishTrackAsync(std::uint64_t local_participant_handle,
698-
std::uint64_t track_handle,
699-
const TrackPublishOptions& options) {
698+
std::future<proto::OwnedTrackPublication> FfiClient::publishTrackAsync(
699+
std::uint64_t local_participant_handle, std::uint64_t track_handle, const TrackPublishOptions& options,
700+
std::function<void(const proto::OwnedTrackPublication&)> on_success) {
700701
// Generate client-side async_id first
701702
const AsyncId async_id = generateAsyncId();
702703

@@ -708,7 +709,8 @@ std::future<proto::OwnedTrackPublication> FfiClient::publishTrackAsync(std::uint
708709
return event.has_publish_track() && event.publish_track().async_id() == async_id;
709710
},
710711
// Handler: resolve with publication or throw error
711-
[](const proto::FfiEvent& event, std::promise<proto::OwnedTrackPublication>& pr) {
712+
[on_success = std::move(on_success)](const proto::FfiEvent& event,
713+
std::promise<proto::OwnedTrackPublication>& pr) {
712714
const auto& cb = event.publish_track();
713715

714716
// Oneof message { string error = 2; OwnedTrackPublication publication =
@@ -723,7 +725,14 @@ std::future<proto::OwnedTrackPublication> FfiClient::publishTrackAsync(std::uint
723725
}
724726

725727
const proto::OwnedTrackPublication& pub = cb.publication();
726-
pr.set_value(pub);
728+
try {
729+
if (on_success) {
730+
on_success(pub);
731+
}
732+
pr.set_value(pub);
733+
} catch (...) {
734+
pr.set_exception(std::current_exception());
735+
}
727736
});
728737

729738
// Build and send the request

‎src/ffi_client.h‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ class LIVEKIT_INTERNAL_API FfiClient {
109109
std::future<void> simulateScenarioAsync(uintptr_t room_handle, int scenario);
110110

111111
// Participant APIs
112-
std::future<proto::OwnedTrackPublication> publishTrackAsync(std::uint64_t local_participant_handle,
113-
std::uint64_t track_handle,
114-
const TrackPublishOptions& options);
112+
std::future<proto::OwnedTrackPublication> publishTrackAsync(
113+
std::uint64_t local_participant_handle, std::uint64_t track_handle, const TrackPublishOptions& options,
114+
std::function<void(const proto::OwnedTrackPublication&)> on_success = {});
115115
std::future<void> unpublishTrackAsync(std::uint64_t local_participant_handle, const std::string& track_sid,
116116
bool stop_on_unpublish);
117117
std::future<void> publishDataAsync(std::uint64_t local_participant_handle, const std::uint8_t* data_ptr,

‎src/local_participant.cpp‎

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -190,19 +190,18 @@ void LocalParticipant::publishTrack(const std::shared_ptr<Track>& track, const T
190190
if (track_handle == 0) {
191191
throw std::runtime_error("LocalParticipant::publishTrack: invalid track FFI handle");
192192
}
193-
auto fut = FfiClient::instance().publishTrackAsync(static_cast<std::uint64_t>(participant_handle),
194-
static_cast<std::uint64_t>(track_handle), options);
195-
196-
// Will throw if the async op fails (error in callback).
197-
const proto::OwnedTrackPublication owned_pub = fut.get();
198-
199-
// Construct a LocalTrackPublication from the proto publication.
200-
auto publication = std::make_shared<LocalTrackPublication>(owned_pub);
201-
202-
const std::string sid = publication->sid();
203-
published_tracks_by_sid_[sid] = std::weak_ptr<Track>(track);
204-
205-
track->setPublication(publication);
193+
auto fut = FfiClient::instance().publishTrackAsync(
194+
static_cast<std::uint64_t>(participant_handle), static_cast<std::uint64_t>(track_handle), options,
195+
[this, track](const proto::OwnedTrackPublication& owned_pub) {
196+
auto publication = std::make_shared<LocalTrackPublication>(owned_pub);
197+
const std::scoped_lock<std::mutex> guard(published_tracks_mutex_);
198+
published_tracks_by_sid_[publication->sid()] = track;
199+
track->setPublication(publication);
200+
});
201+
202+
// Publication state is installed on the FFI thread before this future is
203+
// completed and before Rust can emit LocalTrackPublished.
204+
(void)fut.get();
206205
}
207206

208207
std::shared_ptr<LocalVideoTrack> LocalParticipant::publishVideoTrack(const std::string& name,
@@ -248,6 +247,7 @@ void LocalParticipant::unpublishTrack(const std::string& track_sid) {
248247

249248
fut.get();
250249

250+
const std::scoped_lock<std::mutex> guard(published_tracks_mutex_);
251251
if (auto it = published_tracks_by_sid_.find(track_sid); it != published_tracks_by_sid_.end()) {
252252
if (auto t = it->second.lock()) {
253253
t->setPublication(nullptr);
@@ -257,6 +257,7 @@ void LocalParticipant::unpublishTrack(const std::string& track_sid) {
257257
}
258258

259259
LocalParticipant::PublicationMap LocalParticipant::trackPublications() const {
260+
const std::scoped_lock<std::mutex> guard(published_tracks_mutex_);
260261
PublicationMap out;
261262
for (auto it = published_tracks_by_sid_.begin(); it != published_tracks_by_sid_.end();) {
262263
auto t = it->second.lock();
@@ -494,6 +495,7 @@ void LocalParticipant::handleRpcMethodInvocation(uint64_t invocation_id, const s
494495
}
495496

496497
std::shared_ptr<TrackPublication> LocalParticipant::findTrackPublication(const std::string& sid) const {
498+
const std::scoped_lock<std::mutex> guard(published_tracks_mutex_);
497499
auto it = published_tracks_by_sid_.find(sid);
498500
if (it == published_tracks_by_sid_.end()) {
499501
return nullptr;

‎src/room.cpp‎

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,6 @@ std::shared_ptr<LocalTrackPublication> localTrackPublication(const std::shared_p
6363
return nullptr;
6464
}
6565

66-
void updateLocalTrackPublicationInfo(LocalTrackPublication& publication, const proto::TrackPublicationInfo& info) {
67-
publication.sid_ = info.sid();
68-
publication.name_ = info.name();
69-
publication.kind_ = fromProto(info.kind());
70-
publication.source_ = fromProto(info.source());
71-
publication.simulcasted_ = info.simulcasted();
72-
publication.width_ = info.width();
73-
publication.height_ = info.height();
74-
publication.mime_type_ = info.mime_type();
75-
publication.muted_ = info.muted();
76-
publication.encryption_type_ = static_cast<EncryptionType>(info.encryption_type());
77-
publication.audio_features_ = convertAudioFeatures(info.audio_features());
78-
}
79-
8066
std::shared_ptr<livekit::RemoteParticipant> createRemoteParticipant(const proto::OwnedParticipant& owned) {
8167
const auto& pinfo = owned.info();
8268
std::unordered_map<std::string, std::string> attrs;
@@ -632,6 +618,7 @@ void Room::onEvent(const FfiEvent& event) {
632618
const auto& ltr = re.local_track_republished();
633619
const std::string& previous_sid = ltr.previous_sid();
634620

621+
const std::scoped_lock<std::mutex> publications_guard(local_participant_->published_tracks_mutex_);
635622
auto& published = local_participant_->published_tracks_by_sid_;
636623
auto it = published.find(previous_sid);
637624
if (it == published.end()) {
@@ -649,7 +636,18 @@ void Room::onEvent(const FfiEvent& event) {
649636
LK_LOG_WARN("local_track_republished missing publication for sid: {}", previous_sid);
650637
break;
651638
}
652-
updateLocalTrackPublicationInfo(*publication, ltr.info());
639+
const auto& info = ltr.info();
640+
publication->sid_ = info.sid();
641+
publication->name_ = info.name();
642+
publication->kind_ = fromProto(info.kind());
643+
publication->source_ = fromProto(info.source());
644+
publication->simulcasted_ = info.simulcasted();
645+
publication->width_ = info.width();
646+
publication->height_ = info.height();
647+
publication->mime_type_ = info.mime_type();
648+
publication->muted_ = info.muted();
649+
publication->encryption_type_ = static_cast<EncryptionType>(info.encryption_type());
650+
publication->audio_features_ = convertAudioFeatures(info.audio_features());
653651
published.erase(it);
654652
published[publication->sid()] = track;
655653
track->setPublication(publication);

‎src/tests/integration/test_local_track_publish_sid.cpp‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
#include <gtest/gtest.h>
1818

1919
#include <chrono>
20+
#include <condition_variable>
2021
#include <memory>
22+
#include <mutex>
2123
#include <string>
2224
#include <thread>
25+
#include <unordered_set>
2326

2427
#include "../common/audio_utils.h"
2528
#include "../common/room_test_access.h"
@@ -31,6 +34,30 @@ namespace {
3134

3235
using namespace std::chrono_literals;
3336

37+
class LocalPublicationDelegate final : public RoomDelegate {
38+
public:
39+
void onLocalTrackPublished(Room&, const LocalTrackPublishedEvent& event) override {
40+
if (!event.publication) {
41+
return;
42+
}
43+
{
44+
const std::scoped_lock<std::mutex> guard(mutex_);
45+
published_sids_.insert(event.publication->sid());
46+
}
47+
cv_.notify_all();
48+
}
49+
50+
bool waitForPublished(const std::string& sid, std::chrono::milliseconds timeout) {
51+
std::unique_lock<std::mutex> lock(mutex_);
52+
return cv_.wait_for(lock, timeout, [&] { return published_sids_.count(sid) != 0; });
53+
}
54+
55+
private:
56+
std::mutex mutex_;
57+
std::condition_variable cv_;
58+
std::unordered_set<std::string> published_sids_;
59+
};
60+
3461
void expectTrackSidAssigned(const Track& track, const LocalTrackPublication& publication) {
3562
const std::string& track_sid = track.sid();
3663
const std::string& publication_sid = publication.sid();
@@ -58,6 +85,8 @@ TEST_F(LocalTrackPublishSidTest, PublishVideoTrackAssignsSid) {
5885
failIfNotConfigured();
5986

6087
Room room;
88+
LocalPublicationDelegate delegate;
89+
room.setDelegate(&delegate);
6190
const RoomOptions room_options;
6291
ASSERT_TRUE(room.connect(config_.url, config_.token_a, room_options));
6392

@@ -69,13 +98,16 @@ TEST_F(LocalTrackPublishSidTest, PublishVideoTrackAssignsSid) {
6998
ASSERT_NE(track->publication(), nullptr);
7099

71100
expectTrackSidAssigned(*track, *track->publication());
101+
EXPECT_TRUE(delegate.waitForPublished(track->sid(), 5s));
72102
lockLocalParticipant(room)->unpublishTrack(track->publication()->sid());
73103
}
74104

75105
TEST_F(LocalTrackPublishSidTest, PublishAudioTrackAssignsSid) {
76106
failIfNotConfigured();
77107

78108
Room room;
109+
LocalPublicationDelegate delegate;
110+
room.setDelegate(&delegate);
79111
const RoomOptions room_options;
80112
ASSERT_TRUE(room.connect(config_.url, config_.token_a, room_options));
81113

@@ -87,6 +119,7 @@ TEST_F(LocalTrackPublishSidTest, PublishAudioTrackAssignsSid) {
87119
ASSERT_NE(track->publication(), nullptr);
88120

89121
expectTrackSidAssigned(*track, *track->publication());
122+
EXPECT_TRUE(delegate.waitForPublished(track->sid(), 5s));
90123
lockLocalParticipant(room)->unpublishTrack(track->publication()->sid());
91124
}
92125

0 commit comments

Comments
 (0)