Skip to content
Draft
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);

/// @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);

/// @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);

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");
}

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;
}

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;
}
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;
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