Skip to content

Commit 96e7643

Browse files
RPC features (#13)
* RPC features * added a new README and fix the comments * point to the right line * fix the linux build * fix the build and addressed the comments * fix the build * recover the room.cpp * recover room.cpp from the uncheckin changes
1 parent 1319391 commit 96e7643

File tree

16 files changed

+1244
-124
lines changed

16 files changed

+1244
-124
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ add_library(livekit
169169
include/livekit/local_participant.h
170170
include/livekit/remote_participant.h
171171
include/livekit/livekit.h
172+
include/livekit/rpc_error.h
172173
include/livekit/stats.h
173174
include/livekit/track.h
174175
include/livekit/track_publication.h
@@ -198,6 +199,7 @@ add_library(livekit
198199
src/track_publication.cpp
199200
src/local_track_publication.cpp
200201
src/remote_track_publication.cpp
202+
src/rpc_error.cpp
201203
src/video_frame.cpp
202204
src/video_source.cpp
203205
src/video_stream.cpp

README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,16 @@ All build actions are managed by the provided build.sh script.
4141

4242
## 🧪 Run Example
4343

44+
### Generate Tokens
45+
Before running any participant, create JWT tokens with the proper identity and room name, example
4446
```bash
45-
./build/examples/SimpleRoom --url ws://localhost:7880 --token <jwt-token>
47+
lk token create -r test -i your_own_identity --join --valid-for 99999h --dev --room=your_own_room
48+
```
49+
50+
### SimpleRoom
51+
52+
```bash
53+
./build/examples/SimpleRoom --url $URL --token <jwt-token>
4654
```
4755

4856
You can also provide the URL and token via environment variables:
@@ -54,6 +62,33 @@ export LIVEKIT_TOKEN=<jwt-token>
5462

5563
Press Ctrl-C to exit the example.
5664

65+
### SimpleRpc
66+
The SimpleRpc example demonstrates how to:
67+
- Connect multiple participants to the same LiveKit room
68+
- Register RPC handlers (e.g., arrival, square-root, divide, long-calculation)
69+
- Send RPC requests from one participant to another
70+
- Handle success, application errors, unsupported methods, and timeouts
71+
- Observe round-trip times (RTT) for each RPC call
72+
73+
#### 🔑 Generate Tokens
74+
Before running any participant, create JWT tokens with **caller**, **greeter** and **math-genius** identities and room name.
75+
```bash
76+
lk token create -r test -i caller --join --valid-for 99999h --dev --room=your_own_room
77+
lk token create -r test -i greeter --join --valid-for 99999h --dev --room=your_own_room
78+
lk token create -r test -i math-genius --join --valid-for 99999h --dev --room=your_own_room
79+
```
80+
81+
#### ▶ Start Participants
82+
Every participant is run as a separate terminal process, note --role needs to match the token identity.
83+
```bash
84+
./build/examples/SimpleRpc --url $URL --token <jwt-token> --role=math-genius
85+
```
86+
The caller will automatically:
87+
- Wait for the greeter and math-genius to join
88+
- Perform RPC calls
89+
- Print round-trip times
90+
- Annotate expected successes or expected failures
91+
5792
## 🧰 Recommended Setup
5893
### macOS
5994
```bash

examples/CMakeLists.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ project (livekit-examples)
44
set(CMAKE_CXX_STANDARD 17)
55
set(CMAKE_CXX_STANDARD_REQUIRED ON)
66

7+
#################### SimpleRoom example ##########################
8+
79
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
810
include(sdl3)
911

@@ -32,3 +34,22 @@ add_custom_command(TARGET SimpleRoom POST_BUILD
3234
${CMAKE_SOURCE_DIR}/data
3335
${CMAKE_CURRENT_BINARY_DIR}/data
3436
)
37+
38+
#################### SimpleRpc example ##########################
39+
40+
include(FetchContent)
41+
FetchContent_Declare(
42+
nlohmann_json
43+
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
44+
)
45+
FetchContent_MakeAvailable(nlohmann_json)
46+
47+
add_executable(SimpleRpc
48+
simple_rpc/main.cpp
49+
)
50+
51+
target_link_libraries(SimpleRpc
52+
PRIVATE
53+
nlohmann_json::nlohmann_json
54+
livekit
55+
)

examples/simple_room/main.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ namespace {
4242

4343
std::atomic<bool> g_running{true};
4444

45-
void print_usage(const char *prog) {
45+
void printUsage(const char *prog) {
4646
std::cerr << "Usage:\n"
4747
<< " " << prog << " <ws-url> <token>\n"
4848
<< "or:\n"
@@ -52,9 +52,9 @@ void print_usage(const char *prog) {
5252
<< " LIVEKIT_URL, LIVEKIT_TOKEN\n";
5353
}
5454

55-
void handle_sigint(int) { g_running.store(false); }
55+
void handleSignal(int) { g_running.store(false); }
5656

57-
bool parse_args(int argc, char *argv[], std::string &url, std::string &token) {
57+
bool parseArgs(int argc, char *argv[], std::string &url, std::string &token) {
5858
// 1) --help
5959
for (int i = 1; i < argc; ++i) {
6060
std::string a = argv[i];
@@ -215,8 +215,8 @@ class SimpleRoomDelegate : public livekit::RoomDelegate {
215215

216216
int main(int argc, char *argv[]) {
217217
std::string url, token;
218-
if (!parse_args(argc, argv, url, token)) {
219-
print_usage(argv[0]);
218+
if (!parseArgs(argc, argv, url, token)) {
219+
printUsage(argv[0]);
220220
return 1;
221221
}
222222

@@ -238,7 +238,7 @@ int main(int argc, char *argv[]) {
238238
std::cout << "Connecting to: " << url << std::endl;
239239

240240
// Handle Ctrl-C to exit the idle loop
241-
std::signal(SIGINT, handle_sigint);
241+
std::signal(SIGINT, handleSignal);
242242

243243
livekit::Room room{};
244244
SimpleRoomDelegate delegate(media);

examples/simple_rpc/README

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# 📘 SimpleRpc Example — Technical Overview
2+
3+
This README provides deeper technical details about the RPC (Remote Procedure Call) support demonstrated in the SimpleRpc example.
4+
It complements the example instructions found in the root README.md.
5+
6+
If you're looking for how to run the example, see the root [README](https://github.com/livekit/client-sdk-cpp).
7+
8+
This document explains:
9+
- How LiveKit RPC works in the C++ SDK
10+
- Where the APIs are defined
11+
- How senders call RPC methods
12+
- How receivers register handlers
13+
- What happens if the receiver is absent
14+
- How long-running operations behave
15+
- Timeouts, disconnects, and unsupported methods
16+
- RPC lifecycle events and error propagation
17+
18+
## 🔧 Overview: How RPC Works
19+
LiveKit RPC allows one participant (the caller) to invoke a method on another participant (the receiver) using the data channel transport.
20+
It is:
21+
- Peer-to-peer within the room (not server-executed RPC)
22+
- Request/response (caller waits for a reply or an error)
23+
- Asynchronous under the hood, synchronous or blocking from the caller’s perspective
24+
- Delivery-guaranteed when using the reliable data channel
25+
26+
Each RPC call includes:
27+
| Field | Meaning |
28+
|--------------------------|-----------------------------------------------------|
29+
| **destination_identity** | Identity of the target participant |
30+
| **method** | Method name string (e.g., "square-root") |
31+
| **payload** | Arbitrary UTF-8 text |
32+
| **response_timeout** | Optional timeout (seconds) |
33+
| **invocation_id** | Server-generated ID used internally for correlation |
34+
35+
## 📍 Location of APIs in C++
36+
All public-facing RPC APIs live in:
37+
[include/livekit/local_participant.h](https://github.com/livekit/client-sdk-cpp/blob/main/include/livekit/local_participant.h#L160)
38+
39+
### Key methods:
40+
41+
#### Sender-side APIs
42+
```bash
43+
std::string performRpc(
44+
const std::string& destination_identity,
45+
const std::string& method,
46+
const std::string& payload,
47+
std::optional<double> response_timeout_sec = std::nullopt
48+
);
49+
50+
Receiver-side APIs
51+
void registerRpcMethod(
52+
const std::string& method_name,
53+
RpcHandler handler
54+
);
55+
56+
void unregisterRpcMethod(const std::string& method_name);
57+
58+
Handler signature
59+
using RpcHandler =
60+
std::function<std::optional<std::string>(const RpcInvocationData&)>;
61+
```
62+
63+
Handlers can:
64+
- Return a string (the RPC response payload)
65+
- Return std::nullopt (meaning “no return payload”)
66+
- Throw exceptions (mapped to APPLICATION_ERROR)
67+
- Throw a RpcError (mapped to specific error codes)
68+
69+
#### 🛰 Sender Behavior (performRpc)
70+
71+
When the caller invokes:
72+
```bash
73+
auto reply = lp->performRpc("math-genius", "square-root", "{\"number\":16}");
74+
```
75+
76+
The following occurs:
77+
78+
A PerformRpcRequest is sent through FFI to the SDK core.
79+
80+
The SDK transmits the invocation to the target participant (if present).
81+
82+
The caller begins waiting for a matching RpcMethodInvocationResponse.
83+
84+
One of the following happens:
85+
| Outcome | Meaning |
86+
|--------------------------|------------------------------------------|
87+
| **Success** | Receiver returned a payload |
88+
| **UNSUPPORTED_METHOD** | Receiver did not register the method |
89+
| **RECIPIENT_NOT_FOUND** | Target identity not present in room |
90+
| **RECIPIENT_DISCONNECTED** | Target left before replying |
91+
| **RESPONSE_TIMEOUT** | Receiver took too long |
92+
| **APPLICATION_ERROR** | Handler threw an exception |
93+
94+
#### 🔄 Round-trip time (RTT)
95+
96+
The caller can measure RTT externally (as SimpleRpc does), but the SDK does not measure RTT internally.
97+
98+
#### 📡 Receiver Behavior (registerRpcMethod)
99+
100+
A receiver must explicitly register handlers:
101+
```bash
102+
local_participant->registerRpcMethod("square-root",
103+
[](const RpcInvocationData& data) {
104+
double number = parse(data.payload);
105+
return make_json("result", std::sqrt(number));
106+
});
107+
```
108+
109+
When an invocation arrives:
110+
- Room receives a RpcMethodInvocationEvent
111+
- Room forwards it to the corresponding LocalParticipant
112+
- LocalParticipant::handleRpcMethodInvocation():
113+
- Calls the handler
114+
- Converts any exceptions into RpcError
115+
- Sends back RpcMethodInvocationResponse
116+
117+
⚠ If no handler exists:
118+
119+
Receiver returns: UNSUPPORTED_METHOD
120+
121+
122+
#### 🚨 What Happens if Receiver Is Absent?
123+
| Case | Behavior |
124+
|-----------------------------------------------------|---------------------------------------------------|
125+
| Receiver identity is not in the room | Caller immediately receives `RECIPIENT_NOT_FOUND` |
126+
| Receiver is present but disconnects before replying | Caller receives `RECIPIENT_DISCONNECTED` |
127+
| Receiver joins later | Caller must retry manually (no automatic waiting) |
128+
129+
**Important**:
130+
LiveKit does not queue RPC calls for offline participants.
131+
132+
#### ⏳ Timeout Behavior
133+
134+
If the caller specifies:
135+
136+
performRpc(..., /*response_timeout=*/10.0);
137+
138+
Then:
139+
- Receiver is given 10 seconds to respond.
140+
- If the receiver handler takes longer (e.g., sleep 30s), caller receives:
141+
RESPONSE_TIMEOUT
142+
143+
**If no response_timeout is provided explicitly, the default timeout is 15 seconds.**
144+
145+
146+
This is by design and demonstrated in the example.
147+
148+
#### 🧨 Errors & Failure Modes
149+
| Error Code | Cause |
150+
|------------------------|---------------------------------------------|
151+
| **APPLICATION_ERROR** | Handler threw a C++ exception |
152+
| **UNSUPPORTED_METHOD** | No handler registered for the method |
153+
| **RECIPIENT_NOT_FOUND** | Destination identity not in room |
154+
| **RECIPIENT_DISCONNECTED** | Participant left mid-flight |
155+
| **RESPONSE_TIMEOUT** | Handler exceeded allowed response time |
156+
| **CONNECTION_TIMEOUT** | Transport-level issue |
157+
| **SEND_FAILED** | SDK failed to send invocation |

0 commit comments

Comments
 (0)