Skip to content

Commit 0acb97e

Browse files
Add remaining available RoomOptions fields (#161)
1 parent 29846aa commit 0acb97e

5 files changed

Lines changed: 214 additions & 50 deletions

File tree

include/livekit/room.h

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#pragma once
1818

19+
#include <chrono>
20+
#include <cstdint>
1921
#include <future>
2022
#include <memory>
2123
#include <mutex>
@@ -72,21 +74,53 @@ struct RoomOptions {
7274
/// This is CRITICAL. Without auto_subscribe, you will never receive:
7375
/// - `track_subscribed` events
7476
/// - remote audio/video frames
77+
///
78+
/// @see https://docs.livekit.io/transport/media/subscribe/#selective-subscription
7579
bool auto_subscribe = true;
7680

81+
/// Enable adaptive stream for subscribed video tracks.
82+
///
83+
/// When enabled, the SDK tells the server it may adjust the video layers sent
84+
/// to this client based on what the application is currently rendering. This
85+
/// lets the server pause or downscale subscribed video that is off-screen,
86+
/// hidden, or only needed at a smaller size, reducing downstream bandwidth and
87+
/// decode work. This affects media received by this room; use @ref dynacast
88+
/// to control how this client publishes layers to others.
89+
///
90+
/// @see https://docs.livekit.io/transport/media/subscribe/#adaptive-stream
91+
///
92+
/// If unset, the Rust SDK default is used.
93+
std::optional<bool> adaptive_stream;
94+
7795
/// Enable dynacast (server sends optimal layers depending on subscribers).
96+
///
97+
/// @see https://docs.livekit.io/transport/media/publish/#dynacast
7898
bool dynacast = false;
7999

100+
/// Optional end-to-end encryption settings.
101+
///
102+
/// @see https://docs.livekit.io/transport/encryption/
103+
std::optional<E2EEOptions> encryption;
104+
105+
/// Optional WebRTC configuration (ICE policy, servers, etc.)
106+
///
107+
/// @see https://docs.livekit.io/intro/basics/connect/#connection-reliability
108+
std::optional<RtcConfig> rtc_config;
109+
110+
/// Number of retries for the initial room join after the first attempt.
111+
///
112+
/// If unset, the Rust SDK default is used.
113+
std::optional<std::uint32_t> join_retries;
114+
80115
/// Enable single peer connection mode. When true, uses one RTCPeerConnection
81116
/// for both publishing and subscribing instead of two separate connections.
82117
/// Falls back to dual peer connection if the server doesn't support single PC.
83118
bool single_peer_connection = true;
84119

85-
/// Optional WebRTC configuration (ICE policy, servers, etc.)
86-
std::optional<RtcConfig> rtc_config;
87-
88-
/// Optional end-to-end encryption settings.
89-
std::optional<E2EEOptions> encryption;
120+
/// Timeout for each individual signal connection attempt.
121+
///
122+
/// If unset, the Rust SDK default is used.
123+
std::optional<std::chrono::milliseconds> connect_timeout;
90124
};
91125

92126
/// Represents a LiveKit room session.

src/ffi_client.cpp

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
#include <cassert>
2020
#include <csignal>
2121
#include <cstdio>
22+
#include <string>
23+
#include <type_traits>
2224

2325
#include "data_track.pb.h"
24-
#include "e2ee.pb.h"
2526
#include "ffi.pb.h"
2627
#include "livekit/build.h"
2728
#include "livekit/data_track_error.h"
28-
#include "livekit/e2ee.h"
2929
#include "livekit/ffi_handle.h"
3030
#include "livekit/room.h"
3131
#include "livekit/rpc_error.h"
@@ -37,11 +37,27 @@
3737
namespace livekit {
3838

3939
namespace {
40+
4041
inline void logAndThrow(const std::string& error_msg) {
4142
LK_LOG_ERROR("LiveKit SDK Error: {}", error_msg);
4243
throw std::runtime_error(error_msg);
4344
}
4445

46+
// Helper for debug logging of optional values
47+
const auto optional_to_string = [](const auto& value) -> std::string {
48+
if (!value) {
49+
return "<unset>";
50+
}
51+
using Value = std::decay_t<decltype(*value)>;
52+
if constexpr (std::is_same_v<Value, bool>) {
53+
return *value ? "true" : "false";
54+
} else if constexpr (std::is_same_v<Value, std::chrono::milliseconds>) {
55+
return std::to_string(value->count());
56+
} else {
57+
return std::to_string(*value);
58+
}
59+
};
60+
4561
Result<proto::OwnedDataTrackStream, SubscribeDataTrackError> subscribeDataTrackFailure(SubscribeDataTrackErrorCode code,
4662
const std::string& message) {
4763
LK_LOG_WARN("Subscribe data track failed: code={} message={}", static_cast<std::uint32_t>(code), message);
@@ -471,50 +487,14 @@ std::future<proto::ConnectCallback> FfiClient::connectAsync(const std::string& u
471487
connect->set_url(url);
472488
connect->set_token(token);
473489
connect->set_request_async_id(async_id);
474-
auto* opts = connect->mutable_options();
475-
opts->set_auto_subscribe(options.auto_subscribe);
476-
opts->set_dynacast(options.dynacast);
477-
opts->set_single_peer_connection(options.single_peer_connection);
490+
connect->mutable_options()->CopyFrom(toProto(options));
478491

479492
LK_LOG_DEBUG(
480-
"[FfiClient] connectAsync: auto_subscribe={}, dynacast={}, "
481-
"single_peer_connection={}",
482-
options.auto_subscribe, options.dynacast, options.single_peer_connection);
483-
484-
// --- E2EE / encryption (optional) ---
485-
if (options.encryption.has_value()) {
486-
const E2EEOptions& e2ee = *options.encryption;
487-
const auto& kpo = e2ee.key_provider_options;
488-
489-
auto* enc = opts->mutable_encryption();
490-
enc->set_encryption_type(static_cast<proto::EncryptionType>(e2ee.encryption_type));
491-
enc->mutable_key_provider_options()->CopyFrom(toProto(kpo));
492-
}
493-
494-
// --- RTC configuration (optional) ---
495-
if (options.rtc_config.has_value()) {
496-
const RtcConfig& rc = *options.rtc_config;
497-
auto* rtc = opts->mutable_rtc_config();
498-
499-
rtc->set_ice_transport_type(static_cast<proto::IceTransportType>(rc.ice_transport_type));
500-
rtc->set_continual_gathering_policy(static_cast<proto::ContinualGatheringPolicy>(rc.continual_gathering_policy));
501-
502-
for (const IceServer& ice : rc.ice_servers) {
503-
auto* s = rtc->add_ice_servers();
504-
505-
// proto: repeated string urls = 1
506-
if (!ice.url.empty()) {
507-
s->add_urls(ice.url);
508-
}
509-
if (!ice.username.empty()) {
510-
s->set_username(ice.username);
511-
}
512-
if (!ice.credential.empty()) {
513-
// proto: password = 3
514-
s->set_password(ice.credential);
515-
}
516-
}
517-
}
493+
"[FfiClient] connectAsync: auto_subscribe={}, adaptive_stream={}, dynacast={}, "
494+
"single_peer_connection={}, join_retries={}, connect_timeout_ms={}",
495+
options.auto_subscribe, optional_to_string(options.adaptive_stream), options.dynacast,
496+
options.single_peer_connection, optional_to_string(options.join_retries),
497+
optional_to_string(options.connect_timeout));
518498

519499
try {
520500
const proto::FfiResponse resp = sendRequest(req);

src/room_proto_converter.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "room_proto_converter.h"
1818

1919
#include "livekit/data_stream.h"
20+
#include "livekit/room.h"
2021
#include "room.pb.h"
2122

2223
namespace livekit {
@@ -402,6 +403,50 @@ proto::KeyProviderOptions toProto(const KeyProviderOptions& in) {
402403
return out;
403404
}
404405

406+
proto::RoomOptions toProto(const RoomOptions& in) {
407+
proto::RoomOptions out;
408+
out.set_auto_subscribe(in.auto_subscribe);
409+
if (in.adaptive_stream) {
410+
out.set_adaptive_stream(*in.adaptive_stream);
411+
}
412+
out.set_dynacast(in.dynacast);
413+
414+
if (in.encryption) {
415+
auto* encryption = out.mutable_encryption();
416+
encryption->set_encryption_type(static_cast<proto::EncryptionType>(in.encryption->encryption_type));
417+
encryption->mutable_key_provider_options()->CopyFrom(toProto(in.encryption->key_provider_options));
418+
}
419+
420+
if (in.rtc_config) {
421+
auto* rtc = out.mutable_rtc_config();
422+
rtc->set_ice_transport_type(static_cast<proto::IceTransportType>(in.rtc_config->ice_transport_type));
423+
rtc->set_continual_gathering_policy(
424+
static_cast<proto::ContinualGatheringPolicy>(in.rtc_config->continual_gathering_policy));
425+
426+
for (const IceServer& ice : in.rtc_config->ice_servers) {
427+
auto* server = rtc->add_ice_servers();
428+
if (!ice.url.empty()) {
429+
server->add_urls(ice.url);
430+
}
431+
if (!ice.username.empty()) {
432+
server->set_username(ice.username);
433+
}
434+
if (!ice.credential.empty()) {
435+
server->set_password(ice.credential);
436+
}
437+
}
438+
}
439+
440+
if (in.join_retries) {
441+
out.set_join_retries(*in.join_retries);
442+
}
443+
out.set_single_peer_connection(in.single_peer_connection);
444+
if (in.connect_timeout) {
445+
out.set_connect_timeout_ms(static_cast<std::uint64_t>(in.connect_timeout->count()));
446+
}
447+
return out;
448+
}
449+
405450
proto::AudioEncoding toProto(const AudioEncodingOptions& in) {
406451
proto::AudioEncoding msg;
407452
msg.set_max_bitrate(in.max_bitrate);

src/room_proto_converter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ namespace livekit {
2929
enum class RpcErrorCode;
3030
class RemoteParticipant;
3131
struct ByteStreamInfo;
32+
struct RoomOptions;
3233
struct TextStreamInfo;
3334

3435
// --------- basic helper conversions ---------
@@ -73,6 +74,7 @@ LIVEKIT_INTERNAL_API RoomMovedEvent roomMovedFromProto(const proto::RoomInfo& in
7374
// --------- room options conversions ---------
7475

7576
LIVEKIT_INTERNAL_API proto::KeyProviderOptions toProto(const KeyProviderOptions& in);
77+
LIVEKIT_INTERNAL_API proto::RoomOptions toProto(const RoomOptions& in);
7678

7779
LIVEKIT_INTERNAL_API proto::AudioEncoding toProto(const AudioEncodingOptions& in);
7880
LIVEKIT_INTERNAL_API AudioEncodingOptions fromProto(const proto::AudioEncoding& in);

src/tests/unit/test_room.cpp

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
#include <gtest/gtest.h>
1818
#include <livekit/livekit.h>
1919

20+
#include <chrono>
21+
#include <string>
22+
23+
#include "ffi.pb.h"
24+
#include "room_proto_converter.h"
25+
2026
namespace livekit::test {
2127

2228
class RoomTest : public ::testing::Test {
@@ -31,6 +37,8 @@ TEST_F(RoomTest, ConnectWithoutInitialize) {
3137
livekit::shutdown();
3238

3339
Room room;
40+
41+
// Default room options okay here, will return before FFI layer since not initialized
3442
bool result = room.connect("wss://localhost:7880", "test", livekit::RoomOptions());
3543
EXPECT_FALSE(result) << "Connecting without initializing should return false";
3644
EXPECT_TRUE(room.localParticipant().expired()) << "Local participant should be empty after failed connect";
@@ -47,9 +55,104 @@ TEST_F(RoomTest, RoomOptionsDefaults) {
4755
RoomOptions options;
4856

4957
EXPECT_TRUE(options.auto_subscribe) << "auto_subscribe should default to true";
58+
EXPECT_FALSE(options.adaptive_stream.has_value()) << "adaptive_stream should defer to Rust default";
5059
EXPECT_FALSE(options.dynacast) << "dynacast should default to false";
51-
EXPECT_FALSE(options.rtc_config.has_value()) << "rtc_config should not have a value by default";
5260
EXPECT_FALSE(options.encryption.has_value()) << "encryption should not have a value by default";
61+
EXPECT_FALSE(options.rtc_config.has_value()) << "rtc_config should not have a value by default";
62+
EXPECT_FALSE(options.join_retries.has_value()) << "join_retries should defer to Rust default";
63+
EXPECT_TRUE(options.single_peer_connection) << "single_peer_connection should default to true";
64+
EXPECT_FALSE(options.connect_timeout.has_value()) << "connect_timeout should defer to Rust default";
65+
}
66+
67+
TEST_F(RoomTest, RoomOptionsToProtoSerializesDefaults) {
68+
const proto::RoomOptions proto_options = toProto(RoomOptions{});
69+
70+
EXPECT_TRUE(proto_options.has_auto_subscribe());
71+
EXPECT_TRUE(proto_options.auto_subscribe());
72+
EXPECT_FALSE(proto_options.has_adaptive_stream());
73+
EXPECT_TRUE(proto_options.has_dynacast());
74+
EXPECT_FALSE(proto_options.dynacast());
75+
EXPECT_FALSE(proto_options.has_encryption());
76+
EXPECT_FALSE(proto_options.has_rtc_config());
77+
EXPECT_FALSE(proto_options.has_join_retries());
78+
EXPECT_TRUE(proto_options.has_single_peer_connection());
79+
EXPECT_TRUE(proto_options.single_peer_connection());
80+
EXPECT_FALSE(proto_options.has_connect_timeout_ms());
81+
}
82+
83+
TEST_F(RoomTest, RoomOptionsProtoConverter) {
84+
RoomOptions options;
85+
options.auto_subscribe = false;
86+
options.adaptive_stream = true;
87+
options.dynacast = true;
88+
E2EEOptions encryption;
89+
encryption.key_provider_options.shared_key = std::vector<std::uint8_t>{'s', 'e', 'c', 'r', 'e', 't'};
90+
options.encryption = encryption;
91+
RtcConfig rtc_config;
92+
rtc_config.ice_transport_type = proto::TRANSPORT_ALL;
93+
rtc_config.continual_gathering_policy = proto::GATHER_CONTINUALLY;
94+
rtc_config.ice_servers.push_back({"stun:stun.l.google.com:19302", "", ""});
95+
rtc_config.ice_servers.push_back({"turn:turn.example.com:3478", "user", "pass"});
96+
options.rtc_config = rtc_config;
97+
options.join_retries = 8;
98+
options.single_peer_connection = false;
99+
options.connect_timeout = std::chrono::milliseconds(750);
100+
101+
const proto::RoomOptions proto_options = toProto(options);
102+
103+
EXPECT_TRUE(proto_options.has_auto_subscribe());
104+
EXPECT_FALSE(proto_options.auto_subscribe());
105+
EXPECT_TRUE(proto_options.has_adaptive_stream());
106+
EXPECT_TRUE(proto_options.adaptive_stream());
107+
EXPECT_TRUE(proto_options.has_dynacast());
108+
EXPECT_TRUE(proto_options.dynacast());
109+
ASSERT_TRUE(proto_options.has_encryption());
110+
EXPECT_EQ(proto_options.encryption().encryption_type(),
111+
static_cast<proto::EncryptionType>(encryption.encryption_type));
112+
ASSERT_TRUE(proto_options.encryption().has_key_provider_options());
113+
EXPECT_EQ(proto_options.encryption().key_provider_options().shared_key(), "secret");
114+
ASSERT_TRUE(proto_options.has_rtc_config());
115+
EXPECT_EQ(proto_options.rtc_config().ice_transport_type(), proto::TRANSPORT_ALL);
116+
EXPECT_EQ(proto_options.rtc_config().continual_gathering_policy(), proto::GATHER_CONTINUALLY);
117+
ASSERT_EQ(proto_options.rtc_config().ice_servers_size(), 2);
118+
EXPECT_EQ(proto_options.rtc_config().ice_servers(0).urls(0), "stun:stun.l.google.com:19302");
119+
EXPECT_EQ(proto_options.rtc_config().ice_servers(1).urls(0), "turn:turn.example.com:3478");
120+
EXPECT_EQ(proto_options.rtc_config().ice_servers(1).username(), "user");
121+
EXPECT_EQ(proto_options.rtc_config().ice_servers(1).password(), "pass");
122+
EXPECT_TRUE(proto_options.has_join_retries());
123+
EXPECT_EQ(proto_options.join_retries(), 8U);
124+
EXPECT_TRUE(proto_options.has_single_peer_connection());
125+
EXPECT_FALSE(proto_options.single_peer_connection());
126+
EXPECT_TRUE(proto_options.has_connect_timeout_ms());
127+
EXPECT_EQ(proto_options.connect_timeout_ms(), 750U);
128+
}
129+
130+
TEST(RoomOptionsProtoTest, ConnectRequestSerializesRetryOptions) {
131+
RoomOptions options;
132+
options.join_retries = 8;
133+
options.connect_timeout = std::chrono::milliseconds(750);
134+
135+
proto::FfiRequest request;
136+
auto* connect = request.mutable_connect();
137+
connect->set_url("ws://localhost:7880");
138+
connect->set_token("test-token");
139+
connect->mutable_options()->CopyFrom(toProto(options));
140+
141+
ASSERT_TRUE(connect->options().has_join_retries());
142+
EXPECT_EQ(connect->options().join_retries(), 8U);
143+
ASSERT_TRUE(connect->options().has_connect_timeout_ms());
144+
EXPECT_EQ(connect->options().connect_timeout_ms(), 750U);
145+
146+
ASSERT_TRUE(request.IsInitialized()) << request.InitializationErrorString();
147+
148+
std::string serialized;
149+
ASSERT_TRUE(request.SerializeToString(&serialized));
150+
EXPECT_FALSE(serialized.empty());
151+
152+
proto::FfiRequest decoded;
153+
ASSERT_TRUE(decoded.ParseFromString(serialized));
154+
EXPECT_EQ(decoded.connect().options().join_retries(), 8U);
155+
EXPECT_EQ(decoded.connect().options().connect_timeout_ms(), 750U);
53156
}
54157

55158
TEST_F(RoomTest, RtcConfigDefaults) {

0 commit comments

Comments
 (0)