Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions include/livekit/remote_track_publication.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

#pragma once

#include <cstdint>

#include "livekit/track.h"
#include "livekit/track_publication.h"
#include "livekit/visibility.h"

Expand All @@ -32,12 +35,77 @@ class LIVEKIT_API RemoteTrackPublication : public TrackPublication {
/// safe to accept proto::OwnedTrackPublication.
explicit RemoteTrackPublication(const proto::OwnedTrackPublication& owned);

/// @brief Returns the locally recorded subscription state.
///
/// @return True if the publication is marked as subscribed.
bool subscribed() const noexcept { return subscribed_; }

/// @brief Changes the desired subscription state for this publication.
///
/// @param subscribed True to subscribe; false to unsubscribe.
/// @throws std::runtime_error if the publication has an invalid FFI handle or
/// the FFI request fails.
void setSubscribed(bool subscribed);

/// @brief Returns whether media delivery is enabled for this publication.
///
/// @return True if media delivery is enabled.
bool enabled() const noexcept { return enabled_; }

/// @brief Enables or disables media delivery for this publication.
///
/// Disabling a subscribed publication reduces bandwidth without changing its
/// desired subscription state.
///
/// @param enabled True to enable media delivery; false to disable it.
/// @return True if an update was sent; false if the state is unchanged or
/// the publication is not subscribed.
/// @throws std::runtime_error if the publication has an invalid FFI handle or
/// the FFI request fails.
bool setEnabled(bool enabled);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const bool enabled?


/// @brief Returns the requested maximum simulcast quality.
///
/// @return The requested quality, or @c VideoQuality::HIGH when dimensions
/// are preferred.
VideoQuality videoQuality() const noexcept { return video_quality_; }

/// @brief Requests the maximum simulcast quality to receive.
///
/// This overrides a previous call to setVideoDimensions().
///
/// @param quality Maximum acceptable simulcast quality.
/// @return True if an update was sent; false if the quality is unchanged or
/// the publication is not a subscribed, simulcasted video track.
/// @throws std::runtime_error if the publication has an invalid FFI handle or
/// the FFI request fails.
bool setVideoQuality(VideoQuality quality);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const VideoQuality quality ?


/// @brief Requests the maximum video dimensions to receive for this publication.
///
/// The server selects the closest available simulcast or scalable-video layer.
/// This overrides a previous call to setVideoQuality().
/// Repeating the current dimensions does not send another request.
///
/// @param width Requested maximum width in pixels. Must be greater than zero.
/// @param height Requested maximum height in pixels. Must be greater than zero.
/// @return True if an update was sent; false if the dimensions are unchanged
/// or the publication is not a subscribed video track.
/// @throws std::runtime_error if the publication has an invalid FFI handle or
/// the FFI request fails.
bool setVideoDimensions(std::uint32_t width, std::uint32_t height);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const std::uint32_t width ?


private:
enum class VideoPreference {
DEFAULT,
DIMENSIONS,
QUALITY,
};

bool subscribed_{false};
bool enabled_{true};
VideoPreference video_preference_{VideoPreference::DEFAULT};
VideoQuality video_quality_{VideoQuality::HIGH};
};

} // namespace livekit
8 changes: 7 additions & 1 deletion include/livekit/track.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

#include "livekit/ffi_handle.h"
Expand Down Expand Up @@ -55,6 +54,13 @@ enum class StreamState {
STATE_PAUSED = 2,
};

/// @brief Maximum receive quality requested for a remote video track.
enum class VideoQuality {
LOW,
MEDIUM,
HIGH,
};

/// @brief Optional audio processing or encoding feature advertised for a track.
enum class AudioTrackFeature {
TF_STEREO = 0,
Expand Down
117 changes: 117 additions & 0 deletions src/remote_track_publication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "ffi.pb.h"
#include "ffi_client.h"
#include "lk_log.h"
#include "track_proto_converter.h"

namespace livekit {
Expand Down Expand Up @@ -47,4 +48,120 @@ void RemoteTrackPublication::setSubscribed(bool subscribed) {
subscribed_ = subscribed;
}

bool RemoteTrackPublication::setEnabled(bool enabled) {
if (!subscribed_ || track() == nullptr) {
LK_LOG_WARN("RemoteTrackPublication::setEnabled ignored for unsubscribed publication {}", sid());
return false;
}
if (enabled_ == enabled) {
return false;
}
if (ffiHandleId() == 0) {
throw std::runtime_error("RemoteTrackPublication::setEnabled: invalid FFI handle");
}

proto::FfiRequest req;
auto* msg = req.mutable_enable_remote_track_publication();
msg->set_track_publication_handle(static_cast<std::uint64_t>(ffiHandleId()));
msg->set_enabled(enabled);

const proto::FfiResponse resp = FfiClient::instance().sendRequest(req);
if (!resp.has_enable_remote_track_publication()) {
throw std::runtime_error("RemoteTrackPublication::setEnabled: FFI response missing enabled update result");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i get throwing for ffiHandleId() == 0 but do want to throw if we miss a response? Probably, otherwise we have broken our contract with the ffi?

}

enabled_ = enabled;
return true;
}

bool RemoteTrackPublication::setVideoQuality(VideoQuality quality) {
if (kind() != TrackKind::KIND_VIDEO) {
LK_LOG_WARN("RemoteTrackPublication::setVideoQuality ignored for non-video publication {}", sid());
return false;
}
if (!simulcasted()) {
LK_LOG_WARN("RemoteTrackPublication::setVideoQuality ignored for non-simulcast publication {}", sid());
return false;
}
if (!subscribed_ || track() == nullptr) {
LK_LOG_WARN("RemoteTrackPublication::setVideoQuality ignored for unsubscribed publication {}", sid());
return false;
}
if (video_preference_ == VideoPreference::QUALITY && video_quality_ == quality) {
return false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this means the quality that is desired is already set? If so, should we instead return true here? logic being they are getting the intended result of the function call.

}

proto::VideoQuality proto_quality;
switch (quality) {
case VideoQuality::LOW:
proto_quality = proto::VideoQuality::VIDEO_QUALITY_LOW;
break;
case VideoQuality::MEDIUM:
proto_quality = proto::VideoQuality::VIDEO_QUALITY_MEDIUM;
break;
case VideoQuality::HIGH:
proto_quality = proto::VideoQuality::VIDEO_QUALITY_HIGH;
break;
default:
LK_LOG_WARN("RemoteTrackPublication::setVideoQuality ignored invalid quality for publication {}", sid());
return false;
}
if (ffiHandleId() == 0) {
throw std::runtime_error("RemoteTrackPublication::setVideoQuality: invalid FFI handle");
}

proto::FfiRequest req;
auto* msg = req.mutable_set_remote_track_publication_quality();
msg->set_track_publication_handle(static_cast<std::uint64_t>(ffiHandleId()));
msg->set_quality(proto_quality);

const proto::FfiResponse resp = FfiClient::instance().sendRequest(req);
if (!resp.has_set_remote_track_publication_quality()) {
throw std::runtime_error("RemoteTrackPublication::setVideoQuality: FFI response missing quality update result");
}

video_preference_ = VideoPreference::QUALITY;
video_quality_ = quality;
return true;
}

bool RemoteTrackPublication::setVideoDimensions(std::uint32_t width, std::uint32_t height) {
if (kind() != TrackKind::KIND_VIDEO) {
LK_LOG_WARN("RemoteTrackPublication::setVideoDimensions ignored for non-video publication {}", sid());
return false;
}
if (width == 0 || height == 0) {
LK_LOG_WARN("RemoteTrackPublication::setVideoDimensions requires non-zero dimensions for publication {}", sid());
return false;
}
if (!subscribed_ || track() == nullptr) {
LK_LOG_WARN("RemoteTrackPublication::setVideoDimensions ignored for unsubscribed publication {}", sid());
return false;
}
if (video_preference_ == VideoPreference::DIMENSIONS && width_ == width && height_ == height) {
return false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same return true comment here

}
if (ffiHandleId() == 0) {
throw std::runtime_error("RemoteTrackPublication::setVideoDimensions: invalid FFI handle");
}

proto::FfiRequest req;
auto* msg = req.mutable_update_remote_track_publication_dimension();
msg->set_track_publication_handle(static_cast<std::uint64_t>(ffiHandleId()));
msg->set_width(width);
msg->set_height(height);

const proto::FfiResponse resp = FfiClient::instance().sendRequest(req);
if (!resp.has_update_remote_track_publication_dimension()) {
throw std::runtime_error(
"RemoteTrackPublication::setVideoDimensions: FFI response missing dimension update result");
}

width_ = width;
height_ = height;
Comment on lines +160 to +161

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Requesting a smaller video size makes the app report the wrong published resolution

The publication's reported video size is overwritten with the size the app asked to receive (width_ = width at src/remote_track_publication.cpp:160-161) instead of being kept separate, so apps reading the publication's resolution get the requested value rather than the resolution actually published by the sender.
Impact: UI or logic that relies on a remote publication's advertised video resolution silently shows incorrect numbers after a dimension request, and the original resolution is lost forever.

Metadata fields shared between publication info and the new dimension-preference state

width_/height_ are the protected publication-info members populated from the server's TrackInfo in the constructor (include/livekit/track_publication.h:83-84, exposed via width()/height() at include/livekit/track_publication.h:54-55). setVideoDimensions() reuses them both as the dedup key (src/remote_track_publication.cpp:141) and as the storage for the requested values (src/remote_track_publication.cpp:160-161). Nothing else ever writes these fields (no other setter exists), so once a request is made the actual published dimensions are unrecoverable. The new integration test even asserts the corrupted values (src/tests/integration/test_remote_track_publication.cpp:80-81). The JS SDK keeps requested dimensions in a separate videoDimensions field rather than mutating the publication's track info.

Prompt for agents
In RemoteTrackPublication::setVideoDimensions (src/remote_track_publication.cpp), the requested receive dimensions are stored into the base class's width_/height_ members, which hold the publication info reported by the server and are exposed publicly through TrackPublication::width()/height(). This destroys the real published resolution and makes those accessors report the app's request instead. Introduce private members on RemoteTrackPublication (e.g. requested_width_/requested_height_) to track the last requested dimensions, use them for the dedup check currently at line 141, and optionally expose them via a new accessor. Update the unit/integration tests that currently assert width()/height() equal the requested values.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

video_preference_ = VideoPreference::DIMENSIONS;
video_quality_ = VideoQuality::HIGH;
return true;
}

} // namespace livekit
109 changes: 109 additions & 0 deletions src/tests/integration/test_remote_track_publication.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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 <chrono>
#include <cstdint>
#include <memory>
#include <string>
#include <thread>

#include "tests/common/test_common.h"

namespace livekit::test {

class RemoteTrackPublicationServerTest : public LiveKitTestBase {};

TEST_F(RemoteTrackPublicationServerTest, PublicationControlsRoundTripAndDeduplicate) {
failIfNotConfigured();

Room publisher_room;
Room subscriber_room;
const RoomOptions options;

ASSERT_TRUE(subscriber_room.connect(config_.url, config_.token_b, options));
ASSERT_TRUE(publisher_room.connect(config_.url, config_.token_a, options));

const std::string publisher_identity = lockLocalParticipant(publisher_room)->identity();
ASSERT_TRUE(waitForParticipant(&subscriber_room, publisher_identity, 10s));

constexpr std::uint32_t kSourceWidth = 640;
constexpr std::uint32_t kSourceHeight = 360;
constexpr std::uint32_t kRequestedWidth = 320;
constexpr std::uint32_t kRequestedHeight = 180;
const std::string track_name = "remote-dimension-update";

auto source = std::make_shared<VideoSource>(kSourceWidth, kSourceHeight);
auto track = LocalVideoTrack::createLocalVideoTrack(track_name, source);
TrackPublishOptions publish_options;
publish_options.source = TrackSource::SOURCE_CAMERA;
publish_options.simulcast = true;
ASSERT_NO_THROW(lockLocalParticipant(publisher_room)->publishTrack(track, publish_options));

std::shared_ptr<RemoteTrackPublication> publication;
const auto subscription_deadline = std::chrono::steady_clock::now() + 10s;
while (std::chrono::steady_clock::now() < subscription_deadline) {
auto publisher = subscriber_room.remoteParticipant(publisher_identity).lock();
if (publisher != nullptr) {
for (const auto& [sid, candidate] : publisher->trackPublications()) {
(void)sid;
if (candidate != nullptr && candidate->name() == track_name && candidate->subscribed() &&
candidate->track() != nullptr) {
publication = candidate;
break;
}
}
}
if (publication != nullptr) {
break;
}
std::this_thread::sleep_for(10ms);
}

ASSERT_NE(publication, nullptr) << "Timed out waiting for remote video subscription";
ASSERT_EQ(publication->kind(), TrackKind::KIND_VIDEO);
ASSERT_TRUE(publication->simulcasted());

EXPECT_TRUE(publication->setVideoDimensions(kRequestedWidth, kRequestedHeight));
EXPECT_EQ(publication->width(), kRequestedWidth);
EXPECT_EQ(publication->height(), kRequestedHeight);
EXPECT_FALSE(publication->setVideoDimensions(kRequestedWidth, kRequestedHeight));

EXPECT_FALSE(publication->setVideoQuality(static_cast<VideoQuality>(-1)));
EXPECT_TRUE(publication->setVideoQuality(VideoQuality::LOW));
EXPECT_EQ(publication->videoQuality(), VideoQuality::LOW);
EXPECT_FALSE(publication->setVideoQuality(VideoQuality::LOW));
EXPECT_TRUE(publication->setVideoDimensions(kRequestedWidth, kRequestedHeight));
EXPECT_EQ(publication->videoQuality(), VideoQuality::HIGH);

EXPECT_TRUE(publication->setEnabled(false));
EXPECT_FALSE(publication->enabled());
EXPECT_FALSE(publication->setEnabled(false));
EXPECT_TRUE(publication->setEnabled(true));
EXPECT_TRUE(publication->enabled());

EXPECT_FALSE(publication->setVideoDimensions(0, kRequestedHeight));

publication->setSubscribed(false);
EXPECT_FALSE(publication->setVideoDimensions(kSourceWidth, kSourceHeight));
EXPECT_FALSE(publication->setVideoQuality(VideoQuality::MEDIUM));
EXPECT_FALSE(publication->setEnabled(false));

if (track->publication() != nullptr) {
lockLocalParticipant(publisher_room)->unpublishTrack(track->publication()->sid());
}
}

} // namespace livekit::test
Loading
Loading