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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ set(FFI_PROTO_FILES
${FFI_PROTO_DIR}/data_track.proto
${FFI_PROTO_DIR}/rpc.proto
${FFI_PROTO_DIR}/track_publication.proto
${FFI_PROTO_DIR}/capture.proto
${FFI_PROTO_DIR}/client_info.proto
)
set(PROTO_BINARY_DIR ${LIVEKIT_BINARY_DIR}/generated)
file(MAKE_DIRECTORY ${PROTO_BINARY_DIR})
Expand Down
2 changes: 1 addition & 1 deletion client-sdk-rust
103 changes: 103 additions & 0 deletions include/livekit/client_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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.
*/

#pragma once

#include <cstdint>
#include <string>
#include <vector>

#include "livekit/visibility.h"

namespace livekit {

/// @brief Describes the SDK and platform metadata advertised by this client.
struct ClientInfo {
/// @brief Identifies the LiveKit SDK implementation.
enum class Sdk {
Unknown = 0,
Js = 1,
Swift = 2,
Android = 3,
Flutter = 4,
Go = 5,
Unity = 6,
ReactNative = 7,
Rust = 8,
Python = 9,
Cpp = 10,
UnityWeb = 11,
Node = 12,
Unreal = 13,
Esp32 = 14,
};

/// @brief Identifies an optional feature advertised by the client.
enum class Capability {
Unused = 0,
PacketTrailer = 1,
CompressionDeflateRaw = 2,
};

/// LiveKit SDK implementation.
Sdk sdk = Sdk::Unknown;

/// LiveKit SDK version.
std::string version;

/// LiveKit signaling protocol version.
std::int32_t protocol = 0;

/// Operating system name.
std::string os;

/// Operating system version.
std::string os_version;

/// Device model, when available.
std::string device_model;

/// Browser name, when applicable.
std::string browser;

/// Browser version, when applicable.
std::string browser_version;

/// Client network address, when available.
std::string address;

/// Network type such as wifi, wired, cellular, or VPN, when available.
std::string network;

/// Comma-separated additional LiveKit SDKs used by the client.
std::string other_sdks;

/// Client feature protocol version advertised to other participants.
std::int32_t client_protocol = 0;

/// Optional features advertised by the client.
std::vector<Capability> capabilities;
};

/// @brief Retrieve the metadata this SDK advertises to LiveKit when connecting.
///
/// The SDK must be initialized before calling this function.
///
/// @return A snapshot of the current client metadata.
/// @throws std::runtime_error If the SDK is not initialized or the FFI request fails.
LIVEKIT_API ClientInfo getClientInfo();

} // namespace livekit
1 change: 1 addition & 0 deletions include/livekit/livekit.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "livekit/audio_source.h"
#include "livekit/audio_stream.h"
#include "livekit/build.h"
#include "livekit/client_info.h"
#include "livekit/e2ee.h"
#include "livekit/local_audio_track.h"
#include "livekit/local_participant.h"
Expand Down
6 changes: 6 additions & 0 deletions include/livekit/room.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ struct RoomOptions {
///
/// If unset, the Rust SDK default is used.
std::optional<std::chrono::milliseconds> connect_timeout;

/// Comma-separated additional LiveKit SDKs used by the application, with versions.
///
/// For example: `"ros_portal:1.2.3,another-sdk:2.0.0"`.
/// Older servers or legacy signaling connections may ignore this value.
std::string other_sdks;
};

/// Represents a LiveKit room session.
Expand Down
86 changes: 86 additions & 0 deletions src/livekit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,87 @@

#include "livekit/livekit.h"

#include <stdexcept>

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

namespace livekit {

namespace {

ClientInfo::Sdk toClientSdk(proto::ClientInfo_SDK sdk) {
switch (sdk) {
case proto::ClientInfo::JS:
return ClientInfo::Sdk::Js;
case proto::ClientInfo::SWIFT:
return ClientInfo::Sdk::Swift;
case proto::ClientInfo::ANDROID:
return ClientInfo::Sdk::Android;
case proto::ClientInfo::FLUTTER:
return ClientInfo::Sdk::Flutter;
case proto::ClientInfo::GO:
return ClientInfo::Sdk::Go;
case proto::ClientInfo::UNITY:
return ClientInfo::Sdk::Unity;
case proto::ClientInfo::REACT_NATIVE:
return ClientInfo::Sdk::ReactNative;
case proto::ClientInfo::RUST:
return ClientInfo::Sdk::Rust;
case proto::ClientInfo::PYTHON:
return ClientInfo::Sdk::Python;
case proto::ClientInfo::CPP:
return ClientInfo::Sdk::Cpp;
case proto::ClientInfo::UNITY_WEB:
return ClientInfo::Sdk::UnityWeb;
case proto::ClientInfo::NODE:
return ClientInfo::Sdk::Node;
case proto::ClientInfo::UNREAL:
return ClientInfo::Sdk::Unreal;
case proto::ClientInfo::ESP32:
return ClientInfo::Sdk::Esp32;
case proto::ClientInfo::UNKNOWN:
default:
return ClientInfo::Sdk::Unknown;
}
}

ClientInfo::Capability toClientCapability(int capability) {
switch (capability) {
case proto::ClientInfo::CAP_PACKET_TRAILER:
return ClientInfo::Capability::PacketTrailer;
case proto::ClientInfo::CAP_COMPRESSION_DEFLATE_RAW:
return ClientInfo::Capability::CompressionDeflateRaw;
case proto::ClientInfo::CAP_UNUSED:
default:
return ClientInfo::Capability::Unused;
}
}

ClientInfo fromProto(const proto::ClientInfo& input) {
ClientInfo output;
output.sdk = toClientSdk(input.sdk());
output.version = input.version();
output.protocol = input.protocol();
output.os = input.os();
output.os_version = input.os_version();
output.device_model = input.device_model();
output.browser = input.browser();
output.browser_version = input.browser_version();
output.address = input.address();
output.network = input.network();
output.other_sdks = input.other_sdks();
output.client_protocol = input.client_protocol();
output.capabilities.reserve(input.capabilities_size());
for (const auto capability : input.capabilities()) {
output.capabilities.push_back(toClientCapability(capability));
}
return output;
}

} // namespace

bool initialize(const LogLevel& level) {
// Initializes logger if singleton instance is not already initialized
setLogLevel(level);
Expand All @@ -29,6 +105,16 @@ bool initialize(const LogLevel& level) {
return ffi_client.initialize(false);
}

ClientInfo getClientInfo() {
proto::FfiRequest request;
(void)request.mutable_get_client_info();
const auto response = FfiClient::instance().sendRequest(request);
if (!response.has_get_client_info()) {
throw std::runtime_error("FfiResponse missing get_client_info");
}
return fromProto(response.get_client_info().info());
}

bool isInitialized() { return FfiClient::instance().isInitialized(); }

void shutdown() {
Expand Down
3 changes: 3 additions & 0 deletions src/room_proto_converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,9 @@ proto::RoomOptions toProto(const RoomOptions& in) {
if (in.connect_timeout) {
out.set_connect_timeout_ms(static_cast<std::uint64_t>(in.connect_timeout->count()));
}
if (!in.other_sdks.empty()) {
out.set_other_sdks(in.other_sdks);
}
return out;
}

Expand Down
79 changes: 79 additions & 0 deletions src/tests/stress/test_client_info_stress.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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 <gtest/gtest.h>
#include <livekit/livekit.h>

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
#include <vector>

namespace livekit::test {

class ClientInfoStressTest : public ::testing::Test {
protected:
void SetUp() override { ASSERT_TRUE(livekit::initialize(livekit::LogLevel::Info)); }

void TearDown() override { livekit::shutdown(); }
};

TEST_F(ClientInfoStressTest, RepeatedQueries) {
constexpr int kIterations = 1000;
const auto start = std::chrono::steady_clock::now();

for (int i = 0; i < kIterations; ++i) {
const auto info = livekit::getClientInfo();
ASSERT_EQ(info.sdk, livekit::ClientInfo::Sdk::Cpp);
}

const auto elapsed = std::chrono::steady_clock::now() - start;
const auto elapsed_us = std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
std::cout << "Retrieved ClientInfo " << kIterations << " times in " << elapsed_us << "us\n";
}

TEST_F(ClientInfoStressTest, ConcurrentQueries) {
constexpr int kThreadCount = 8;
constexpr int kQueriesPerThread = 250;
std::atomic<int> successful_queries{0};
std::vector<std::thread> threads;
threads.reserve(kThreadCount);

const auto start = std::chrono::steady_clock::now();
for (int thread_index = 0; thread_index < kThreadCount; ++thread_index) {
threads.emplace_back([&successful_queries] {
for (int query_index = 0; query_index < kQueriesPerThread; ++query_index) {
const auto info = livekit::getClientInfo();
if (info.sdk == livekit::ClientInfo::Sdk::Cpp) {
successful_queries.fetch_add(1, std::memory_order_relaxed);
}
}
});
}

for (auto& thread : threads) {
thread.join();
}

const auto elapsed = std::chrono::steady_clock::now() - start;
const auto elapsed_us = std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
EXPECT_EQ(successful_queries.load(std::memory_order_relaxed), kThreadCount * kQueriesPerThread);
std::cout << "Retrieved ClientInfo " << successful_queries.load(std::memory_order_relaxed) << " times across "
<< kThreadCount << " threads in " << elapsed_us << "us\n";
}

} // namespace livekit::test
Loading
Loading