Skip to content

Commit 499d4af

Browse files
initial commit
1 parent c3f3260 commit 499d4af

17 files changed

Lines changed: 3144 additions & 84 deletions

CMakeLists.txt

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ set(CMAKE_CXX_STANDARD 17)
66
set(CMAKE_CXX_STANDARD_REQUIRED ON)
77
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
88

9+
############## Protobuf ################
10+
911
# ---- Protobuf (FFI protos) ----
1012
set(FFI_PROTO_DIR ${CMAKE_SOURCE_DIR}/client-sdk-rust/livekit-ffi/protocol)
1113
set(FFI_PROTO_FILES
@@ -25,7 +27,7 @@ set(FFI_PROTO_FILES
2527
set(PROTO_BINARY_DIR ${CMAKE_BINARY_DIR}/generated)
2628
file(MAKE_DIRECTORY ${PROTO_BINARY_DIR})
2729

28-
find_package(Protobuf REQUIRED) # protobuf::libprotobuf, protoc
30+
find_package(Protobuf REQUIRED)
2931
find_package(absl CONFIG REQUIRED)
3032

3133
# Object library that owns generated .pb.cc/.pb.h
@@ -104,17 +106,28 @@ add_custom_target(build_rust_ffi ALL
104106
# ---- C++ wrapper library ----
105107
add_library(livekit
106108
include/livekit/room.h
109+
include/livekit/ffi_handle.h
107110
include/livekit/ffi_client.h
111+
include/livekit/eventEmitter.h
112+
include/livekit/participant.h
108113
include/livekit/livekit.h
114+
include/livekit/stats.h
115+
include/livekit/track.h
116+
src/ffi_handle.cpp
109117
src/ffi_client.cpp
110118
src/room.cpp
119+
src/room_event_converter.cpp
120+
src/room_event_converter.h
121+
src/stats.cpp
122+
src/track.cpp
111123
)
112124

113125
# Add generated proto objects to the wrapper
114126
target_sources(livekit PRIVATE $<TARGET_OBJECTS:livekit_proto>)
115127

116128
target_include_directories(livekit PUBLIC
117129
${CMAKE_SOURCE_DIR}/include
130+
${CMAKE_SOURCE_DIR}/src
118131
${RUST_ROOT}/livekit-ffi/include
119132
${PROTO_BINARY_DIR}
120133
)
@@ -131,6 +144,45 @@ message(STATUS "Protobuf: version=${Protobuf_VERSION}; protoc=${Protobuf_PROTOC_
131144
add_dependencies(livekit build_rust_ffi)
132145

133146

147+
########### auto-gen build.h #######################
148+
149+
# Where to place the generated header
150+
set(GENERATED_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
151+
file(MAKE_DIRECTORY "${GENERATED_INCLUDE_DIR}")
152+
153+
# Try to capture git commit; fall back to "unknown" if git isn't available or repo isn't present.
154+
set(GIT_COMMIT "unknown")
155+
find_package(Git QUIET)
156+
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
157+
execute_process(
158+
COMMAND "${GIT_EXECUTABLE}" rev-parse --short HEAD
159+
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
160+
OUTPUT_VARIABLE GIT_COMMIT
161+
OUTPUT_STRIP_TRAILING_WHITESPACE
162+
ERROR_QUIET
163+
)
164+
endif()
165+
166+
# Build timestamp
167+
string(TIMESTAMP BUILD_DATE "%Y-%m-%d %H:%M:%S")
168+
169+
# Comment shown at the top of the generated header
170+
set(GENERATED_COMMENT "This file was auto-generated by CMake on ${LIVEKIT_BUILD_DATE}. Do NOT edit manually. Edit build.h.in instead.")
171+
172+
# Generate the header from the template
173+
configure_file(
174+
"${CMAKE_CURRENT_SOURCE_DIR}/build.h.in"
175+
"${GENERATED_INCLUDE_DIR}/build.h"
176+
@ONLY
177+
)
178+
179+
# Make the generated header available to your targets:
180+
# (Option A) Global include dir
181+
include_directories("${GENERATED_INCLUDE_DIR}")
182+
183+
# (Option B) Or, per-target (recommended):
184+
# target_include_directories(<your_target> PRIVATE "${GENERATED_INCLUDE_DIR}")
185+
134186
########### Platform specific settings ###########
135187

136188
if(APPLE)

build.h.in

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// ===============================================================
2+
// ⚠️ @GENERATED_COMMENT@
3+
// ===============================================================
4+
5+
#pragma once
6+
7+
#define LIVEKIT_BUILD_VERSION "0.1.0" // Manually maintained
8+
#define LIVEKIT_BUILD_FLAVOR "cpp"
9+
10+
#ifdef DEBUG
11+
#define LIVEKIT_BUILD_SUFFIX "-debug"
12+
#else
13+
#define LIVEKIT_BUILD_SUFFIX "-release"
14+
#endif
15+
16+
// Follow params are auto generated by CMakeLists.txt
17+
#define LIVEKIT_BUILD_DATE "@BUILD_DATE@"
18+
#define LIVEKIT_BUILD_COMMIT "@GIT_COMMIT@"
19+
20+
#define LIVEKIT_BUILD_VERSION_FULL LIVEKIT_BUILD_VERSION LIVEKIT_BUILD_SUFFIX

examples/simple_room/main.cpp

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@
99

1010
#include "livekit/livekit.h"
1111

12+
// TODO, remove this livekit_ffi.h as it should be internal only.
13+
#include "livekit_ffi.h"
14+
15+
// If you have concrete RemoteParticipant / Track / Publication types exposed from Room,
16+
// you can also include their headers and use them directly in main.
17+
// For now we demonstrate logging via RoomDelegate events.
18+
1219
using namespace livekit;
1320

1421
namespace {
22+
1523
std::atomic<bool> g_running{true};
1624

1725
void print_usage(const char* prog) {
@@ -88,6 +96,47 @@ bool parse_args(int argc, char* argv[], std::string& url, std::string& token) {
8896

8997
return !(url.empty() || token.empty());
9098
}
99+
100+
// ---------------------------------------------------------------------
101+
// SimpleRoomDelegate: analogous to the Python @room.on(...) handlers
102+
// ---------------------------------------------------------------------
103+
104+
class SimpleRoomDelegate : public livekit::RoomDelegate {
105+
public:
106+
void onParticipantConnected(
107+
livekit::Room& /*room*/,
108+
const livekit::ParticipantConnectedEvent& ev) override
109+
{
110+
// Python:
111+
// logger.info("participant connected: %s %s", participant.sid, participant.identity)
112+
std::cout << "[Room] participant connected: identity="
113+
<< ev.identity << " name=" << ev.name << "\n";
114+
}
115+
116+
void onTrackSubscribed(
117+
livekit::Room& /*room*/,
118+
const livekit::TrackSubscribedEvent& ev) override
119+
{
120+
// Python:
121+
// logger.info("track subscribed: %s", publication.sid)
122+
std::cout << "[Room] track subscribed: participant_identity="
123+
<< ev.participant_identity
124+
<< " track_sid=" << ev.track_sid
125+
<< " name=" << ev.track_name << "\n";
126+
127+
// Python version also does:
128+
// if track.kind == KIND_VIDEO:
129+
// video_stream = VideoStream(track)
130+
// asyncio.ensure_future(receive_frames(video_stream))
131+
//
132+
// In C++, you'd typically spawn a thread or use your own executor here
133+
// once you have a concrete Track / VideoStream API.
134+
//
135+
// TODO: when you expose Track kind/source here, you can check whether
136+
// this is a video track and start a VideoStream-like consumer.
137+
}
138+
};
139+
91140
} // namespace
92141

93142
int main(int argc, char* argv[]) {
@@ -97,20 +146,75 @@ int main(int argc, char* argv[]) {
97146
return 1;
98147
}
99148

149+
// exit if token and url are not set (similar to Python example)
150+
if (url.empty() || token.empty()) {
151+
std::cerr << "LIVEKIT_URL and LIVEKIT_TOKEN (or CLI args) are required\n";
152+
return 1;
153+
}
154+
100155
std::cout << "Connecting to: " << url << std::endl;
101156

102157
// Handle Ctrl-C to exit the idle loop
103158
std::signal(SIGINT, handle_sigint);
104159

105-
Room room{};
106-
room.Connect(url.c_str(), token.c_str());
160+
livekit::Room room{};
161+
SimpleRoomDelegate delegate;
162+
room.setDelegate(&delegate);
163+
164+
bool res = room.Connect(url, token);
165+
std::cout << "Connect result is " << std::boolalpha << res << std::endl;
166+
if (!res) {
167+
std::cerr << "Failed to connect to room\n";
168+
FfiClient::instance().shutdown();
169+
return 1;
170+
}
171+
172+
auto info = room.room_info();
173+
std::cout << "Connected to room:\n"
174+
<< " SID: " << (info.sid ? *info.sid : "(none)") << "\n"
175+
<< " Name: " << info.name << "\n"
176+
<< " Metadata: " << info.metadata << "\n"
177+
<< " Max participants: " << info.max_participants << "\n"
178+
<< " Num participants: " << info.num_participants << "\n"
179+
<< " Num publishers: " << info.num_publishers << "\n"
180+
<< " Active recording: " << (info.active_recording ? "yes" : "no") << "\n"
181+
<< " Empty timeout (s): " << info.empty_timeout << "\n"
182+
<< " Departure timeout (s): " << info.departure_timeout << "\n"
183+
<< " Lossy DC low threshold: " << info.lossy_dc_buffered_amount_low_threshold << "\n"
184+
<< " Reliable DC low threshold: " << info.reliable_dc_buffered_amount_low_threshold << "\n"
185+
<< " Creation time (ms): " << info.creation_time << "\n";
186+
187+
188+
// TODO, implement local and remoteParticipants in the room
189+
/*
190+
const auto& participants = room.remoteParticipants(); // e.g. map<string, shared_ptr<RemoteParticipant>>
191+
for (const auto& [identity, participant] : participants) {
192+
std::cout << "identity: " << identity << "\n";
193+
std::cout << "participant sid: " << participant->sid() << "\n";
194+
std::cout << "participant identity: " << participant->identity() << "\n";
195+
std::cout << "participant name: " << participant->name() << "\n";
196+
std::cout << "participant kind: " << static_cast<int>(participant->kind()) << "\n";
197+
198+
const auto& pubs = participant->trackPublications(); // e.g. map<string, shared_ptr<RemoteTrackPublication>>
199+
std::cout << "participant track publications: " << pubs.size() << "\n";
200+
for (const auto& [tid, publication] : pubs) {
201+
std::cout << "\ttrack id: " << tid << "\n";
202+
std::cout << "\t\ttrack publication sid: " << publication->sid() << "\n";
203+
std::cout << "\t\ttrack kind: " << static_cast<int>(publication->kind()) << "\n";
204+
std::cout << "\t\ttrack name: " << publication->name() << "\n";
205+
std::cout << "\t\ttrack source: " << static_cast<int>(publication->source()) << "\n";
206+
}
207+
208+
std::cout << "participant metadata: " << participant->metadata() << "\n";
209+
}*/
107210

108-
// TODO: replace with proper event loop / callbacks.
109-
// For now, keep the app alive until Ctrl-C.
211+
// Keep the app alive until Ctrl-C so we continue receiving events,
212+
// similar to asyncio.run(main()) keeping the loop running.
110213
while (g_running.load()) {
111214
std::this_thread::sleep_for(std::chrono::milliseconds(100));
112215
}
113216

217+
FfiClient::instance().shutdown();
114218
std::cout << "Exiting.\n";
115219
return 0;
116220
}

include/livekit/ffi_client.h

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,26 @@
1818
#define LIVEKIT_FFI_CLIENT_H
1919

2020
#include <iostream>
21+
#include <future>
2122
#include <memory>
2223
#include <functional>
2324
#include <mutex>
2425
#include <unordered_map>
2526

2627
#include "ffi.pb.h"
2728

28-
namespace livekit
29-
{
29+
#include "livekit/stats.h"
30+
31+
namespace livekit {
32+
3033
using FfiCallbackFn = void(*)(const uint8_t*, size_t);
3134
extern "C" void livekit_ffi_initialize(FfiCallbackFn cb,
3235
bool capture_logs,
3336
const char* sdk,
3437
const char* sdk_version);
3538

39+
extern "C" void livekit_ffi_dispose();
40+
3641
extern "C" void LivekitFfiCallback(const uint8_t *buf, size_t len);
3742

3843

@@ -43,38 +48,74 @@ namespace livekit
4348
public:
4449
using ListenerId = int;
4550
using Listener = std::function<void(const proto::FfiEvent&)>;
51+
using AsyncId = std::uint64_t;
4652

4753
FfiClient(const FfiClient&) = delete;
4854
FfiClient& operator=(const FfiClient&) = delete;
55+
FfiClient(FfiClient&&) = delete;
56+
FfiClient& operator=(FfiClient&&) = delete;
4957

50-
static FfiClient& getInstance() {
58+
static FfiClient& instance() noexcept {
5159
static FfiClient instance;
5260
return instance;
5361
}
62+
63+
// Called only once. After calling shutdown(), no further calls into FfiClient are valid.
64+
void shutdown() noexcept;
5465

5566
ListenerId AddListener(const Listener& listener);
5667
void RemoveListener(ListenerId id);
5768

69+
// Room APIs
70+
std::future<livekit::proto::RoomInfo> connectAsync(const std::string& url, const std::string& token);
71+
72+
// Track APIs
73+
std::future<std::vector<RtcStats>> getTrackStatsAsync(uintptr_t track_handle);
74+
std::future<bool> localTrackMuteAsync(uintptr_t track_handle, bool mute);
75+
std::future<bool> enableRemoteTrackAsync(uintptr_t track_handle, bool enabled);
76+
5877
proto::FfiResponse SendRequest(const proto::FfiRequest& request)const;
5978

6079
private:
80+
// Base class for type-erased pending ops
81+
struct PendingBase {
82+
virtual ~PendingBase() = default;
83+
virtual bool matches(const proto::FfiEvent& event) const = 0;
84+
virtual void complete(const proto::FfiEvent& event) = 0;
85+
};
86+
template <typename T>
87+
struct Pending : PendingBase {
88+
std::promise<T> promise;
89+
std::function<bool(const proto::FfiEvent&)> match;
90+
std::function<void(const proto::FfiEvent&, std::promise<T>&)> handler;
91+
92+
bool matches(const proto::FfiEvent& event) const override {
93+
return match && match(event);
94+
}
95+
96+
void complete(const proto::FfiEvent& event) override {
97+
handler(event, promise);
98+
}
99+
};
100+
101+
template <typename T>
102+
std::future<T> registerAsync(
103+
std::function<bool(const proto::FfiEvent&)> match,
104+
std::function<void(const proto::FfiEvent&, std::promise<T>&)> handler);
105+
106+
107+
61108
std::unordered_map<ListenerId, Listener> listeners_;
62109
ListenerId nextListenerId = 1;
63110
mutable std::mutex lock_;
111+
mutable std::vector<std::unique_ptr<PendingBase>> pending_;
64112

65113
FfiClient();
66114
~FfiClient() = default;
67115

68116
void PushEvent(const proto::FfiEvent& event) const;
69117
friend void LivekitFfiCallback(const uint8_t *buf, size_t len);
70118
};
71-
72-
struct FfiHandle {
73-
uintptr_t handle;
74-
75-
FfiHandle(uintptr_t handle);
76-
~FfiHandle();
77-
};
78119
}
79120

80121
#endif /* LIVEKIT_FFI_CLIENT_H */

0 commit comments

Comments
 (0)