Skip to content
Merged
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 @@ -169,6 +169,7 @@ add_library(livekit
include/livekit/local_participant.h
include/livekit/remote_participant.h
include/livekit/livekit.h
include/livekit/rpc_error.h
include/livekit/stats.h
include/livekit/track.h
include/livekit/track_publication.h
Expand Down Expand Up @@ -198,6 +199,7 @@ add_library(livekit
src/track_publication.cpp
src/local_track_publication.cpp
src/remote_track_publication.cpp
src/rpc_error.cpp
src/video_frame.cpp
src/video_source.cpp
src/video_stream.cpp
Expand Down
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,16 @@ All build actions are managed by the provided build.sh script.

## 🧪 Run Example

### Generate Tokens
Before running any participant, create JWT tokens with the proper identity and room name, example
```bash
./build/examples/SimpleRoom --url ws://localhost:7880 --token <jwt-token>
lk token create -r test -i your_own_identity --join --valid-for 99999h --dev --room=your_own_room
```

### SimpleRoom

```bash
./build/examples/SimpleRoom --url $URL --token <jwt-token>
```

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

Press Ctrl-C to exit the example.

### SimpleRpc
The SimpleRpc example demonstrates how to:
- Connect multiple participants to the same LiveKit room
- Register RPC handlers (e.g., arrival, square-root, divide, long-calculation)
- Send RPC requests from one participant to another
- Handle success, application errors, unsupported methods, and timeouts
- Observe round-trip times (RTT) for each RPC call

#### 🔑 Generate Tokens
Before running any participant, create JWT tokens with **caller**, **greeter** and **math-genius** identities and room name.
```bash
lk token create -r test -i caller --join --valid-for 99999h --dev --room=your_own_room
lk token create -r test -i greeter --join --valid-for 99999h --dev --room=your_own_room
lk token create -r test -i math-genius --join --valid-for 99999h --dev --room=your_own_room
```

#### ▶ Start Participants
Every participant is run as a separate terminal process, note --role needs to match the token identity.
```bash
./build/examples/SimpleRpc --url $URL --token <jwt-token> --role=math-genius
```
The caller will automatically:
- Wait for the greeter and math-genius to join
- Perform RPC calls
- Print round-trip times
- Annotate expected successes or expected failures

## 🧰 Recommended Setup
### macOS
```bash
Expand Down
21 changes: 21 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ project (livekit-examples)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

#################### SimpleRoom example ##########################

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(sdl3)

Expand Down Expand Up @@ -32,3 +34,22 @@ add_custom_command(TARGET SimpleRoom POST_BUILD
${CMAKE_SOURCE_DIR}/data
${CMAKE_CURRENT_BINARY_DIR}/data
)

#################### SimpleRpc example ##########################

include(FetchContent)
FetchContent_Declare(
nlohmann_json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
)
FetchContent_MakeAvailable(nlohmann_json)

add_executable(SimpleRpc
simple_rpc/main.cpp
)

target_link_libraries(SimpleRpc
PRIVATE
nlohmann_json::nlohmann_json
livekit
)
12 changes: 6 additions & 6 deletions examples/simple_room/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ namespace {

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

void print_usage(const char *prog) {
void printUsage(const char *prog) {
std::cerr << "Usage:\n"
<< " " << prog << " <ws-url> <token>\n"
<< "or:\n"
Expand All @@ -52,9 +52,9 @@ void print_usage(const char *prog) {
<< " LIVEKIT_URL, LIVEKIT_TOKEN\n";
}

void handle_sigint(int) { g_running.store(false); }
void handleSignal(int) { g_running.store(false); }

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

int main(int argc, char *argv[]) {
std::string url, token;
if (!parse_args(argc, argv, url, token)) {
print_usage(argv[0]);
if (!parseArgs(argc, argv, url, token)) {
printUsage(argv[0]);
return 1;
}

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

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

livekit::Room room{};
SimpleRoomDelegate delegate(media);
Expand Down
157 changes: 157 additions & 0 deletions examples/simple_rpc/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# 📘 SimpleRpc Example — Technical Overview

This README provides deeper technical details about the RPC (Remote Procedure Call) support demonstrated in the SimpleRpc example.
It complements the example instructions found in the root README.md.

If you're looking for how to run the example, see the root [README](https://github.com/livekit/client-sdk-cpp).

This document explains:
- How LiveKit RPC works in the C++ SDK
- Where the APIs are defined
- How senders call RPC methods
- How receivers register handlers
- What happens if the receiver is absent
- How long-running operations behave
- Timeouts, disconnects, and unsupported methods
- RPC lifecycle events and error propagation

## 🔧 Overview: How RPC Works
LiveKit RPC allows one participant (the caller) to invoke a method on another participant (the receiver) using the data channel transport.
It is:
- Peer-to-peer within the room (not server-executed RPC)
- Request/response (caller waits for a reply or an error)
- Asynchronous under the hood, synchronous or blocking from the caller’s perspective
- Delivery-guaranteed when using the reliable data channel

Each RPC call includes:
| Field | Meaning |
|--------------------------|-----------------------------------------------------|
| **destination_identity** | Identity of the target participant |
| **method** | Method name string (e.g., "square-root") |
| **payload** | Arbitrary UTF-8 text |
| **response_timeout** | Optional timeout (seconds) |
| **invocation_id** | Server-generated ID used internally for correlation |

## 📍 Location of APIs in C++
All public-facing RPC APIs live in:
[include/livekit/local_participant.h](https://github.com/livekit/client-sdk-cpp/blob/main/include/livekit/local_participant.h#L160)

### Key methods:

#### Sender-side APIs
```bash
std::string performRpc(
const std::string& destination_identity,
const std::string& method,
const std::string& payload,
std::optional<double> response_timeout_sec = std::nullopt
);

Receiver-side APIs
void registerRpcMethod(
const std::string& method_name,
RpcHandler handler
);

void unregisterRpcMethod(const std::string& method_name);

Handler signature
using RpcHandler =
std::function<std::optional<std::string>(const RpcInvocationData&)>;
```

Handlers can:
- Return a string (the RPC response payload)
- Return std::nullopt (meaning “no return payload”)
- Throw exceptions (mapped to APPLICATION_ERROR)
- Throw a RpcError (mapped to specific error codes)

#### 🛰 Sender Behavior (performRpc)

When the caller invokes:
```bash
auto reply = lp->performRpc("math-genius", "square-root", "{\"number\":16}");
```

The following occurs:

A PerformRpcRequest is sent through FFI to the SDK core.

The SDK transmits the invocation to the target participant (if present).

The caller begins waiting for a matching RpcMethodInvocationResponse.

One of the following happens:
| Outcome | Meaning |
|--------------------------|------------------------------------------|
| **Success** | Receiver returned a payload |
| **UNSUPPORTED_METHOD** | Receiver did not register the method |
| **RECIPIENT_NOT_FOUND** | Target identity not present in room |
| **RECIPIENT_DISCONNECTED** | Target left before replying |
| **RESPONSE_TIMEOUT** | Receiver took too long |
| **APPLICATION_ERROR** | Handler threw an exception |

#### 🔄 Round-trip time (RTT)

The caller can measure RTT externally (as SimpleRpc does), but the SDK does not measure RTT internally.

#### 📡 Receiver Behavior (registerRpcMethod)

A receiver must explicitly register handlers:
```bash
local_participant->registerRpcMethod("square-root",
[](const RpcInvocationData& data) {
double number = parse(data.payload);
return make_json("result", std::sqrt(number));
});
```

When an invocation arrives:
- Room receives a RpcMethodInvocationEvent
- Room forwards it to the corresponding LocalParticipant
- LocalParticipant::handleRpcMethodInvocation():
- Calls the handler
- Converts any exceptions into RpcError
- Sends back RpcMethodInvocationResponse

⚠ If no handler exists:

Receiver returns: UNSUPPORTED_METHOD


#### 🚨 What Happens if Receiver Is Absent?
| Case | Behavior |
|-----------------------------------------------------|---------------------------------------------------|
| Receiver identity is not in the room | Caller immediately receives `RECIPIENT_NOT_FOUND` |
| Receiver is present but disconnects before replying | Caller receives `RECIPIENT_DISCONNECTED` |
| Receiver joins later | Caller must retry manually (no automatic waiting) |

**Important**:
LiveKit does not queue RPC calls for offline participants.

#### ⏳ Timeout Behavior

If the caller specifies:

performRpc(..., /*response_timeout=*/10.0);

Then:
- Receiver is given 10 seconds to respond.
- If the receiver handler takes longer (e.g., sleep 30s), caller receives:
RESPONSE_TIMEOUT

**If no response_timeout is provided explicitly, the default timeout is 15 seconds.**


This is by design and demonstrated in the example.

#### 🧨 Errors & Failure Modes
| Error Code | Cause |
|------------------------|---------------------------------------------|
| **APPLICATION_ERROR** | Handler threw a C++ exception |
| **UNSUPPORTED_METHOD** | No handler registered for the method |
| **RECIPIENT_NOT_FOUND** | Destination identity not in room |
| **RECIPIENT_DISCONNECTED** | Participant left mid-flight |
| **RESPONSE_TIMEOUT** | Handler exceeded allowed response time |
| **CONNECTION_TIMEOUT** | Transport-level issue |
| **SEND_FAILED** | SDK failed to send invocation |
Loading
Loading