From 93264a60d7261900634c76d146e920ccb7c85104 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:01:06 -0700 Subject: [PATCH 1/5] Add test coverage --- .../test_encoded_video_ingestion.cpp | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/tests/integration/test_encoded_video_ingestion.cpp b/src/tests/integration/test_encoded_video_ingestion.cpp index dbe4cd09..fef1c0ad 100644 --- a/src/tests/integration/test_encoded_video_ingestion.cpp +++ b/src/tests/integration/test_encoded_video_ingestion.cpp @@ -43,6 +43,32 @@ constexpr std::array kH264KeyAccessUnit = { class EncodedVideoIngestionIntegrationTest : public LiveKitTestBase {}; +// Regression: LocalVideoTrack is created with the Rust placeholder SID +// "TR_unknown". After publishVideoTrack returns, Track::sid() must reflect the +// server-assigned SID (matching the publication), not the placeholder. +TEST_F(EncodedVideoIngestionIntegrationTest, PublishVideoTrackAssignsRealSid) { + failIfNotConfigured(); + + Room room; + const RoomOptions room_options; + ASSERT_TRUE(room.connect(config_.url, config_.token_a, room_options)); + + auto source = std::make_shared(VideoCodec::H264, 16, 16); + std::shared_ptr track; + ASSERT_NO_THROW(track = lockLocalParticipant(room)->publishVideoTrack("encoded-sid-check", source, + TrackSource::SOURCE_CAMERA)); + ASSERT_NE(track, nullptr); + ASSERT_NE(track->publication(), nullptr); + + const std::string& track_sid = track->sid(); + const std::string& publication_sid = track->publication()->sid(); + EXPECT_NE(track_sid, "TR_unknown") << "LocalVideoTrack::sid() still has the pre-publish placeholder"; + EXPECT_FALSE(track_sid.empty()); + EXPECT_EQ(track_sid, publication_sid); + + lockLocalParticipant(room)->unpublishTrack(publication_sid); +} + TEST_F(EncodedVideoIngestionIntegrationTest, PublisherSendsPreEncodedVideoToReceiver) { failIfNotConfigured(); From 1e190f11d853bc90a2ba8d276cefcba454279d92 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:12:03 -0700 Subject: [PATCH 2/5] Update SID after publication complete --- include/livekit/local_participant.h | 9 ++++++++- include/livekit/track.h | 11 ++++++++++- src/local_participant.cpp | 1 + .../integration/test_encoded_video_ingestion.cpp | 4 ++-- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/include/livekit/local_participant.h b/include/livekit/local_participant.h index 28ba83ef..a105bae8 100644 --- a/include/livekit/local_participant.h +++ b/include/livekit/local_participant.h @@ -136,9 +136,16 @@ class LIVEKIT_API LocalParticipant : public Participant { void setTrackSubscriptionPermissions(bool allow_all_participants, const std::vector& participant_permissions = {}); - /// Publish a local track to the room. + /// @brief Publish a local track to the room. /// + /// On success, updates @p track's SID from the resulting publication so + /// @c track->sid() matches the server-assigned publication SID (local tracks + /// are created with the Rust placeholder `"TR_unknown"`). + /// + /// @param track Local track to publish. Must be non-null with a valid FFI handle. + /// @param options Publish options (source, codec, simulcast, etc.). /// @throws std::runtime_error on error (e.g. publish failure). + /// @throws std::invalid_argument if @p track is null. void publishTrack(const std::shared_ptr& track, const TrackPublishOptions& options); /// Create a @ref LocalVideoTrack backed by the given @ref VideoSource, diff --git a/include/livekit/track.h b/include/livekit/track.h index 87ce4347..07487e19 100644 --- a/include/livekit/track.h +++ b/include/livekit/track.h @@ -113,10 +113,19 @@ class LIVEKIT_API Track { /// with this track. Default implementation is a no-op (e.g. remote tracks). virtual void setPublication(const std::shared_ptr& publication) noexcept { (void)publication; } - // Internal updates (called by Room) + // Internal updates (called by Room / LocalParticipant) void setStreamState(StreamState s) noexcept { state_ = s; } void setMuted(bool m) noexcept { muted_ = m; } void setName(std::string n) noexcept { name_ = std::move(n); } + /// @brief Replace the cached track SID. + /// + /// Local tracks are created with the Rust placeholder `"TR_unknown"`. + /// @ref LocalParticipant::publishTrack updates this to the server-assigned + /// SID so @c sid() matches the publication (same invariant as the Python + /// and Unity FFI bindings). + /// + /// @param sid New track SID. + void setSid(std::string sid) noexcept { sid_ = std::move(sid); } protected: Track(FfiHandle handle, std::string sid, std::string name, TrackKind kind, StreamState state, bool muted, diff --git a/src/local_participant.cpp b/src/local_participant.cpp index 5203ca83..42c70e58 100644 --- a/src/local_participant.cpp +++ b/src/local_participant.cpp @@ -202,6 +202,7 @@ void LocalParticipant::publishTrack(const std::shared_ptr& track, const T const std::string sid = publication->sid(); published_tracks_by_sid_[sid] = std::weak_ptr(track); + track->setSid(sid); track->setPublication(publication); } diff --git a/src/tests/integration/test_encoded_video_ingestion.cpp b/src/tests/integration/test_encoded_video_ingestion.cpp index fef1c0ad..7292b922 100644 --- a/src/tests/integration/test_encoded_video_ingestion.cpp +++ b/src/tests/integration/test_encoded_video_ingestion.cpp @@ -55,8 +55,8 @@ TEST_F(EncodedVideoIngestionIntegrationTest, PublishVideoTrackAssignsRealSid) { auto source = std::make_shared(VideoCodec::H264, 16, 16); std::shared_ptr track; - ASSERT_NO_THROW(track = lockLocalParticipant(room)->publishVideoTrack("encoded-sid-check", source, - TrackSource::SOURCE_CAMERA)); + ASSERT_NO_THROW( + track = lockLocalParticipant(room)->publishVideoTrack("encoded-sid-check", source, TrackSource::SOURCE_CAMERA)); ASSERT_NE(track, nullptr); ASSERT_NE(track->publication(), nullptr); From 37f26d7c4d141360173ff52e1afc8319563e3862 Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:13:32 -0700 Subject: [PATCH 3/5] Clean up --- include/livekit/local_participant.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/include/livekit/local_participant.h b/include/livekit/local_participant.h index a105bae8..b8d6ae6e 100644 --- a/include/livekit/local_participant.h +++ b/include/livekit/local_participant.h @@ -136,16 +136,10 @@ class LIVEKIT_API LocalParticipant : public Participant { void setTrackSubscriptionPermissions(bool allow_all_participants, const std::vector& participant_permissions = {}); - /// @brief Publish a local track to the room. + /// Publish a local track to the room. /// - /// On success, updates @p track's SID from the resulting publication so - /// @c track->sid() matches the server-assigned publication SID (local tracks - /// are created with the Rust placeholder `"TR_unknown"`). - /// - /// @param track Local track to publish. Must be non-null with a valid FFI handle. /// @param options Publish options (source, codec, simulcast, etc.). /// @throws std::runtime_error on error (e.g. publish failure). - /// @throws std::invalid_argument if @p track is null. void publishTrack(const std::shared_ptr& track, const TrackPublishOptions& options); /// Create a @ref LocalVideoTrack backed by the given @ref VideoSource, From 3f95a74e1efafef80c1373b3d8c23a4f905fb3cc Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:20:04 -0700 Subject: [PATCH 4/5] Make setSID protected --- include/livekit/local_audio_track.h | 3 +++ include/livekit/local_participant.h | 1 - include/livekit/local_video_track.h | 3 +++ include/livekit/track.h | 13 +++---------- src/local_participant.cpp | 1 - .../integration/test_encoded_video_ingestion.cpp | 5 +---- 6 files changed, 10 insertions(+), 16 deletions(-) diff --git a/include/livekit/local_audio_track.h b/include/livekit/local_audio_track.h index c5ab2e8c..95e8571d 100644 --- a/include/livekit/local_audio_track.h +++ b/include/livekit/local_audio_track.h @@ -116,6 +116,9 @@ class LIVEKIT_API LocalAudioTrack : public Track { /// the association. void setPublication(const std::shared_ptr& publication) noexcept override { local_publication_ = publication; + if (publication) { + setSid(publication->sid()); + } } private: diff --git a/include/livekit/local_participant.h b/include/livekit/local_participant.h index b8d6ae6e..28ba83ef 100644 --- a/include/livekit/local_participant.h +++ b/include/livekit/local_participant.h @@ -138,7 +138,6 @@ class LIVEKIT_API LocalParticipant : public Participant { /// Publish a local track to the room. /// - /// @param options Publish options (source, codec, simulcast, etc.). /// @throws std::runtime_error on error (e.g. publish failure). void publishTrack(const std::shared_ptr& track, const TrackPublishOptions& options); diff --git a/include/livekit/local_video_track.h b/include/livekit/local_video_track.h index c89893de..2bff4e5b 100644 --- a/include/livekit/local_video_track.h +++ b/include/livekit/local_video_track.h @@ -89,6 +89,9 @@ class LIVEKIT_API LocalVideoTrack : public Track { /// a true move but is a API-breaking change hence left for a future revision. void setPublication(const std::shared_ptr& publication) noexcept override { local_publication_ = publication; + if (publication) { + setSid(publication->sid()); + } } private: diff --git a/include/livekit/track.h b/include/livekit/track.h index 07487e19..bcf9c9b4 100644 --- a/include/livekit/track.h +++ b/include/livekit/track.h @@ -113,24 +113,17 @@ class LIVEKIT_API Track { /// with this track. Default implementation is a no-op (e.g. remote tracks). virtual void setPublication(const std::shared_ptr& publication) noexcept { (void)publication; } - // Internal updates (called by Room / LocalParticipant) + // Internal updates (called by Room) void setStreamState(StreamState s) noexcept { state_ = s; } void setMuted(bool m) noexcept { muted_ = m; } void setName(std::string n) noexcept { name_ = std::move(n); } - /// @brief Replace the cached track SID. - /// - /// Local tracks are created with the Rust placeholder `"TR_unknown"`. - /// @ref LocalParticipant::publishTrack updates this to the server-assigned - /// SID so @c sid() matches the publication (same invariant as the Python - /// and Unity FFI bindings). - /// - /// @param sid New track SID. - void setSid(std::string sid) noexcept { sid_ = std::move(sid); } protected: Track(FfiHandle handle, std::string sid, std::string name, TrackKind kind, StreamState state, bool muted, bool remote); + void setSid(std::string sid) noexcept { sid_ = std::move(sid); } + void setPublicationFields(std::optional source, std::optional simulcasted, std::optional width, std::optional height, std::optional mime_type); diff --git a/src/local_participant.cpp b/src/local_participant.cpp index 42c70e58..5203ca83 100644 --- a/src/local_participant.cpp +++ b/src/local_participant.cpp @@ -202,7 +202,6 @@ void LocalParticipant::publishTrack(const std::shared_ptr& track, const T const std::string sid = publication->sid(); published_tracks_by_sid_[sid] = std::weak_ptr(track); - track->setSid(sid); track->setPublication(publication); } diff --git a/src/tests/integration/test_encoded_video_ingestion.cpp b/src/tests/integration/test_encoded_video_ingestion.cpp index 7292b922..e3246e77 100644 --- a/src/tests/integration/test_encoded_video_ingestion.cpp +++ b/src/tests/integration/test_encoded_video_ingestion.cpp @@ -43,9 +43,6 @@ constexpr std::array kH264KeyAccessUnit = { class EncodedVideoIngestionIntegrationTest : public LiveKitTestBase {}; -// Regression: LocalVideoTrack is created with the Rust placeholder SID -// "TR_unknown". After publishVideoTrack returns, Track::sid() must reflect the -// server-assigned SID (matching the publication), not the placeholder. TEST_F(EncodedVideoIngestionIntegrationTest, PublishVideoTrackAssignsRealSid) { failIfNotConfigured(); @@ -62,7 +59,7 @@ TEST_F(EncodedVideoIngestionIntegrationTest, PublishVideoTrackAssignsRealSid) { const std::string& track_sid = track->sid(); const std::string& publication_sid = track->publication()->sid(); - EXPECT_NE(track_sid, "TR_unknown") << "LocalVideoTrack::sid() still has the pre-publish placeholder"; + EXPECT_NE(track_sid, "TR_unknown"); EXPECT_FALSE(track_sid.empty()); EXPECT_EQ(track_sid, publication_sid); From ade2cefa594cb5c9ce05058360c225bc14b3ecaf Mon Sep 17 00:00:00 2001 From: Jacob Gelman <3182119+ladvoc@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:24:36 -0700 Subject: [PATCH 5/5] Also test audio track --- .../test_encoded_video_ingestion.cpp | 23 ------ .../test_local_track_publish_sid.cpp | 76 +++++++++++++++++++ 2 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 src/tests/integration/test_local_track_publish_sid.cpp diff --git a/src/tests/integration/test_encoded_video_ingestion.cpp b/src/tests/integration/test_encoded_video_ingestion.cpp index e3246e77..dbe4cd09 100644 --- a/src/tests/integration/test_encoded_video_ingestion.cpp +++ b/src/tests/integration/test_encoded_video_ingestion.cpp @@ -43,29 +43,6 @@ constexpr std::array kH264KeyAccessUnit = { class EncodedVideoIngestionIntegrationTest : public LiveKitTestBase {}; -TEST_F(EncodedVideoIngestionIntegrationTest, PublishVideoTrackAssignsRealSid) { - failIfNotConfigured(); - - Room room; - const RoomOptions room_options; - ASSERT_TRUE(room.connect(config_.url, config_.token_a, room_options)); - - auto source = std::make_shared(VideoCodec::H264, 16, 16); - std::shared_ptr track; - ASSERT_NO_THROW( - track = lockLocalParticipant(room)->publishVideoTrack("encoded-sid-check", source, TrackSource::SOURCE_CAMERA)); - ASSERT_NE(track, nullptr); - ASSERT_NE(track->publication(), nullptr); - - const std::string& track_sid = track->sid(); - const std::string& publication_sid = track->publication()->sid(); - EXPECT_NE(track_sid, "TR_unknown"); - EXPECT_FALSE(track_sid.empty()); - EXPECT_EQ(track_sid, publication_sid); - - lockLocalParticipant(room)->unpublishTrack(publication_sid); -} - TEST_F(EncodedVideoIngestionIntegrationTest, PublisherSendsPreEncodedVideoToReceiver) { failIfNotConfigured(); diff --git a/src/tests/integration/test_local_track_publish_sid.cpp b/src/tests/integration/test_local_track_publish_sid.cpp new file mode 100644 index 00000000..91fbf33b --- /dev/null +++ b/src/tests/integration/test_local_track_publish_sid.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2026 LiveKit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include "../common/audio_utils.h" +#include "../common/test_common.h" + +namespace livekit::test { +namespace { + +void expectTrackSidAssigned(const Track& track, const LocalTrackPublication& publication) { + const std::string& track_sid = track.sid(); + const std::string& publication_sid = publication.sid(); + EXPECT_NE(track_sid, "TR_unknown"); + EXPECT_FALSE(track_sid.empty()); + EXPECT_EQ(track_sid, publication_sid); +} + +} // namespace + +class LocalTrackPublishSidTest : public LiveKitTestBase {}; + +TEST_F(LocalTrackPublishSidTest, PublishVideoTrackAssignsSid) { + failIfNotConfigured(); + + Room room; + const RoomOptions room_options; + ASSERT_TRUE(room.connect(config_.url, config_.token_a, room_options)); + + auto source = std::make_shared(VideoCodec::H264, 16, 16); + std::shared_ptr track; + ASSERT_NO_THROW( + track = lockLocalParticipant(room)->publishVideoTrack("video-sid-check", source, TrackSource::SOURCE_CAMERA)); + ASSERT_NE(track, nullptr); + ASSERT_NE(track->publication(), nullptr); + + expectTrackSidAssigned(*track, *track->publication()); + lockLocalParticipant(room)->unpublishTrack(track->publication()->sid()); +} + +TEST_F(LocalTrackPublishSidTest, PublishAudioTrackAssignsSid) { + failIfNotConfigured(); + + Room room; + const RoomOptions room_options; + ASSERT_TRUE(room.connect(config_.url, config_.token_a, room_options)); + + auto source = std::make_shared(kDefaultAudioSampleRate, kDefaultAudioChannels, 0); + std::shared_ptr track; + ASSERT_NO_THROW( + track = lockLocalParticipant(room)->publishAudioTrack("audio-sid-check", source, TrackSource::SOURCE_MICROPHONE)); + ASSERT_NE(track, nullptr); + ASSERT_NE(track->publication(), nullptr); + + expectTrackSidAssigned(*track, *track->publication()); + lockLocalParticipant(room)->unpublishTrack(track->publication()->sid()); +} + +} // namespace livekit::test