Skip to content

Commit c3f3260

Browse files
Sxian/clt 2237/update new rust commit (#6)
* fixed the build * updated the readme * fixed the release builds and racing conditions in the example code * update to the latest client-sdk-rust * addressed the comment * pulled in more screen capture deps for linux bot * added audio backend, alsa and pulse-audio
1 parent d9e8076 commit c3f3260

File tree

8 files changed

+160
-79
lines changed

8 files changed

+160
-79
lines changed

.github/workflows/builds.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ jobs:
5555
sudo apt-get update
5656
sudo apt-get install -y cmake ninja-build build-essential \
5757
protobuf-compiler libprotobuf-dev libabsl-dev \
58-
libx11-dev libxext-dev libgl1-mesa-dev libssl-dev
58+
libx11-dev libxext-dev libgl1-mesa-dev libssl-dev \
59+
libxext-dev libxcomposite-dev libxdamage-dev libxfixes-dev \
60+
libxrandr-dev libxi-dev libxkbcommon-dev \
61+
libdrm-dev libgbm-dev libva-dev \
62+
libasound2-dev libpulse-dev
5963
protoc --version
6064
pkg-config --modversion protobuf
6165
# Fail if versions don't match (best-effort)

CMakeLists.txt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ set(FFI_PROTO_FILES
1616
${FFI_PROTO_DIR}/track.proto
1717
${FFI_PROTO_DIR}/video_frame.proto
1818
${FFI_PROTO_DIR}/audio_frame.proto
19+
${FFI_PROTO_DIR}/e2ee.proto
20+
${FFI_PROTO_DIR}/stats.proto
21+
${FFI_PROTO_DIR}/data_stream.proto
22+
${FFI_PROTO_DIR}/rpc.proto
23+
${FFI_PROTO_DIR}/track_publication.proto
1924
)
2025
set(PROTO_BINARY_DIR ${CMAKE_BINARY_DIR}/generated)
2126
file(MAKE_DIRECTORY ${PROTO_BINARY_DIR})
@@ -25,11 +30,11 @@ find_package(absl CONFIG REQUIRED)
2530

2631
# Object library that owns generated .pb.cc/.pb.h
2732
add_library(livekit_proto OBJECT ${FFI_PROTO_FILES})
28-
target_include_directories(livekit_proto PUBLIC
33+
target_include_directories(livekit_proto PRIVATE
2934
"$<BUILD_INTERFACE:${PROTO_BINARY_DIR}>"
3035
${Protobuf_INCLUDE_DIRS}
3136
)
32-
target_link_libraries(livekit_proto PUBLIC protobuf::libprotobuf)
37+
target_link_libraries(livekit_proto PRIVATE protobuf::libprotobuf)
3338

3439
# Generate .pb sources into ${PROTO_BINARY_DIR} and attach to livekit_proto
3540
protobuf_generate(
@@ -129,6 +134,11 @@ add_dependencies(livekit build_rust_ffi)
129134
########### Platform specific settings ###########
130135

131136
if(APPLE)
137+
# (12.3 is when ScreenCaptureKit was introduced)
138+
if(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "12.3")
139+
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.3" CACHE STRING "Minimum macOS version" FORCE)
140+
endif()
141+
132142
find_library(FW_COREAUDIO CoreAudio REQUIRED)
133143
find_library(FW_AUDIOTOOLBOX AudioToolbox REQUIRED)
134144
find_library(FW_COREFOUNDATION CoreFoundation REQUIRED)
@@ -142,9 +152,10 @@ if(APPLE)
142152
find_library(FW_APPKIT AppKit REQUIRED)
143153
find_library(FW_QUARTZCORE QuartzCore REQUIRED)
144154
find_library(FW_OPENGL OpenGL REQUIRED)
145-
find_library(FW_IOSURFACE IOSurface REQUIRED)
146-
find_library(FW_METAL Metal REQUIRED)
155+
find_library(FW_IOSURFACE IOSurface REQUIRED)
156+
find_library(FW_METAL Metal REQUIRED)
147157
find_library(FW_METALKIT MetalKit REQUIRED)
158+
find_library(FW_SCREENCAPTUREKIT ScreenCaptureKit REQUIRED)
148159

149160
target_link_libraries(livekit PUBLIC
150161
${FW_COREAUDIO}
@@ -163,6 +174,7 @@ if(APPLE)
163174
${FW_IOSURFACE}
164175
${FW_METAL}
165176
${FW_METALKIT}
177+
${FW_SCREENCAPTUREKIT}
166178
)
167179

168180
# Ensure Objective-C categories/classes in static archives are loaded (WebRTC needs this)

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,35 @@ brew install cmake protobuf rust
6363
sudo apt update
6464
sudo apt install -y cmake protobuf-compiler build-essential
6565
curl https://sh.rustup.rs -sSf | sh
66+
```
67+
68+
## 🛠️ Development Tips
69+
### Update Rust version
70+
```bash
71+
git fetch origin
72+
git switch -c try-rust-main origin/main
73+
74+
# Sync submodule URLs and check out what origin/main pins (recursively):
75+
git submodule sync --recursive
76+
git submodule update --init --recursive --checkout
77+
78+
# Now, in case the nested submodule under yuv-sys didn’t materialize, force it explicitly:
79+
git -C client-sdk-rust/yuv-sys submodule sync --recursive
80+
git -C client-sdk-rust/yuv-sys submodule update --init --recursive --checkout
81+
82+
# Sanity check:
83+
git submodule status --recursive
84+
```
85+
86+
### If yuv-sys fails to build
87+
```bash
88+
cargo clean -p yuv-sys
89+
cargo build -p yuv-sys -vv
90+
```
91+
92+
### Full clean (Rust + C++ build folders)
93+
94+
In some cases, you may need to perform a full clean that deletes all build artifacts from both the Rust and C++ folders:
95+
```bash
96+
./build.sh clean-all
6697
```

client-sdk-rust

include/livekit/ffi_client.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,22 @@
2727

2828
namespace livekit
2929
{
30+
using FfiCallbackFn = void(*)(const uint8_t*, size_t);
31+
extern "C" void livekit_ffi_initialize(FfiCallbackFn cb,
32+
bool capture_logs,
33+
const char* sdk,
34+
const char* sdk_version);
35+
3036
extern "C" void LivekitFfiCallback(const uint8_t *buf, size_t len);
3137

38+
3239
// The FfiClient is used to communicate with the FFI interface of the Rust SDK
3340
// We use the generated protocol messages to facilitate the communication
3441
class FfiClient
3542
{
3643
public:
3744
using ListenerId = int;
38-
using Listener = std::function<void(const FFIEvent&)>;
45+
using Listener = std::function<void(const proto::FfiEvent&)>;
3946

4047
FfiClient(const FfiClient&) = delete;
4148
FfiClient& operator=(const FfiClient&) = delete;
@@ -48,7 +55,7 @@ namespace livekit
4855
ListenerId AddListener(const Listener& listener);
4956
void RemoveListener(ListenerId id);
5057

51-
FFIResponse SendRequest(const FFIRequest& request)const;
58+
proto::FfiResponse SendRequest(const proto::FfiRequest& request)const;
5259

5360
private:
5461
std::unordered_map<ListenerId, Listener> listeners_;
@@ -58,7 +65,7 @@ namespace livekit
5865
FfiClient();
5966
~FfiClient() = default;
6067

61-
void PushEvent(const FFIEvent& event) const;
68+
void PushEvent(const proto::FfiEvent& event) const;
6269
friend void LivekitFfiCallback(const uint8_t *buf, size_t len);
6370
};
6471

include/livekit/room.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ namespace livekit
3030
void Connect(const std::string& url, const std::string& token);
3131

3232
private:
33+
void OnConnect(const proto::ConnectCallback& cb);
34+
3335
mutable std::mutex lock_;
3436
FfiHandle handle_{INVALID_HANDLE};
3537
bool connected_{false};
3638
uint64_t connectAsyncId_{0};
3739

3840

39-
void OnEvent(const FFIEvent& event);
41+
void OnEvent(const proto::FfiEvent& event);
4042
};
4143
}
4244

src/ffi_client.cpp

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@ namespace livekit
2424
{
2525

2626
FfiClient::FfiClient() {
27-
InitializeRequest *initRequest = new InitializeRequest;
28-
initRequest->set_event_callback_ptr(reinterpret_cast<uint64_t>(&LivekitFfiCallback));
29-
30-
FFIRequest request{};
31-
request.set_allocated_initialize(initRequest);
32-
SendRequest(request);
27+
livekit_ffi_initialize(&LivekitFfiCallback,
28+
true,
29+
"cpp",
30+
"0.0.0-dev");
3331
}
3432

3533
FfiClient::ListenerId FfiClient::AddListener(const FfiClient::Listener& listener) {
@@ -44,33 +42,36 @@ void FfiClient::RemoveListener(ListenerId id) {
4442
listeners_.erase(id);
4543
}
4644

47-
FFIResponse FfiClient::SendRequest(const FFIRequest &request) const {
48-
size_t len = request.ByteSizeLong();
49-
uint8_t *buf = new uint8_t[len];
50-
assert(request.SerializeToArray(buf, len));
51-
52-
const uint8_t **res_ptr = new const uint8_t*;
53-
size_t *res_len = new size_t;
54-
55-
FfiHandleId handle = livekit_ffi_request(buf, len, res_ptr, res_len);
56-
57-
delete[] buf;
58-
if (handle == INVALID_HANDLE) {
59-
delete res_ptr;
60-
delete res_len;
45+
proto::FfiResponse FfiClient::SendRequest(const proto::FfiRequest &request) const {
46+
std::string bytes;
47+
if (!request.SerializeToString(&bytes) || bytes.empty()) {
48+
throw std::runtime_error("failed to serialize FfiRequest");
49+
}
50+
const uint8_t* resp_ptr = nullptr;
51+
size_t resp_len = 0;
52+
FfiHandleId handle = livekit_ffi_request(
53+
reinterpret_cast<const uint8_t*>(bytes.data()),
54+
bytes.size(), &resp_ptr, &resp_len);
55+
std::cout << "receive a handle " << handle << std::endl;
56+
57+
if (handle == INVALID_HANDLE) {
6158
throw std::runtime_error("failed to send request, received an invalid handle");
6259
}
63-
FfiHandle _handle(handle);
6460

65-
FFIResponse response;
66-
assert(response.ParseFromArray(*res_ptr, *res_len));
67-
delete res_ptr;
68-
delete res_len;
61+
// Ensure we drop the handle exactly once on all paths
62+
FfiHandle handle_guard(static_cast<uintptr_t>(handle));
63+
if (!resp_ptr || resp_len == 0) {
64+
throw std::runtime_error("FFI returned empty response bytes");
65+
}
6966

67+
proto::FfiResponse response;
68+
if (!response.ParseFromArray(resp_ptr, static_cast<int>(resp_len))) {
69+
throw std::runtime_error("failed to parse FfiResponse");
70+
}
7071
return response;
7172
}
7273

73-
void FfiClient::PushEvent(const FFIEvent &event) const {
74+
void FfiClient::PushEvent(const proto::FfiEvent &event) const {
7475
// Dispatch the events to the internal listeners
7576
std::lock_guard<std::mutex> guard(lock_);
7677
for (auto& [_, listener] : listeners_) {
@@ -79,8 +80,8 @@ void FfiClient::PushEvent(const FFIEvent &event) const {
7980
}
8081

8182
void LivekitFfiCallback(const uint8_t *buf, size_t len) {
82-
FFIEvent event;
83-
assert(event.ParseFromArray(buf, len));
83+
proto::FfiEvent event;
84+
event.ParseFromArray(buf, len);
8485

8586
FfiClient::getInstance().PushEvent(event);
8687
}
@@ -91,7 +92,7 @@ FfiHandle::FfiHandle(uintptr_t id) : handle(id) {}
9192

9293
FfiHandle::~FfiHandle() {
9394
if (handle != INVALID_HANDLE) {
94-
assert(livekit_ffi_drop_handle(handle));
95+
livekit_ffi_drop_handle(handle);
9596
}
9697
}
9798

src/room.cpp

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,59 +25,83 @@
2525
namespace livekit
2626
{
2727

28-
void Room::Connect(const std::string& url, const std::string& token)
29-
{
30-
std::lock_guard<std::mutex> guard(lock_);
31-
if (connected_) {
32-
throw std::runtime_error("already connected");
33-
}
28+
using proto::FfiRequest;
29+
using proto::FfiResponse;
30+
using proto::ConnectRequest;
31+
using proto::RoomOptions;
32+
using proto::ConnectCallback;
33+
using proto::FfiEvent;
3434

35-
connected_ = true;
35+
void Room::Connect(const std::string& url, const std::string& token) {
36+
// Register listener first (outside Room lock to avoid lock inversion)
37+
auto listenerId = FfiClient::getInstance().AddListener(
38+
std::bind(&Room::OnEvent, this, std::placeholders::_1));
3639

37-
RoomOptions *options = new RoomOptions;
38-
options->set_auto_subscribe(true);
39-
40-
ConnectRequest *connectRequest = new ConnectRequest;
41-
connectRequest->set_url(url);
42-
connectRequest->set_token(token);
43-
connectRequest->set_allocated_options(options);
40+
// Build request without heap allocs
41+
livekit::proto::FfiRequest req;
42+
auto* connect = req.mutable_connect();
43+
connect->set_url(url);
44+
connect->set_token(token);
45+
connect->mutable_options()->set_auto_subscribe(true);
4446

45-
FFIRequest request;
46-
request.set_allocated_connect(connectRequest);
47-
48-
// TODO Free:
49-
FfiClient::getInstance().AddListener(std::bind(&Room::OnEvent, this, std::placeholders::_1));
50-
51-
FFIResponse response = FfiClient::getInstance().SendRequest(request);
52-
FFIAsyncId asyncId = response.connect().async_id();
47+
// Mark “connecting” under lock, but DO NOT keep the lock across SendRequest
48+
{
49+
std::lock_guard<std::mutex> g(lock_);
50+
if (connected_) {
51+
FfiClient::getInstance().RemoveListener(listenerId);
52+
throw std::runtime_error("already connected");
53+
}
54+
connectAsyncId_ = listenerId;
55+
}
5356

54-
connectAsyncId_ = asyncId.id();
57+
// Call into FFI with no Room lock held (avoid re-entrancy deadlock)
58+
livekit::proto::FfiResponse resp = FfiClient::getInstance().SendRequest(req);
59+
// Store async id under lock
60+
{
61+
std::lock_guard<std::mutex> g(lock_);
62+
connectAsyncId_ = resp.connect().async_id();
63+
}
5564
}
5665

57-
void Room::OnEvent(const FFIEvent& event)
58-
{
66+
void Room::OnEvent(const FfiEvent& event) {
67+
// TODO, it is not a good idea to lock all the callbacks, improve it.
5968
std::lock_guard<std::mutex> guard(lock_);
60-
if (!connected_) {
69+
switch (event.message_case()) {
70+
case FfiEvent::kConnect:
71+
OnConnect(event.connect());
72+
break;
73+
74+
// TODO: Handle other FfiEvent types here (e.g. room_event, track_event, etc.)
75+
default:
76+
break;
77+
}
78+
}
79+
80+
void Room::OnConnect(const ConnectCallback& cb) {
81+
// Match the async_id with the pending connectAsyncId_
82+
if (cb.async_id() != connectAsyncId_) {
6183
return;
6284
}
63-
64-
if (event.has_connect()) {
65-
ConnectCallback connectCallback = event.connect();
66-
if (connectCallback.async_id().id() != connectAsyncId_) {
67-
return;
68-
}
6985

70-
std::cout << "Received ConnectCallback" << std::endl;
86+
std::cout << "Received ConnectCallback" << std::endl;
7187

72-
if (!connectCallback.has_error()) {
73-
handle_ = FfiHandle(connectCallback.room().handle().id());
88+
if (cb.message_case() == ConnectCallback::kError) {
89+
std::cerr << "Failed to connect to room: " << cb.error() << std::endl;
90+
connected_ = false;
91+
return;
92+
}
7493

75-
std::cout << "Connected to room" << std::endl;
76-
std::cout << "Room SID: " << connectCallback.room().sid() << std::endl;
77-
} else {
78-
std::cerr << "Failed to connect to room: " << connectCallback.error() << std::endl;
79-
}
94+
// Success path
95+
const auto& result = cb.result();
96+
const auto& owned_room = result.room();
97+
// OwnedRoom { FfiOwnedHandle handle = 1; RoomInfo info = 2; }
98+
handle_ = FfiHandle(static_cast<uintptr_t>(owned_room.handle().id()));
99+
if (owned_room.info().has_sid()) {
100+
std::cout << "Room SID: " << owned_room.info().sid() << std::endl;
80101
}
102+
103+
connected_ = true;
104+
std::cout << "Connected to room" << std::endl;
81105
}
82106

83107
}

0 commit comments

Comments
 (0)