Skip to content

Commit 22498a1

Browse files
Bridge RPC: Mute/unmute working examples with audio/video
RPCManager: bridge object for managing default and custom RPC calls bridge_rpc examples RPCManager tests requestTrackMute/Unmute -> requestRemoteTrackMute/Unmute . Make the trackActionFn action input an Enum add spdlog to docker and github actions
1 parent 42b046c commit 22498a1

31 files changed

+2446
-151
lines changed

.github/workflows/builds.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,15 @@ jobs:
108108
libssl-dev \
109109
libprotobuf-dev protobuf-compiler \
110110
libabsl-dev \
111-
libwayland-dev libdecor-0-dev
111+
libwayland-dev libdecor-0-dev \
112+
libspdlog-dev
112113
113114
- name: Install deps (macOS)
114115
if: runner.os == 'macOS'
115116
run: |
116117
set -eux
117118
brew update
118-
brew install cmake ninja protobuf abseil
119+
brew install cmake ninja protobuf abseil spdlog
119120
120121
# ---------- Rust toolchain ----------
121122
- name: Install Rust (stable)

bridge/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ add_library(livekit_bridge SHARED
1111
src/bridge_video_track.cpp
1212
src/bridge_room_delegate.cpp
1313
src/bridge_room_delegate.h
14+
src/rpc_controller.cpp
15+
src/rpc_controller.h
1416
)
1517

1618
if(WIN32)

bridge/README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,21 @@ bridge.setOnVideoFrameCallback("remote-peer", livekit::TrackSource::SOURCE_CAMER
4444
// Called on a background reader thread
4545
});
4646

47-
// 5. Cleanup is automatic (RAII), or explicit:
47+
// 5. RPC (Remote Procedure Call)
48+
bridge.registerRpcMethod("greet",
49+
[](const livekit::RpcInvocationData& data) -> std::optional<std::string> {
50+
return "Hello, " + data.caller_identity + "!";
51+
});
52+
53+
std::string response = bridge.performRpc("remote-peer", "greet", "");
54+
55+
bridge.unregisterRpcMethod("greet");
56+
57+
// Controller side: send commands to the publisher
58+
controller_bridge.requestRemoteTrackMute("robot-1", "mic"); // mute audio track "mic"
59+
controller_bridge.requestRemoteTrackUnmute("robot-1", "mic"); // unmute it
60+
61+
// 7. Cleanup is automatic (RAII), or explicit:
4862
mic.reset(); // unpublishes the audio track
4963
cam.reset(); // unpublishes the video track
5064
bridge.disconnect();
@@ -138,6 +152,11 @@ bridge.connect(url, token, options);
138152
| `setOnVideoFrameCallback(identity, source, callback)` | Register a callback for video frames from a specific remote participant + track source. |
139153
| `clearOnAudioFrameCallback(identity, source)` | Clear the audio callback for a specific remote participant + track source. Stops and joins the reader thread if active. |
140154
| `clearOnVideoFrameCallback(identity, source)` | Clear the video callback for a specific remote participant + track source. Stops and joins the reader thread if active. |
155+
| `performRpc(destination_identity, method, payload, response_timeout?)` | Blocking RPC call to a remote participant. Returns the response payload. Throws `livekit::RpcError` on failure. |
156+
| `registerRpcMethod(method_name, handler)` | Register a handler for incoming RPC invocations. The handler returns an optional response payload or throws `livekit::RpcError`. |
157+
| `unregisterRpcMethod(method_name)` | Unregister a previously registered RPC handler. |
158+
| `requestRemoteTrackMute(identity, track_name)` | Ask a remote participant to mute a track by name. Throws `livekit::RpcError` on failure. |
159+
| `requestRemoteTrackUnmute(identity, track_name)` | Ask a remote participant to unmute a track by name. Throws `livekit::RpcError` on failure. |
141160

142161
### `BridgeAudioTrack`
143162

@@ -240,7 +259,7 @@ The bridge is designed for simplicity and currently only supports limited audio
240259

241260
- We dont support all events defined in the RoomDelegate interface.
242261
- E2EE configuration
243-
- RPC / data channels / data tracks
262+
- data tracks
244263
- Simulcast tuning
245264
- Video format selection (RGBA is the default; no format option yet)
246265
- Custom `RoomOptions` or `TrackPublishOptions`

bridge/include/livekit_bridge/livekit_bridge.h

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121

2222
#include "livekit_bridge/bridge_audio_track.h"
2323
#include "livekit_bridge/bridge_video_track.h"
24+
#include "livekit_bridge/rpc_constants.h"
2425

26+
#include "livekit/local_participant.h"
2527
#include "livekit/room.h"
28+
#include "livekit/rpc_error.h"
2629

2730
#include <cstdint>
2831
#include <functional>
@@ -46,6 +49,7 @@ enum class TrackSource;
4649
namespace livekit_bridge {
4750

4851
class BridgeRoomDelegate;
52+
class RpcController;
4953

5054
namespace test {
5155
class CallbackKeyTest;
@@ -264,6 +268,90 @@ class LiveKitBridge {
264268
void clearOnVideoFrameCallback(const std::string &participant_identity,
265269
livekit::TrackSource source);
266270

271+
// ---------------------------------------------------------------
272+
// RPC (Remote Procedure Call)
273+
// ---------------------------------------------------------------
274+
275+
/**
276+
* Initiate a blocking RPC call to a remote participant.
277+
*
278+
* Sends a request to the participant identified by
279+
* @p destination_identity and blocks until a response is received
280+
* or the call times out.
281+
*
282+
* @param destination_identity Identity of the remote participant.
283+
* @param method Name of the RPC method to invoke.
284+
* @param payload Request payload string.
285+
* @param response_timeout Optional timeout in seconds. If not set,
286+
* the server default (15 s) is used.
287+
* @return The response payload returned by the remote handler. nullptr if the
288+
* RPC call fails, or the bridge is not connected.
289+
*/
290+
std::optional<std::string>
291+
performRpc(const std::string &destination_identity, const std::string &method,
292+
const std::string &payload,
293+
const std::optional<double> &response_timeout = std::nullopt);
294+
295+
/**
296+
* Register a handler for incoming RPC method invocations.
297+
*
298+
* When a remote participant calls the given @p method_name on this
299+
* participant, the bridge invokes @p handler. The handler may return
300+
* an optional response payload or throw a @c livekit::RpcError to
301+
* signal failure to the caller.
302+
*
303+
* If a handler is already registered for @p method_name, it is
304+
* silently replaced.
305+
*
306+
* @param method_name Name of the RPC method to handle.
307+
* @param handler Callback invoked on each incoming invocation.
308+
* @return true if the RPC method was registered successfully.
309+
*/
310+
bool registerRpcMethod(const std::string &method_name,
311+
livekit::LocalParticipant::RpcHandler handler);
312+
313+
/**
314+
* Unregister a previously registered RPC method handler.
315+
*
316+
* After this call, invocations for @p method_name result in an
317+
* "unsupported method" error being returned to the remote caller.
318+
* If no handler is registered for this name, the call is a no-op.
319+
*
320+
* @param method_name Name of the RPC method to unregister.
321+
* @return true if the RPC method was unregistered successfully.
322+
*/
323+
bool unregisterRpcMethod(const std::string &method_name);
324+
325+
// ---------------------------------------------------------------
326+
// Remote Track Control (via RPC)
327+
// ---------------------------------------------------------------
328+
329+
/**
330+
* Request a remote participant to mute a published track.
331+
*
332+
* The remote participant must be a LiveKitBridge instance (which
333+
* automatically registers the built-in track-control RPC handler).
334+
*
335+
* @param destination_identity Identity of the remote participant.
336+
* @param track_name Name of the track to mute.
337+
* @return true if the track was muted successfully.
338+
*/
339+
bool requestRemoteTrackMute(const std::string &destination_identity,
340+
const std::string &track_name);
341+
342+
/**
343+
* Request a remote participant to unmute a published track.
344+
*
345+
* The remote participant must be a LiveKitBridge instance (which
346+
* automatically registers the built-in track-control RPC handler).
347+
*
348+
* @param destination_identity Identity of the remote participant.
349+
* @param track_name Name of the track to unmute.
350+
* @return true if the track was unmuted successfully.
351+
*/
352+
bool requestRemoteTrackUnmute(const std::string &destination_identity,
353+
const std::string &track_name);
354+
267355
private:
268356
friend class BridgeRoomDelegate;
269357
friend class test::CallbackKeyTest;
@@ -314,6 +402,13 @@ class LiveKitBridge {
314402
const std::shared_ptr<livekit::Track> &track,
315403
VideoFrameCallback cb);
316404

405+
/// Execute a track action (mute/unmute) by track name.
406+
/// Used as the TrackActionFn callback for RpcController.
407+
/// Throws livekit::RpcError if the track is not found.
408+
/// @pre Caller does NOT hold mutex_ (acquires it internally).
409+
void executeTrackAction(const rpc::track_control::Action &action,
410+
const std::string &track_name);
411+
317412
mutable std::mutex mutex_;
318413
bool connected_;
319414
bool connecting_; // guards against concurrent connect() calls
@@ -323,6 +418,7 @@ class LiveKitBridge {
323418

324419
std::unique_ptr<livekit::Room> room_;
325420
std::unique_ptr<BridgeRoomDelegate> delegate_;
421+
std::unique_ptr<RpcController> rpc_controller_;
326422

327423
/// Registered callbacks (may be registered before tracks are subscribed).
328424
std::unordered_map<CallbackKey, AudioFrameCallback, CallbackKeyHash>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2026 LiveKit
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/// @file rpc_constants.h
18+
/// @brief Constants for built-in bridge RPC methods.
19+
20+
#pragma once
21+
22+
#include <string>
23+
24+
namespace livekit_bridge {
25+
namespace rpc {
26+
27+
/// Built-in RPC method name used by remote track control.
28+
/// Allows remote participants to mute or unmute tracks
29+
/// published by this bridge. Must be called after connect().
30+
/// Audio/video tracks support mute and unmute. Data tracks
31+
/// only support mute and unmute.
32+
namespace track_control {
33+
34+
enum class Action { kActionMute, kActionUnmute };
35+
36+
/// RPC method name registered by the bridge for remote track control.
37+
constexpr const char *kMethod = "lk.bridge.track-control";
38+
39+
/// Payload action strings.
40+
constexpr const char *kActionMute = "mute";
41+
constexpr const char *kActionUnmute = "unmute";
42+
43+
/// Delimiter between action and track name in the payload (e.g. "mute:cam").
44+
constexpr char kDelimiter = ':';
45+
46+
/// Response payload returned on success.
47+
constexpr const char *kResponseOk = "ok";
48+
49+
/// Build a track-control RPC payload: "<action>:<track_name>".
50+
inline std::string formatPayload(const char *action,
51+
const std::string &track_name) {
52+
std::string payload;
53+
payload.reserve(std::char_traits<char>::length(action) + 1 +
54+
track_name.size());
55+
payload += action;
56+
payload += kDelimiter;
57+
payload += track_name;
58+
return payload;
59+
}
60+
61+
} // namespace track_control
62+
} // namespace rpc
63+
} // namespace livekit_bridge

0 commit comments

Comments
 (0)