Skip to content
Open
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 bridge/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ add_library(livekit_bridge SHARED
src/bridge_video_track.cpp
src/bridge_room_delegate.cpp
src/bridge_room_delegate.h
src/rpc_manager.cpp
src/rpc_manager.h
)

if(WIN32)
Expand Down
25 changes: 23 additions & 2 deletions bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,22 @@ bridge.setOnVideoFrameCallback("remote-peer", livekit::TrackSource::SOURCE_CAMER
// Called on a background reader thread
});

// 5. Cleanup is automatic (RAII), or explicit:
// 5. RPC (Remote Procedure Call)
bridge.registerRpcMethod("greet",
[](const livekit::RpcInvocationData& data) -> std::optional<std::string> {
return "Hello, " + data.caller_identity + "!";
});

std::string response = bridge.performRpc("remote-peer", "greet", "");

bridge.unregisterRpcMethod("greet");

// Controller side: send commands to the publisher
controller_bridge.requestTrackMute("robot-1", "mic"); // mute audio track "mic"
controller_bridge.requestTrackUnmute("robot-1", "mic"); // unmute it
controller_bridge.requestTrackRelease("robot-1", "cam"); // unpublish video track "cam"

// 7. Cleanup is automatic (RAII), or explicit:
mic.reset(); // unpublishes the audio track
cam.reset(); // unpublishes the video track
bridge.disconnect();
Expand Down Expand Up @@ -138,6 +153,12 @@ bridge.connect(url, token, options);
| `setOnVideoFrameCallback(identity, source, callback)` | Register a callback for video frames from a specific remote participant + track source. |
| `clearOnAudioFrameCallback(identity, source)` | Clear the audio callback for a specific remote participant + track source. Stops and joins the reader thread if active. |
| `clearOnVideoFrameCallback(identity, source)` | Clear the video callback for a specific remote participant + track source. Stops and joins the reader thread if active. |
| `performRpc(destination_identity, method, payload, response_timeout?)` | Blocking RPC call to a remote participant. Returns the response payload. Throws `livekit::RpcError` on failure. |
| `registerRpcMethod(method_name, handler)` | Register a handler for incoming RPC invocations. The handler returns an optional response payload or throws `livekit::RpcError`. |
| `unregisterRpcMethod(method_name)` | Unregister a previously registered RPC handler. |
| `requestTrackMute(identity, track_name)` | Ask a remote participant to mute a track by name. Throws `livekit::RpcError` on failure. |
| `requestTrackUnmute(identity, track_name)` | Ask a remote participant to unmute a track by name. Throws `livekit::RpcError` on failure. |
| `requestTrackRelease(identity, track_name)` | Ask a remote participant to release (unpublish) a track by name. Throws `livekit::RpcError` on failure. |

### `BridgeAudioTrack`

Expand Down Expand Up @@ -240,7 +261,7 @@ The bridge is designed for simplicity and currently only supports limited audio

- We dont support all events defined in the RoomDelegate interface.
- E2EE configuration
- RPC / data channels / data tracks
- data tracks
- Simulcast tuning
- Video format selection (RGBA is the default; no format option yet)
- Custom `RoomOptions` or `TrackPublishOptions`
Expand Down
96 changes: 95 additions & 1 deletion bridge/include/livekit_bridge/livekit_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
#include "livekit_bridge/bridge_audio_track.h"
#include "livekit_bridge/bridge_video_track.h"

#include "livekit/local_participant.h"
#include "livekit/room.h"
#include "livekit/rpc_error.h"

#include <cstdint>
#include <functional>
Expand All @@ -46,6 +48,7 @@ enum class TrackSource;
namespace livekit_bridge {

class BridgeRoomDelegate;
class RpcManager;

namespace test {
class CallbackKeyTest;
Expand Down Expand Up @@ -264,6 +267,90 @@ class LiveKitBridge {
void clearOnVideoFrameCallback(const std::string &participant_identity,
livekit::TrackSource source);

// ---------------------------------------------------------------
// RPC (Remote Procedure Call)
// ---------------------------------------------------------------

/**
* Initiate a blocking RPC call to a remote participant.
*
* Sends a request to the participant identified by
* @p destination_identity and blocks until a response is received
* or the call times out.
*
* @param destination_identity Identity of the remote participant.
* @param method Name of the RPC method to invoke.
* @param payload Request payload string.
* @param response_timeout Optional timeout in seconds. If not set,
* the server default (15 s) is used.
* @return The response payload returned by the remote handler. nullptr if the
* RPC call fails, or the bridge is not connected.
*/
std::optional<std::string>
performRpc(const std::string &destination_identity, const std::string &method,
const std::string &payload,
const std::optional<double> &response_timeout = std::nullopt);

/**
* Register a handler for incoming RPC method invocations.
*
* When a remote participant calls the given @p method_name on this
* participant, the bridge invokes @p handler. The handler may return
* an optional response payload or throw a @c livekit::RpcError to
* signal failure to the caller.
*
* If a handler is already registered for @p method_name, it is
* silently replaced.
*
* @param method_name Name of the RPC method to handle.
* @param handler Callback invoked on each incoming invocation.
* @return true if the RPC method was registered successfully.
*/
bool registerRpcMethod(const std::string &method_name,
livekit::LocalParticipant::RpcHandler handler);

/**
* Unregister a previously registered RPC method handler.
*
* After this call, invocations for @p method_name result in an
* "unsupported method" error being returned to the remote caller.
* If no handler is registered for this name, the call is a no-op.
*
* @param method_name Name of the RPC method to unregister.
* @return true if the RPC method was unregistered successfully.
*/
bool unregisterRpcMethod(const std::string &method_name);

// ---------------------------------------------------------------
// Remote Track Control (via RPC)
// ---------------------------------------------------------------

/**
* Request a remote participant to mute a published track.
*
* The remote participant must be a LiveKitBridge instance (which
* automatically registers the built-in track-control RPC handler).
*
* @param destination_identity Identity of the remote participant.
* @param track_name Name of the track to mute.
* @return true if the track was muted successfully.
*/
bool requestTrackMute(const std::string &destination_identity,
const std::string &track_name);

/**
* Request a remote participant to unmute a published track.
*
* The remote participant must be a LiveKitBridge instance (which
* automatically registers the built-in track-control RPC handler).
*
* @param destination_identity Identity of the remote participant.
* @param track_name Name of the track to unmute.
* @return true if the track was unmuted successfully.
*/
bool requestTrackUnmute(const std::string &destination_identity,
const std::string &track_name);

private:
friend class BridgeRoomDelegate;
friend class test::CallbackKeyTest;
Expand Down Expand Up @@ -314,6 +401,13 @@ class LiveKitBridge {
const std::shared_ptr<livekit::Track> &track,
VideoFrameCallback cb);

/// Execute a track action (mute/unmute/release) by track name.
/// Used as the TrackActionFn callback for RpcManager.
/// Throws livekit::RpcError if the track is not found.
/// @pre Caller does NOT hold mutex_ (acquires it internally).
void executeTrackAction(const std::string &action,
const std::string &track_name);

mutable std::mutex mutex_;
bool connected_;
bool connecting_; // guards against concurrent connect() calls
Expand All @@ -323,6 +417,7 @@ class LiveKitBridge {

std::unique_ptr<livekit::Room> room_;
std::unique_ptr<BridgeRoomDelegate> delegate_;
std::unique_ptr<RpcManager> rpc_manager_;

/// Registered callbacks (may be registered before tracks are subscribed).
std::unordered_map<CallbackKey, AudioFrameCallback, CallbackKeyHash>
Expand All @@ -341,7 +436,6 @@ class LiveKitBridge {
std::vector<std::shared_ptr<BridgeAudioTrack>> published_audio_tracks_;
/// @copydoc published_audio_tracks_
std::vector<std::shared_ptr<BridgeVideoTrack>> published_video_tracks_;

};

} // namespace livekit_bridge
62 changes: 62 additions & 0 deletions bridge/include/livekit_bridge/rpc_constants.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2026 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/// @file rpc_constants.h
/// @brief Constants for built-in bridge RPC methods.

#pragma once

#include <string>

namespace livekit_bridge {
namespace rpc {

/// Built-in RPC method name used by remote track control.
/// Allows remote participants to mute, unmute, or release tracks
/// published by this bridge. Must be called after connect().
/// Audio/video tracks support mute, unmute, and release. Data tracks
/// only support release (they have no mute/unmute); a mute request on
/// a data track is treated as release, and unmute returns an error.
namespace track_control {

/// RPC method name registered by the bridge for remote track control.
constexpr const char *kMethod = "lk.bridge.track-control";

/// Payload action strings.
constexpr const char *kActionMute = "mute";
constexpr const char *kActionUnmute = "unmute";

/// Delimiter between action and track name in the payload (e.g. "mute:cam").
constexpr char kDelimiter = ':';

/// Response payload returned on success.
constexpr const char *kResponseOk = "ok";

/// Build a track-control RPC payload: "<action>:<track_name>".
inline std::string formatPayload(const char *action,
const std::string &track_name) {
std::string payload;
payload.reserve(std::char_traits<char>::length(action) + 1 +
track_name.size());
payload += action;
payload += kDelimiter;
payload += track_name;
return payload;
}

} // namespace track_control
} // namespace rpc
} // namespace livekit_bridge
Loading
Loading