diff --git a/.gitignore b/.gitignore index 8a37fba..447173b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ received_green.avif docs/*.bak docs/html/ docs/latex/ +web/ .vs/ .vscode/ .cursor/ @@ -29,3 +30,5 @@ lib/ *.dll *.exe livekit.log + +examples/realsense-livekit/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 39d06fa..9efd17d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") option(LIVEKIT_BUILD_EXAMPLES "Build LiveKit examples" OFF) option(LIVEKIT_BUILD_TESTS "Build LiveKit tests" OFF) -option(LIVEKIT_BUILD_BRIDGE "Build LiveKit Bridge (simplified high-level API)" OFF) + # vcpkg is only used on Windows; Linux/macOS use system package managers if(WIN32) @@ -353,6 +353,14 @@ add_library(livekit SHARED src/remote_video_track.cpp src/video_utils.cpp src/video_utils.h + src/session_manager/session_manager.cpp + src/session_manager/managed_local_audio_track.cpp + src/session_manager/managed_local_video_track.cpp + src/session_manager/session_manager_room_delegate.cpp + src/session_manager/session_manager_room_delegate.h + src/session_manager/rpc_constants.cpp + src/session_manager/rpc_controller.cpp + src/session_manager/rpc_controller.h ) if(WIN32) set_target_properties(livekit PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) @@ -367,15 +375,17 @@ target_include_directories(livekit $ PRIVATE ${LIVEKIT_ROOT_DIR}/src + ${LIVEKIT_ROOT_DIR}/src/session_manager ${RUST_ROOT}/livekit-ffi/include ${PROTO_BINARY_DIR} ) target_link_libraries(livekit + PUBLIC + $ PRIVATE livekit_ffi ${LIVEKIT_PROTOBUF_TARGET} - spdlog::spdlog ) target_compile_definitions(livekit @@ -679,19 +689,13 @@ install(FILES # ------------------------------------------------------------------------ -# Build the LiveKit C++ bridge before examples (human_robot depends on it) -add_subdirectory(bridge) - -# ---- Examples ---- -# add_subdirectory(examples) - - if(LIVEKIT_BUILD_EXAMPLES) add_subdirectory(examples) endif() if(LIVEKIT_BUILD_TESTS) add_subdirectory(src/tests) + add_subdirectory(src/tests/session_manager) endif() add_custom_target(clean_generated diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt deleted file mode 100644 index 25d88aa..0000000 --- a/bridge/CMakeLists.txt +++ /dev/null @@ -1,61 +0,0 @@ -cmake_minimum_required(VERSION 3.20) - -project(livekit_bridge LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -add_library(livekit_bridge SHARED - src/livekit_bridge.cpp - src/bridge_audio_track.cpp - src/bridge_video_track.cpp - src/bridge_room_delegate.cpp - src/bridge_room_delegate.h - src/rpc_controller.cpp - src/rpc_controller.h -) - -if(WIN32) - set_target_properties(livekit_bridge PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) -endif() - -target_include_directories(livekit_bridge - PUBLIC - $ - $ - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src - ${LIVEKIT_ROOT_DIR}/src -) - -# Link against the main livekit SDK library (which transitively provides -# include paths for livekit/*.h and links livekit_ffi). -target_link_libraries(livekit_bridge - PUBLIC - livekit - PRIVATE - spdlog::spdlog -) - -target_compile_definitions(livekit_bridge - PRIVATE - SPDLOG_ACTIVE_LEVEL=${_SPDLOG_ACTIVE_LEVEL} -) - -if(MSVC) - target_compile_options(livekit_bridge PRIVATE /permissive- /Zc:__cplusplus /W4) -else() - target_compile_options(livekit_bridge PRIVATE -Wall -Wextra -Wpedantic) -endif() - -# --- Tests --- -# Bridge tests default to OFF. They are automatically enabled when the parent -# SDK tests are enabled (LIVEKIT_BUILD_TESTS=ON), e.g. via ./build.sh debug-tests. -option(LIVEKIT_BRIDGE_BUILD_TESTS "Build bridge unit tests" OFF) - -if(LIVEKIT_BRIDGE_BUILD_TESTS OR LIVEKIT_BUILD_TESTS) - add_subdirectory(tests) -endif() - -# Bridge examples (robot + human) are built from examples/CMakeLists.txt -# when LIVEKIT_BUILD_EXAMPLES is ON; see examples/bridge_human_robot/. diff --git a/bridge/tests/test_livekit_bridge.cpp b/bridge/tests/test_livekit_bridge.cpp deleted file mode 100644 index 38b1d7d..0000000 --- a/bridge/tests/test_livekit_bridge.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2025 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 test_livekit_bridge.cpp -/// @brief Unit tests for LiveKitBridge. - -#include -#include - -#include - -#include - -namespace livekit_bridge { -namespace test { - -class LiveKitBridgeTest : public ::testing::Test { -protected: - // No SetUp/TearDown needed -- we test the bridge without initializing - // the LiveKit SDK, since we only exercise pre-connection behaviour. -}; - -// ============================================================================ -// Initial state -// ============================================================================ - -TEST_F(LiveKitBridgeTest, InitiallyNotConnected) { - LiveKitBridge bridge; - - EXPECT_FALSE(bridge.isConnected()) - << "Bridge should not be connected immediately after construction"; -} - -TEST_F(LiveKitBridgeTest, DisconnectBeforeConnectIsNoOp) { - LiveKitBridge bridge; - - EXPECT_NO_THROW(bridge.disconnect()) - << "disconnect() on an unconnected bridge should be a safe no-op"; - - EXPECT_FALSE(bridge.isConnected()); -} - -TEST_F(LiveKitBridgeTest, MultipleDisconnectsAreIdempotent) { - LiveKitBridge bridge; - - EXPECT_NO_THROW({ - bridge.disconnect(); - bridge.disconnect(); - bridge.disconnect(); - }) << "Multiple disconnect() calls should be safe"; -} - -TEST_F(LiveKitBridgeTest, DestructorOnUnconnectedBridgeIsSafe) { - // Just verify no crash when the bridge is destroyed without connecting. - EXPECT_NO_THROW({ - LiveKitBridge bridge; - // bridge goes out of scope here - }); -} - -// ============================================================================ -// Track creation before connection -// ============================================================================ - -TEST_F(LiveKitBridgeTest, CreateAudioTrackBeforeConnectThrows) { - LiveKitBridge bridge; - - EXPECT_THROW(bridge.createAudioTrack("mic", 48000, 2, - livekit::TrackSource::SOURCE_MICROPHONE), - std::runtime_error) - << "createAudioTrack should throw when not connected"; -} - -TEST_F(LiveKitBridgeTest, CreateVideoTrackBeforeConnectThrows) { - LiveKitBridge bridge; - - EXPECT_THROW(bridge.createVideoTrack("cam", 1280, 720, - livekit::TrackSource::SOURCE_CAMERA), - std::runtime_error) - << "createVideoTrack should throw when not connected"; -} - -// ============================================================================ -// Callback registration (pre-connection, pure map operations) -// ============================================================================ - -TEST_F(LiveKitBridgeTest, RegisterAndUnregisterAudioCallbackDoesNotCrash) { - LiveKitBridge bridge; - - EXPECT_NO_THROW({ - bridge.setOnAudioFrameCallback("remote-participant", - livekit::TrackSource::SOURCE_MICROPHONE, - [](const livekit::AudioFrame &) {}); - - bridge.clearOnAudioFrameCallback("remote-participant", - livekit::TrackSource::SOURCE_MICROPHONE); - }) << "Registering and unregistering an audio callback should be safe " - "even without a connection"; -} - -TEST_F(LiveKitBridgeTest, RegisterAndUnregisterVideoCallbackDoesNotCrash) { - LiveKitBridge bridge; - - EXPECT_NO_THROW({ - bridge.setOnVideoFrameCallback( - "remote-participant", livekit::TrackSource::SOURCE_CAMERA, - [](const livekit::VideoFrame &, std::int64_t) {}); - - bridge.clearOnVideoFrameCallback("remote-participant", - livekit::TrackSource::SOURCE_CAMERA); - }) << "Registering and unregistering a video callback should be safe " - "even without a connection"; -} - -TEST_F(LiveKitBridgeTest, UnregisterNonExistentCallbackIsNoOp) { - LiveKitBridge bridge; - - EXPECT_NO_THROW({ - bridge.clearOnAudioFrameCallback("nonexistent", - livekit::TrackSource::SOURCE_MICROPHONE); - bridge.clearOnVideoFrameCallback("nonexistent", - livekit::TrackSource::SOURCE_CAMERA); - }) << "Unregistering a callback that was never registered should be a no-op"; -} - -TEST_F(LiveKitBridgeTest, MultipleRegistrationsSameKeyOverwrites) { - LiveKitBridge bridge; - - int call_count = 0; - - // Register a first callback - bridge.setOnAudioFrameCallback("alice", - livekit::TrackSource::SOURCE_MICROPHONE, - [](const livekit::AudioFrame &) {}); - - // Register a second callback for the same key -- should overwrite - bridge.setOnAudioFrameCallback( - "alice", livekit::TrackSource::SOURCE_MICROPHONE, - [&call_count](const livekit::AudioFrame &) { call_count++; }); - - // Unregister once should be enough (only one entry per key) - EXPECT_NO_THROW(bridge.clearOnAudioFrameCallback( - "alice", livekit::TrackSource::SOURCE_MICROPHONE)); -} - -TEST_F(LiveKitBridgeTest, RegisterCallbacksForMultipleParticipants) { - LiveKitBridge bridge; - - EXPECT_NO_THROW({ - bridge.setOnAudioFrameCallback("alice", - livekit::TrackSource::SOURCE_MICROPHONE, - [](const livekit::AudioFrame &) {}); - - bridge.setOnVideoFrameCallback( - "bob", livekit::TrackSource::SOURCE_CAMERA, - [](const livekit::VideoFrame &, std::int64_t) {}); - - bridge.setOnAudioFrameCallback( - "charlie", livekit::TrackSource::SOURCE_SCREENSHARE_AUDIO, - [](const livekit::AudioFrame &) {}); - }) << "Should be able to register callbacks for multiple participants"; - - // Cleanup - bridge.clearOnAudioFrameCallback("alice", - livekit::TrackSource::SOURCE_MICROPHONE); - bridge.clearOnVideoFrameCallback("bob", livekit::TrackSource::SOURCE_CAMERA); - bridge.clearOnAudioFrameCallback( - "charlie", livekit::TrackSource::SOURCE_SCREENSHARE_AUDIO); -} - -} // namespace test -} // namespace livekit_bridge diff --git a/build.sh b/build.sh index 278cea5..e3b02d6 100755 --- a/build.sh +++ b/build.sh @@ -37,6 +37,7 @@ Commands: release Configure + build Release version (build-release/) release-examples Configure + build Release version with examples release-tests Configure + build Release version with tests + build-all Configure + build all of the above (debug/release + examples + tests) clean Clean both Debug and Release build directories clean-all Full clean (build dirs + Rust targets) help Show this help message @@ -60,6 +61,7 @@ Examples: ./build.sh debug ./build.sh debug-tests ./build.sh release-tests + ./build.sh build-all ./build.sh clean ./build.sh clean-all EOF @@ -401,6 +403,26 @@ case "${cmd}" in fi fi ;; + build-all) + echo "==> Build-all: debug, debug-examples, debug-tests, release, release-examples, release-tests" + BUILD_TYPE="Debug" + BUILD_DIR="${PROJECT_ROOT}/build-debug" + PRESET="${OS_TYPE}-debug" + configure && build + PRESET="${OS_TYPE}-debug-examples" + configure && build + PRESET="${OS_TYPE}-debug-tests" + configure && build + BUILD_TYPE="Release" + BUILD_DIR="${PROJECT_ROOT}/build-release" + PRESET="${OS_TYPE}-release" + configure && build + PRESET="${OS_TYPE}-release-examples" + configure && build + PRESET="${OS_TYPE}-release-tests" + configure && build + echo "==> Build-all complete." + ;; clean) clean ;; diff --git a/cmake/LiveKitConfig.cmake.in b/cmake/LiveKitConfig.cmake.in index b5805e4..90b1a7e 100644 --- a/cmake/LiveKitConfig.cmake.in +++ b/cmake/LiveKitConfig.cmake.in @@ -1,4 +1,7 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(spdlog) + include("${CMAKE_CURRENT_LIST_DIR}/LiveKitTargets.cmake") diff --git a/docker/Dockerfile b/docker/Dockerfile index 0f42ff0..6848ab3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -104,7 +104,6 @@ WORKDIR /client-sdk-cpp RUN mkdir -p /client-sdk-cpp COPY src /client-sdk-cpp/src COPY include /client-sdk-cpp/include -COPY bridge /client-sdk-cpp/bridge COPY build.sh /client-sdk-cpp/build.sh COPY CMakePresets.json /client-sdk-cpp/CMakePresets.json COPY build.cmd /client-sdk-cpp/build.cmd diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2eb68be..3f4c7c3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -45,36 +45,30 @@ set(EXAMPLES_ALL SimpleDataStream LoggingLevelsBasicUsage LoggingLevelsCustomSinks - BridgeRobot - BridgeHuman - BridgeMuteCaller - BridgeMuteReceiver - BridgeRpcCaller - BridgeRpcReceiver -) - -# Bridge examples (need livekit_bridge DLL/shared lib in addition to livekit_ffi) -set(EXAMPLES_BRIDGE - BridgeRobot - BridgeHuman - BridgeMuteCaller - BridgeMuteReceiver - BridgeRpcCaller - BridgeRpcReceiver + HumanRobotRobot + HumanRobotHuman + MuteUnmuteCaller + MuteUnmuteReceiver + RpcCaller + RpcReceiver + SessionManagerBaseSdkProducer + SessionManagerBaseSdkViewer ) # Examples that use SDL3 (need SDL3 lib copied on Linux; SimpleRoom is handled separately above) set(EXAMPLES_NEED_SDL3 - BridgeRobot - BridgeHuman - BridgeMuteCaller - BridgeMuteReceiver + HumanRobotRobot + HumanRobotHuman + MuteUnmuteCaller + MuteUnmuteReceiver + SessionManagerBaseSdkProducer + SessionManagerBaseSdkViewer ) add_executable(SimpleRoom - simple_room/main.cpp - simple_room/fallback_capture.cpp - simple_room/fallback_capture.h + base_sdk/simple_room/main.cpp + ${EXAMPLES_COMMON_DIR}/fallback_capture.cpp + ${EXAMPLES_COMMON_DIR}/fallback_capture.h ${EXAMPLES_COMMON_DIR}/sdl_media.cpp ${EXAMPLES_COMMON_DIR}/sdl_media.h ${EXAMPLES_COMMON_DIR}/sdl_media_manager.cpp @@ -88,13 +82,12 @@ add_executable(SimpleRoom target_include_directories(SimpleRoom PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS} ${EXAMPLES_COMMON_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/simple_room + ${CMAKE_CURRENT_SOURCE_DIR}/base_sdk/simple_room ) target_link_libraries(SimpleRoom PRIVATE livekit - spdlog::spdlog SDL3::SDL3 ) @@ -140,7 +133,7 @@ if(NOT nlohmann_json_FOUND) endif() add_executable(SimpleRpc - simple_rpc/main.cpp + base_sdk/simple_rpc/main.cpp ) target_include_directories(SimpleRpc PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS}) @@ -149,20 +142,19 @@ target_link_libraries(SimpleRpc PRIVATE nlohmann_json::nlohmann_json livekit - spdlog::spdlog ) # --- SimpleJoystick example (sender + receiver executables with shared json_utils) --- add_library(simple_joystick_json_utils STATIC - simple_joystick/json_utils.cpp - simple_joystick/json_utils.h - simple_joystick/utils.cpp - simple_joystick/utils.h + base_sdk/simple_joystick/json_utils.cpp + base_sdk/simple_joystick/json_utils.h + base_sdk/simple_joystick/utils.cpp + base_sdk/simple_joystick/utils.h ) target_include_directories(simple_joystick_json_utils PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/simple_joystick + ${CMAKE_CURRENT_SOURCE_DIR}/base_sdk/simple_joystick ) target_link_libraries(simple_joystick_json_utils @@ -171,7 +163,7 @@ target_link_libraries(simple_joystick_json_utils ) add_executable(SimpleJoystickReceiver - simple_joystick/receiver.cpp + base_sdk/simple_joystick/receiver.cpp ) target_include_directories(SimpleJoystickReceiver PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS}) @@ -180,11 +172,10 @@ target_link_libraries(SimpleJoystickReceiver PRIVATE simple_joystick_json_utils livekit - spdlog::spdlog ) add_executable(SimpleJoystickSender - simple_joystick/sender.cpp + base_sdk/simple_joystick/sender.cpp ) target_include_directories(SimpleJoystickSender PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS}) @@ -193,13 +184,12 @@ target_link_libraries(SimpleJoystickSender PRIVATE simple_joystick_json_utils livekit - spdlog::spdlog ) # --- LoggingLevelsBasicUsage example --- add_executable(LoggingLevelsBasicUsage - logging_levels/basic_usage.cpp + base_sdk/logging_levels/basic_usage.cpp ) target_include_directories(LoggingLevelsBasicUsage PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS}) @@ -207,13 +197,12 @@ target_include_directories(LoggingLevelsBasicUsage PRIVATE ${EXAMPLES_PRIVATE_IN target_link_libraries(LoggingLevelsBasicUsage PRIVATE livekit - spdlog::spdlog ) # --- LoggingLevelsCustomSinks example --- add_executable(LoggingLevelsCustomSinks - logging_levels/custom_sinks.cpp + base_sdk/logging_levels/custom_sinks.cpp ) target_link_libraries(LoggingLevelsCustomSinks @@ -224,7 +213,7 @@ target_link_libraries(LoggingLevelsCustomSinks # --- SimpleDataStream example --- add_executable(SimpleDataStream - simple_data_stream/main.cpp + base_sdk/simple_data_stream/main.cpp ) target_include_directories(SimpleDataStream PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS}) @@ -232,7 +221,6 @@ target_include_directories(SimpleDataStream PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_D target_link_libraries(SimpleDataStream PRIVATE livekit - spdlog::spdlog ) add_custom_command( @@ -243,67 +231,107 @@ add_custom_command( $/data ) -# --- bridge_human_robot examples (robot + human; use livekit_bridge and SDL3) --- +# --- human_robot examples (robot + human; use session_manager and SDL3) --- -add_executable(BridgeRobot - bridge_human_robot/robot.cpp +add_executable(HumanRobotRobot + human_robot/robot.cpp ${EXAMPLES_COMMON_DIR}/sdl_media.cpp ${EXAMPLES_COMMON_DIR}/sdl_media.h ) -target_include_directories(BridgeRobot PRIVATE +target_include_directories(HumanRobotRobot PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS} ${EXAMPLES_COMMON_DIR} ) -target_link_libraries(BridgeRobot PRIVATE livekit_bridge spdlog::spdlog SDL3::SDL3) +target_link_libraries(HumanRobotRobot PRIVATE livekit SDL3::SDL3) -add_executable(BridgeHuman - bridge_human_robot/human.cpp +add_executable(HumanRobotHuman + human_robot/human.cpp ${EXAMPLES_COMMON_DIR}/sdl_media.cpp ${EXAMPLES_COMMON_DIR}/sdl_media.h ) -target_include_directories(BridgeHuman PRIVATE +target_include_directories(HumanRobotHuman PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS} ${EXAMPLES_COMMON_DIR} ) -target_link_libraries(BridgeHuman PRIVATE livekit_bridge spdlog::spdlog SDL3::SDL3) +target_link_libraries(HumanRobotHuman PRIVATE livekit SDL3::SDL3) -# --- bridge_rpc examples (headless custom RPC caller + receiver) --- +# --- rpc examples (headless custom RPC caller + receiver) --- -add_executable(BridgeRpcCaller - bridge_rpc/custom_caller.cpp +add_executable(RpcCaller + rpc/custom_caller.cpp ) -target_link_libraries(BridgeRpcCaller PRIVATE livekit_bridge) +target_include_directories(RpcCaller PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS}) +target_link_libraries(RpcCaller PRIVATE livekit) -add_executable(BridgeRpcReceiver - bridge_rpc/custom_receiver.cpp +add_executable(RpcReceiver + rpc/custom_receiver.cpp ) -target_link_libraries(BridgeRpcReceiver PRIVATE livekit_bridge) +target_include_directories(RpcReceiver PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS}) +target_link_libraries(RpcReceiver PRIVATE livekit) + +# --- mute_unmute examples (caller uses SDL3 for A/V playback; receiver is headless) --- -# --- bridge_mute_unmute examples (caller uses SDL3 for A/V playback; receiver is headless) --- +add_executable(MuteUnmuteCaller + mute_unmute/caller.cpp + ${EXAMPLES_COMMON_DIR}/sdl_media.cpp + ${EXAMPLES_COMMON_DIR}/sdl_media.h +) +target_include_directories(MuteUnmuteCaller PRIVATE + ${EXAMPLES_PRIVATE_INCLUDE_DIRS} + ${EXAMPLES_COMMON_DIR} +) +target_link_libraries(MuteUnmuteCaller PRIVATE livekit SDL3::SDL3) -add_executable(BridgeMuteCaller - bridge_mute_unmute/caller.cpp +add_executable(MuteUnmuteReceiver + mute_unmute/receiver.cpp ${EXAMPLES_COMMON_DIR}/sdl_media.cpp ${EXAMPLES_COMMON_DIR}/sdl_media.h ) -target_include_directories(BridgeMuteCaller PRIVATE +target_include_directories(MuteUnmuteReceiver PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS} ${EXAMPLES_COMMON_DIR} ) -target_link_libraries(BridgeMuteCaller PRIVATE livekit_bridge spdlog::spdlog SDL3::SDL3) +target_link_libraries(MuteUnmuteReceiver PRIVATE livekit SDL3::SDL3) -add_executable(BridgeMuteReceiver - bridge_mute_unmute/receiver.cpp +# --- session_manager_base_sdk (SessionManager connect + getRoom(); Room for all else) --- +add_executable(SessionManagerBaseSdkProducer + session_manager_base_sdk/video_producer.cpp ${EXAMPLES_COMMON_DIR}/sdl_media.cpp ${EXAMPLES_COMMON_DIR}/sdl_media.h ) -target_include_directories(BridgeMuteReceiver PRIVATE +target_include_directories(SessionManagerBaseSdkProducer PRIVATE ${EXAMPLES_PRIVATE_INCLUDE_DIRS} ${EXAMPLES_COMMON_DIR} ) -target_link_libraries(BridgeMuteReceiver PRIVATE livekit_bridge spdlog::spdlog SDL3::SDL3) +target_link_libraries(SessionManagerBaseSdkProducer PRIVATE livekit SDL3::SDL3) -# Copy SDL3 shared library to bridge example output directories +add_executable(SessionManagerBaseSdkViewer + session_manager_base_sdk/video_viewer.cpp + ${EXAMPLES_COMMON_DIR}/fallback_capture.cpp + ${EXAMPLES_COMMON_DIR}/fallback_capture.h + ${EXAMPLES_COMMON_DIR}/sdl_media.cpp + ${EXAMPLES_COMMON_DIR}/sdl_media.h + ${EXAMPLES_COMMON_DIR}/sdl_media_manager.cpp + ${EXAMPLES_COMMON_DIR}/sdl_media_manager.h + ${EXAMPLES_COMMON_DIR}/sdl_video_renderer.cpp + ${EXAMPLES_COMMON_DIR}/sdl_video_renderer.h + ${EXAMPLES_COMMON_DIR}/wav_audio_source.cpp + ${EXAMPLES_COMMON_DIR}/wav_audio_source.h +) +target_include_directories(SessionManagerBaseSdkViewer PRIVATE + ${EXAMPLES_PRIVATE_INCLUDE_DIRS} + ${EXAMPLES_COMMON_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/base_sdk/simple_room +) +target_link_libraries(SessionManagerBaseSdkViewer PRIVATE livekit SDL3::SDL3) + +add_custom_command(TARGET SessionManagerBaseSdkViewer POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${LIVEKIT_ROOT_DIR}/data + $/data +) + +# Copy SDL3 shared library to examples output directories if(UNIX AND NOT APPLE) foreach(_target ${EXAMPLES_NEED_SDL3}) add_custom_command(TARGET ${_target} POST_BUILD @@ -346,19 +374,6 @@ if(WIN32) add_dependencies(${EXAMPLE} copy_ffi_dll_to_bin) endforeach() - # Bridge examples also need livekit_bridge.dll (single copy to bin/) - set(BRIDGE_DLL "livekit_bridge.dll") - add_custom_command(OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${BRIDGE_DLL} - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${BRIDGE_DLL} - DEPENDS livekit_bridge - COMMENT "Copying livekit_bridge DLL to examples output directory" - ) - add_custom_target(copy_bridge_dll_to_bin ALL DEPENDS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${BRIDGE_DLL}) - foreach(EXAMPLE ${EXAMPLES_BRIDGE}) - add_dependencies(${EXAMPLE} copy_bridge_dll_to_bin) - endforeach() endif() # Linux/macOS: Copy shared library to examples output directory (single copy to avoid parallel POST_BUILD races) @@ -382,21 +397,4 @@ if(UNIX) add_dependencies(${EXAMPLE} copy_ffi_to_bin) endforeach() - # Bridge examples also need livekit_bridge shared library (single copy to bin/) - if(APPLE) - set(BRIDGE_SHARED_LIB "liblivekit_bridge.dylib") - else() - set(BRIDGE_SHARED_LIB "liblivekit_bridge.so") - endif() - add_custom_command(OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${BRIDGE_SHARED_LIB} - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${BRIDGE_SHARED_LIB} - DEPENDS livekit_bridge - COMMENT "Copying livekit_bridge to examples output directory" - ) - add_custom_target(copy_bridge_to_bin ALL DEPENDS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${BRIDGE_SHARED_LIB}) - foreach(EXAMPLE ${EXAMPLES_BRIDGE}) - add_dependencies(${EXAMPLE} copy_bridge_to_bin) - endforeach() endif() \ No newline at end of file diff --git a/examples/logging_levels/README.md b/examples/base_sdk/logging_levels/README.md similarity index 100% rename from examples/logging_levels/README.md rename to examples/base_sdk/logging_levels/README.md diff --git a/examples/logging_levels/basic_usage.cpp b/examples/base_sdk/logging_levels/basic_usage.cpp similarity index 99% rename from examples/logging_levels/basic_usage.cpp rename to examples/base_sdk/logging_levels/basic_usage.cpp index 657f6c3..e77a5c5 100644 --- a/examples/logging_levels/basic_usage.cpp +++ b/examples/base_sdk/logging_levels/basic_usage.cpp @@ -35,7 +35,6 @@ /// see which messages are filtered at each setting. #include "livekit/livekit.h" -#include "lk_log.h" #include #include diff --git a/examples/logging_levels/custom_sinks.cpp b/examples/base_sdk/logging_levels/custom_sinks.cpp similarity index 98% rename from examples/logging_levels/custom_sinks.cpp rename to examples/base_sdk/logging_levels/custom_sinks.cpp index 40ddcb4..f1bc46e 100644 --- a/examples/logging_levels/custom_sinks.cpp +++ b/examples/base_sdk/logging_levels/custom_sinks.cpp @@ -225,7 +225,7 @@ void runJsonSinkDemo() { void runRos2SinkDemo() { std::cout << "\n=== ROS2 bridge sink (stubbed) ===\n\n"; - const std::string node_name = "livekit_bridge_node"; + const std::string node_name = "session_manager_node"; livekit::LogCallback ros2Sink = [&node_name](livekit::LogLevel level, const std::string &logger_name, @@ -251,7 +251,7 @@ void runRos2SinkDemo() { break; } - // Mimic: [INFO] [1719500000.123] [livekit_bridge_node]: [livekit] msg + // Mimic: [INFO] [1719500000.123] [session_manager_node]: [livekit] msg auto epoch_s = std::chrono::duration( std::chrono::system_clock::now().time_since_epoch()) .count(); diff --git a/examples/simple_data_stream/main.cpp b/examples/base_sdk/simple_data_stream/main.cpp similarity index 100% rename from examples/simple_data_stream/main.cpp rename to examples/base_sdk/simple_data_stream/main.cpp diff --git a/examples/simple_joystick/json_utils.cpp b/examples/base_sdk/simple_joystick/json_utils.cpp similarity index 100% rename from examples/simple_joystick/json_utils.cpp rename to examples/base_sdk/simple_joystick/json_utils.cpp diff --git a/examples/simple_joystick/json_utils.h b/examples/base_sdk/simple_joystick/json_utils.h similarity index 100% rename from examples/simple_joystick/json_utils.h rename to examples/base_sdk/simple_joystick/json_utils.h diff --git a/examples/simple_joystick/receiver.cpp b/examples/base_sdk/simple_joystick/receiver.cpp similarity index 100% rename from examples/simple_joystick/receiver.cpp rename to examples/base_sdk/simple_joystick/receiver.cpp diff --git a/examples/simple_joystick/sender.cpp b/examples/base_sdk/simple_joystick/sender.cpp similarity index 100% rename from examples/simple_joystick/sender.cpp rename to examples/base_sdk/simple_joystick/sender.cpp diff --git a/examples/simple_joystick/utils.cpp b/examples/base_sdk/simple_joystick/utils.cpp similarity index 100% rename from examples/simple_joystick/utils.cpp rename to examples/base_sdk/simple_joystick/utils.cpp diff --git a/examples/simple_joystick/utils.h b/examples/base_sdk/simple_joystick/utils.h similarity index 100% rename from examples/simple_joystick/utils.h rename to examples/base_sdk/simple_joystick/utils.h diff --git a/examples/simple_room/main.cpp b/examples/base_sdk/simple_room/main.cpp similarity index 99% rename from examples/simple_room/main.cpp rename to examples/base_sdk/simple_room/main.cpp index 9076185..4b2e2e1 100644 --- a/examples/simple_room/main.cpp +++ b/examples/base_sdk/simple_room/main.cpp @@ -27,7 +27,6 @@ #include #include "livekit/livekit.h" -#include "lk_log.h" #include "sdl_media_manager.h" #include "wav_audio_source.h" diff --git a/examples/simple_rpc/README.md b/examples/base_sdk/simple_rpc/README.md similarity index 100% rename from examples/simple_rpc/README.md rename to examples/base_sdk/simple_rpc/README.md diff --git a/examples/simple_rpc/main.cpp b/examples/base_sdk/simple_rpc/main.cpp similarity index 100% rename from examples/simple_rpc/main.cpp rename to examples/base_sdk/simple_rpc/main.cpp diff --git a/examples/simple_room/fallback_capture.cpp b/examples/common/fallback_capture.cpp similarity index 99% rename from examples/simple_room/fallback_capture.cpp rename to examples/common/fallback_capture.cpp index 29508e3..2626988 100644 --- a/examples/simple_room/fallback_capture.cpp +++ b/examples/common/fallback_capture.cpp @@ -24,7 +24,6 @@ #include #include "livekit/livekit.h" -#include "lk_log.h" #include "wav_audio_source.h" using namespace livekit; diff --git a/examples/simple_room/fallback_capture.h b/examples/common/fallback_capture.h similarity index 100% rename from examples/simple_room/fallback_capture.h rename to examples/common/fallback_capture.h diff --git a/examples/common/sdl_media.cpp b/examples/common/sdl_media.cpp index 6975aca..d4a44e6 100644 --- a/examples/common/sdl_media.cpp +++ b/examples/common/sdl_media.cpp @@ -16,7 +16,7 @@ #include "sdl_media.h" -#include "lk_log.h" +#include "livekit/lk_log.h" // ---------------------- SDLMicSource ----------------------------- diff --git a/examples/common/sdl_media_manager.cpp b/examples/common/sdl_media_manager.cpp index 93dc0d4..fcb4b53 100644 --- a/examples/common/sdl_media_manager.cpp +++ b/examples/common/sdl_media_manager.cpp @@ -18,7 +18,6 @@ #include "fallback_capture.h" #include "livekit/livekit.h" -#include "lk_log.h" #include "sdl_media.h" #include "sdl_video_renderer.h" #include diff --git a/examples/common/sdl_video_renderer.cpp b/examples/common/sdl_video_renderer.cpp index b820f53..fb7e4d6 100644 --- a/examples/common/sdl_video_renderer.cpp +++ b/examples/common/sdl_video_renderer.cpp @@ -17,7 +17,6 @@ #include "sdl_video_renderer.h" #include "livekit/livekit.h" -#include "lk_log.h" #include using namespace livekit; diff --git a/examples/bridge_human_robot/human.cpp b/examples/human_robot/human.cpp similarity index 97% rename from examples/bridge_human_robot/human.cpp rename to examples/human_robot/human.cpp index 434deca..3aa4d41 100644 --- a/examples/bridge_human_robot/human.cpp +++ b/examples/human_robot/human.cpp @@ -44,10 +44,9 @@ */ #include "livekit/audio_frame.h" +#include "livekit/session_manager/session_manager.h" #include "livekit/track.h" #include "livekit/video_frame.h" -#include "livekit_bridge/livekit_bridge.h" -#include "lk_log.h" #include "sdl_media.h" #include @@ -84,7 +83,7 @@ struct LatestVideoFrame { static LatestVideoFrame g_latest_video; /// Store a video frame for the main loop to render. -/// Called from bridge callbacks when their source is the active selection. +/// Called from session manager callbacks when their source is selected. static void renderFrame(const livekit::VideoFrame &frame) { const std::uint8_t *src = frame.data(); const std::size_t size = frame.dataSize(); @@ -192,11 +191,11 @@ int main(int argc, char *argv[]) { std::mutex speaker_mutex; // ----- Connect to LiveKit ----- - livekit_bridge::LiveKitBridge bridge; + livekit::SessionManager sm; std::cout << "[human] Connecting to " << url << " ...\n"; livekit::RoomOptions options; options.auto_subscribe = true; - if (!bridge.connect(url, token, options)) { + if (!sm.connect(url, token, options)) { LK_LOG_ERROR("[human] Failed to connect."); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); @@ -231,7 +230,7 @@ int main(int argc, char *argv[]) { // ----- set audio callbacks ----- // Real mic (SOURCE_MICROPHONE) -- plays only when 'w' is selected - bridge.setOnAudioFrameCallback( + sm.setOnAudioFrameCallback( "robot", livekit::TrackSource::SOURCE_MICROPHONE, [playAudio, no_audio](const livekit::AudioFrame &frame) { g_audio_frames.fetch_add(1, std::memory_order_relaxed); @@ -243,7 +242,7 @@ int main(int argc, char *argv[]) { // Sim audio / siren (SOURCE_SCREENSHARE_AUDIO) -- plays only when 's' is // selected - bridge.setOnAudioFrameCallback( + sm.setOnAudioFrameCallback( "robot", livekit::TrackSource::SOURCE_SCREENSHARE_AUDIO, [playAudio, no_audio](const livekit::AudioFrame &frame) { g_audio_frames.fetch_add(1, std::memory_order_relaxed); @@ -255,7 +254,7 @@ int main(int argc, char *argv[]) { // ----- set video callbacks ----- // Webcam feed (SOURCE_CAMERA) -- renders only when 'w' is selected - bridge.setOnVideoFrameCallback( + sm.setOnVideoFrameCallback( "robot", livekit::TrackSource::SOURCE_CAMERA, [](const livekit::VideoFrame &frame, std::int64_t /*timestamp_us*/) { g_video_frames.fetch_add(1, std::memory_order_relaxed); @@ -266,7 +265,7 @@ int main(int argc, char *argv[]) { }); // Sim frame feed (SOURCE_SCREENSHARE) -- renders only when 's' is selected - bridge.setOnVideoFrameCallback( + sm.setOnVideoFrameCallback( "robot", livekit::TrackSource::SOURCE_SCREENSHARE, [](const livekit::VideoFrame &frame, std::int64_t /*timestamp_us*/) { g_video_frames.fetch_add(1, std::memory_order_relaxed); @@ -404,7 +403,7 @@ int main(int argc, char *argv[]) { if (input_thread.joinable()) input_thread.detach(); - bridge.disconnect(); + sm.disconnect(); { std::lock_guard lock(speaker_mutex); diff --git a/examples/bridge_human_robot/robot.cpp b/examples/human_robot/robot.cpp similarity index 96% rename from examples/bridge_human_robot/robot.cpp rename to examples/human_robot/robot.cpp index 18d07b9..9b42219 100644 --- a/examples/bridge_human_robot/robot.cpp +++ b/examples/human_robot/robot.cpp @@ -34,10 +34,9 @@ */ #include "livekit/audio_frame.h" +#include "livekit/session_manager/session_manager.h" #include "livekit/track.h" #include "livekit/video_frame.h" -#include "livekit_bridge/livekit_bridge.h" -#include "lk_log.h" #include "sdl_media.h" #include @@ -357,11 +356,11 @@ int main(int argc, char *argv[]) { } // ----- Connect to LiveKit ----- - livekit_bridge::LiveKitBridge bridge; + livekit::SessionManager sm; LK_LOG_INFO("[robot] Connecting to {} ...", url); livekit::RoomOptions options; options.auto_subscribe = true; - if (!bridge.connect(url, token, options)) { + if (!sm.connect(url, token, options)) { LK_LOG_ERROR("[robot] Failed to connect."); SDL_Quit(); return 1; @@ -377,19 +376,18 @@ int main(int argc, char *argv[]) { constexpr int kSimWidth = 480; constexpr int kSimHeight = 320; - std::shared_ptr mic; + std::shared_ptr mic; if (use_mic) { - mic = bridge.createAudioTrack("robot-mic", kSampleRate, kChannels, - livekit::TrackSource::SOURCE_MICROPHONE); + mic = sm.createAudioTrack("robot-mic", kSampleRate, kChannels, + livekit::TrackSource::SOURCE_MICROPHONE); } auto sim_audio = - bridge.createAudioTrack("robot-sim-audio", kSampleRate, kChannels, - livekit::TrackSource::SOURCE_SCREENSHARE_AUDIO); - auto cam = bridge.createVideoTrack("robot-cam", kWidth, kHeight, - livekit::TrackSource::SOURCE_CAMERA); - auto sim_cam = - bridge.createVideoTrack("robot-sim-frame", kSimWidth, kSimHeight, - livekit::TrackSource::SOURCE_SCREENSHARE); + sm.createAudioTrack("robot-sim-audio", kSampleRate, kChannels, + livekit::TrackSource::SOURCE_SCREENSHARE_AUDIO); + auto cam = sm.createVideoTrack("robot-cam", kWidth, kHeight, + livekit::TrackSource::SOURCE_CAMERA); + auto sim_cam = sm.createVideoTrack("robot-sim-frame", kSimWidth, kSimHeight, + livekit::TrackSource::SOURCE_SCREENSHARE); LK_LOG_INFO("[robot] Publishing {} sim audio ({} Hz, {} ch), cam + sim frame " "({}x{} / {}x{}).", use_mic ? "mic + " : "(no mic) ", kSampleRate, kChannels, kWidth, @@ -654,7 +652,7 @@ int main(int argc, char *argv[]) { sim_audio.reset(); cam.reset(); sim_cam.reset(); - bridge.disconnect(); + sm.disconnect(); SDL_Quit(); LK_LOG_INFO("[robot] Done."); diff --git a/examples/bridge_mute_unmute/README.md b/examples/mute_unmute/README.md similarity index 84% rename from examples/bridge_mute_unmute/README.md rename to examples/mute_unmute/README.md index 6dcd544..94bec9c 100644 --- a/examples/bridge_mute_unmute/README.md +++ b/examples/mute_unmute/README.md @@ -1,6 +1,6 @@ -# Bridge Mute/Unmute Example +# Mute/Unmute Example -Demonstrates remote track control using the `LiveKitBridge` built-in +Demonstrates remote track control using the `SessionManager` built-in track-control RPC. A **receiver** publishes audio and video tracks, and a **caller** subscribes to them and toggles mute/unmute every few seconds. @@ -8,8 +8,8 @@ track-control RPC. A **receiver** publishes audio and video tracks, and a | Executable | Role | |-----------------------|------| -| **BridgeMuteReceiver** | Publishes an audio track (`"mic"`) and a video track (`"cam"`) using SDL3 hardware capture when available, falling back to silence and solid-color frames otherwise. The bridge automatically registers a built-in `lk.bridge.track-control` RPC handler on connect. | -| **BridgeMuteCaller** | Subscribes to the receiver's mic and cam tracks, renders them via SDL3 (speaker + window), and periodically calls `requestRemoteTrackMute` / `requestRemoteTrackUnmute` to toggle both tracks. | +| **MuteUnmuteReceiver** | Publishes an audio track (`"mic"`) and a video track (`"cam"`) using SDL3 hardware capture when available, falling back to silence and solid-color frames otherwise. The SessionManager automatically registers a built-in `lk.session_manager.track-control` RPC handler on connect. | +| **MuteUnmuteCaller** | Subscribes to the receiver's mic and cam tracks, renders them via SDL3 (speaker + window), and periodically calls `requestRemoteTrackMute` / `requestRemoteTrackUnmute` to toggle both tracks. | When the caller mutes a track, the receiver's `LocalAudioTrack::mute()` or `LocalVideoTrack::mute()` is invoked via RPC, which signals the LiveKit @@ -30,17 +30,17 @@ Start the receiver first, then the caller: ```bash # Terminal 1 -LIVEKIT_URL=wss://... LIVEKIT_TOKEN= ./build-release/bin/BridgeMuteReceiver +LIVEKIT_URL=wss://... LIVEKIT_TOKEN= ./build-release/bin/MuteUnmuteReceiver # Terminal 2 -LIVEKIT_URL=wss://... LIVEKIT_TOKEN= ./build-release/bin/BridgeMuteCaller +LIVEKIT_URL=wss://... LIVEKIT_TOKEN= ./build-release/bin/MuteUnmuteCaller ``` The caller also accepts an optional third argument for the receiver's identity (defaults to `"receiver"`): ```bash -./build-release/bin/BridgeMuteCaller wss://... my-receiver +./build-release/bin/MuteUnmuteCaller wss://... my-receiver ``` ## Sample output @@ -48,7 +48,7 @@ identity (defaults to `"receiver"`): ### Receiver ``` -./build-release/bin/BridgeMuteReceiver +./build-release/bin/MuteUnmuteReceiver [receiver] Connecting to wss://sderosasandbox-15g80zq7.livekit.cloud ... [receiver] Connected. cs.state() is 1 connection_state_ is 1 @@ -66,7 +66,7 @@ cs.state() is 1 connection_state_ is 1 ### Caller ``` -./build-release/bin/BridgeMuteCaller +./build-release/bin/MuteUnmuteCaller [caller] Connecting to wss://sderosasandbox-15g80zq7.livekit.cloud ... cs.state() is 1 connection_state_ is 1 [caller] Connected. diff --git a/examples/bridge_mute_unmute/caller.cpp b/examples/mute_unmute/caller.cpp similarity index 92% rename from examples/bridge_mute_unmute/caller.cpp rename to examples/mute_unmute/caller.cpp index a47b2e1..6119b7f 100644 --- a/examples/bridge_mute_unmute/caller.cpp +++ b/examples/mute_unmute/caller.cpp @@ -15,7 +15,7 @@ */ /* - * Caller (controller) for the bridge mute/unmute example. + * Caller (controller) for the SessionManager mute/unmute example. * * Connects to the same room as the receiver, subscribes to the receiver's * "mic" and "cam" tracks, and renders them via SDL3 (speaker + window). @@ -23,8 +23,8 @@ * so you can see and hear the tracks go silent and come back. * * Usage: - * BridgeMuteCaller [receiver-identity] - * LIVEKIT_URL=... LIVEKIT_TOKEN=... BridgeMuteCaller [receiver-identity] + * MuteUnmuteCaller [receiver-identity] + * LIVEKIT_URL=... LIVEKIT_TOKEN=... MuteUnmuteCaller [receiver-identity] * * The token must grant a different identity (e.g. "caller"). Generate with: * lk token create --api-key --api-secret \ @@ -33,9 +33,9 @@ #include "livekit/audio_frame.h" #include "livekit/rpc_error.h" +#include "livekit/session_manager/session_manager.h" #include "livekit/track.h" #include "livekit/video_frame.h" -#include "livekit_bridge/livekit_bridge.h" #include "sdl_media.h" #include @@ -103,8 +103,8 @@ int main(int argc, char *argv[]) { } if (url.empty() || token.empty()) { std::cerr - << "Usage: BridgeMuteCaller [receiver-identity]\n" - << " or: LIVEKIT_URL=... LIVEKIT_TOKEN=... BridgeMuteCaller " + << "Usage: MuteUnmuteCaller [receiver-identity]\n" + << " or: LIVEKIT_URL=... LIVEKIT_TOKEN=... MuteUnmuteCaller " "[receiver-identity]\n" << "Default receiver-identity: \"receiver\"\n"; return 1; @@ -146,13 +146,13 @@ int main(int argc, char *argv[]) { std::mutex speaker_mutex; // ----- Connect to LiveKit ----- - livekit_bridge::LiveKitBridge bridge; + livekit::SessionManager sm; std::cout << "[caller] Connecting to " << url << " ...\n"; livekit::RoomOptions options; options.auto_subscribe = true; - if (!bridge.connect(url, token, options)) { + if (!sm.connect(url, token, options)) { std::cerr << "[caller] Failed to connect.\n"; SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); @@ -164,7 +164,7 @@ int main(int argc, char *argv[]) { << "\"\n"; // ----- Subscribe to receiver's audio ----- - bridge.setOnAudioFrameCallback( + sm.setOnAudioFrameCallback( receiver_identity, livekit::TrackSource::SOURCE_MICROPHONE, [&speaker, &speaker_mutex](const livekit::AudioFrame &frame) { const auto &samples = frame.data(); @@ -187,7 +187,7 @@ int main(int argc, char *argv[]) { }); // ----- Subscribe to receiver's video ----- - bridge.setOnVideoFrameCallback( + sm.setOnVideoFrameCallback( receiver_identity, livekit::TrackSource::SOURCE_CAMERA, [](const livekit::VideoFrame &frame, std::int64_t /*timestamp_us*/) { storeFrame(frame); @@ -214,10 +214,10 @@ int main(int argc, char *argv[]) { // Toggle audio track "mic" try { if (currently_muted) { - bridge.requestRemoteTrackUnmute(receiver_identity, "mic"); + sm.requestRemoteTrackUnmute(receiver_identity, "mic"); std::cout << "[caller] mic: unmuted OK\n"; } else { - bridge.requestRemoteTrackMute(receiver_identity, "mic"); + sm.requestRemoteTrackMute(receiver_identity, "mic"); std::cout << "[caller] mic: muted OK\n"; } } catch (const livekit::RpcError &e) { @@ -230,10 +230,10 @@ int main(int argc, char *argv[]) { // Toggle video track "cam" try { if (currently_muted) { - bridge.requestRemoteTrackUnmute(receiver_identity, "cam"); + sm.requestRemoteTrackUnmute(receiver_identity, "cam"); std::cout << "[caller] cam: unmuted OK\n"; } else { - bridge.requestRemoteTrackMute(receiver_identity, "cam"); + sm.requestRemoteTrackMute(receiver_identity, "cam"); std::cout << "[caller] cam: muted OK\n"; } } catch (const livekit::RpcError &e) { @@ -319,7 +319,7 @@ int main(int argc, char *argv[]) { if (toggle_thread.joinable()) toggle_thread.join(); - bridge.disconnect(); + sm.disconnect(); { std::lock_guard lock(speaker_mutex); diff --git a/examples/bridge_mute_unmute/receiver.cpp b/examples/mute_unmute/receiver.cpp similarity index 91% rename from examples/bridge_mute_unmute/receiver.cpp rename to examples/mute_unmute/receiver.cpp index 1abafbc..dce5874 100644 --- a/examples/bridge_mute_unmute/receiver.cpp +++ b/examples/mute_unmute/receiver.cpp @@ -15,7 +15,7 @@ */ /* - * Receiver (publisher) for the bridge mute/unmute example. + * Receiver (publisher) for the mute/unmute example. * * Publishes an audio track ("mic") and a video track ("cam"), then enables * remote track control so that a remote caller can mute/unmute them via RPC. @@ -25,16 +25,16 @@ * frames (video). * * Usage: - * BridgeMuteReceiver - * LIVEKIT_URL=... LIVEKIT_TOKEN=... BridgeMuteReceiver + * MuteUnmuteReceiver + * LIVEKIT_URL=... LIVEKIT_TOKEN=... MuteUnmuteReceiver * * The token must grant identity "receiver". Generate one with: * lk token create --api-key --api-secret \ * --join --room my-room --identity receiver --valid-for 24h */ +#include "livekit/session_manager/session_manager.h" #include "livekit/track.h" -#include "livekit_bridge/livekit_bridge.h" #include "sdl_media.h" #include @@ -69,8 +69,8 @@ int main(int argc, char *argv[]) { } if (url.empty() || token.empty()) { std::cerr - << "Usage: BridgeMuteReceiver \n" - << " or: LIVEKIT_URL=... LIVEKIT_TOKEN=... BridgeMuteReceiver\n"; + << "Usage: MuteUnmuteReceiver \n" + << " or: LIVEKIT_URL=... LIVEKIT_TOKEN=... MuteUnmuteReceiver\n"; return 1; } @@ -83,13 +83,13 @@ int main(int argc, char *argv[]) { } // ----- Connect to LiveKit ----- - livekit_bridge::LiveKitBridge bridge; + livekit::SessionManager sm; std::cout << "[receiver] Connecting to " << url << " ...\n"; livekit::RoomOptions options; options.auto_subscribe = true; - if (!bridge.connect(url, token, options)) { + if (!sm.connect(url, token, options)) { std::cerr << "[receiver] Failed to connect.\n"; SDL_Quit(); return 1; @@ -101,10 +101,10 @@ int main(int argc, char *argv[]) { constexpr int kWidth = 1280; constexpr int kHeight = 720; - auto mic = bridge.createAudioTrack("mic", kSampleRate, kChannels, - livekit::TrackSource::SOURCE_MICROPHONE); - auto cam = bridge.createVideoTrack("cam", kWidth, kHeight, - livekit::TrackSource::SOURCE_CAMERA); + auto mic = sm.createAudioTrack("mic", kSampleRate, kChannels, + livekit::TrackSource::SOURCE_MICROPHONE); + auto cam = sm.createVideoTrack("cam", kWidth, kHeight, + livekit::TrackSource::SOURCE_CAMERA); std::cout << "[receiver] Published audio track \"mic\" and video track " "\"cam\".\n"; @@ -258,7 +258,7 @@ int main(int argc, char *argv[]) { mic.reset(); cam.reset(); - bridge.disconnect(); + sm.disconnect(); SDL_Quit(); std::cout << "[receiver] Done.\n"; diff --git a/examples/bridge_rpc/README.md b/examples/rpc/README.md similarity index 95% rename from examples/bridge_rpc/README.md rename to examples/rpc/README.md index 8969619..8178404 100644 --- a/examples/bridge_rpc/README.md +++ b/examples/rpc/README.md @@ -1,9 +1,9 @@ -# Bridge RPC Example +# RPC Example A minimal example of custom user-registered RPC methods using the -`LiveKitBridge` high-level API. +`SessionManager` high-level API. -Two headless executables — **BridgeRpcReceiver** and **BridgeRpcCaller** — +Two headless executables — **RpcReceiver** and **RpcCaller** — connect to the same LiveKit room. The receiver registers a `"print"` RPC method that logs the caller's message and sleeps for a variable duration before responding. The caller sends a numbered message every ~1 second and @@ -37,10 +37,10 @@ Start the receiver first, then the caller: ```bash # Terminal 1 -LIVEKIT_URL=wss://... LIVEKIT_TOKEN= ./build-release/bin/BridgeRpcReceiver +LIVEKIT_URL=wss://... LIVEKIT_TOKEN= ./build-release/bin/RpcReceiver # Terminal 2 -LIVEKIT_URL=wss://... LIVEKIT_TOKEN= ./build-release/bin/BridgeRpcCaller +LIVEKIT_URL=wss://... LIVEKIT_TOKEN= ./build-release/bin/RpcCaller ``` ## Sample output diff --git a/examples/bridge_rpc/custom_caller.cpp b/examples/rpc/custom_caller.cpp similarity index 86% rename from examples/bridge_rpc/custom_caller.cpp rename to examples/rpc/custom_caller.cpp index 4ff5d35..22e2c16 100644 --- a/examples/bridge_rpc/custom_caller.cpp +++ b/examples/rpc/custom_caller.cpp @@ -15,7 +15,7 @@ */ /* - * Caller for the bridge_rpc example. + * Caller for the rpc example. * * Connects to a LiveKit room as "caller" and sends a string to the * receiver's custom "print" RPC method every second. The receiver @@ -23,15 +23,15 @@ * calls will take noticeably longer to return. * * Usage: - * BridgeRpcCaller - * LIVEKIT_URL=... LIVEKIT_TOKEN=... BridgeRpcCaller + * RpcCaller + * LIVEKIT_URL=... LIVEKIT_TOKEN=... RpcCaller * * Generate a token with: * lk token create --join --room --identity caller --valid-for 24h */ #include "livekit/rpc_error.h" -#include "livekit_bridge/livekit_bridge.h" +#include "livekit/session_manager/session_manager.h" #include #include @@ -58,18 +58,18 @@ int main(int argc, char *argv[]) { token = e; } if (url.empty() || token.empty()) { - std::cerr << "Usage: BridgeRpcCaller \n" - << " or: LIVEKIT_URL=... LIVEKIT_TOKEN=... BridgeRpcCaller\n"; + std::cerr << "Usage: RpcCaller \n" + << " or: LIVEKIT_URL=... LIVEKIT_TOKEN=... RpcCaller\n"; return 1; } std::signal(SIGINT, handleSignal); - livekit_bridge::LiveKitBridge bridge; + livekit::SessionManager sm; std::cout << "[caller] Connecting to " << url << " ...\n"; livekit::RoomOptions options; - if (!bridge.connect(url, token, options)) { + if (!sm.connect(url, token, options)) { std::cerr << "[caller] Failed to connect.\n"; return 1; } @@ -89,8 +89,7 @@ int main(int argc, char *argv[]) { auto t0 = std::chrono::steady_clock::now(); try { - auto response = - bridge.performRpc("receiver", "print", message, std::nullopt); + auto response = sm.performRpc("receiver", "print", message, std::nullopt); auto elapsed = std::chrono::duration_cast( std::chrono::steady_clock::now() - t0) .count(); @@ -116,7 +115,7 @@ int main(int argc, char *argv[]) { } std::cout << "[caller] Shutting down...\n"; - bridge.disconnect(); + sm.disconnect(); std::cout << "[caller] Done.\n"; return 0; } diff --git a/examples/bridge_rpc/custom_receiver.cpp b/examples/rpc/custom_receiver.cpp similarity index 86% rename from examples/bridge_rpc/custom_receiver.cpp rename to examples/rpc/custom_receiver.cpp index a98cbd3..e06c45e 100644 --- a/examples/bridge_rpc/custom_receiver.cpp +++ b/examples/rpc/custom_receiver.cpp @@ -15,20 +15,20 @@ */ /* - * Receiver for the bridge_rpc example. + * Receiver for the rpc example. * * Connects to a LiveKit room as "receiver", registers a custom RPC method * called "print", and prints whatever string the caller sends. * * Usage: - * BridgeRpcReceiver - * LIVEKIT_URL=... LIVEKIT_TOKEN=... BridgeRpcReceiver + * RpcReceiver + * LIVEKIT_URL=... LIVEKIT_TOKEN=... RpcReceiver * * Generate a token with: * lk token create --join --room --identity receiver --valid-for 24h */ -#include "livekit_bridge/livekit_bridge.h" +#include "livekit/session_manager/session_manager.h" #include #include @@ -55,18 +55,18 @@ int main(int argc, char *argv[]) { token = e; } if (url.empty() || token.empty()) { - std::cerr << "Usage: BridgeRpcReceiver \n" - << " or: LIVEKIT_URL=... LIVEKIT_TOKEN=... BridgeRpcReceiver\n"; + std::cerr << "Usage: RpcReceiver \n" + << " or: LIVEKIT_URL=... LIVEKIT_TOKEN=... RpcReceiver\n"; return 1; } std::signal(SIGINT, handleSignal); - livekit_bridge::LiveKitBridge bridge; + livekit::SessionManager sm; std::cout << "[receiver] Connecting to " << url << " ...\n"; livekit::RoomOptions options; - if (!bridge.connect(url, token, options)) { + if (!sm.connect(url, token, options)) { std::cerr << "[receiver] Failed to connect.\n"; return 1; } @@ -74,7 +74,7 @@ int main(int argc, char *argv[]) { std::atomic call_count{0}; - bridge.registerRpcMethod( + sm.registerRpcMethod( "print", [&call_count](const livekit::RpcInvocationData &data) -> std::optional { @@ -107,7 +107,7 @@ int main(int argc, char *argv[]) { } std::cout << "[receiver] Shutting down...\n"; - bridge.disconnect(); + sm.disconnect(); std::cout << "[receiver] Done.\n"; return 0; } diff --git a/examples/session_manager_base_sdk/video_producer.cpp b/examples/session_manager_base_sdk/video_producer.cpp new file mode 100644 index 0000000..adb4430 --- /dev/null +++ b/examples/session_manager_base_sdk/video_producer.cpp @@ -0,0 +1,230 @@ +/* + * Copyright 2025 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. + */ + +/* + * SessionManager + base SDK video producer. + * + * SessionManager handles connect/disconnect; getRoom() provides the Room. + * All other work uses the Room directly: room_info(), localParticipant(), + * publishTrack(), etc. + * + * Usage: + * video_producer + * LIVEKIT_URL=... LIVEKIT_TOKEN=... video_producer + * + * Use identity "producer" for the token. Run video_viewer in another terminal. + * + * Requires a webcam. Falls back to solid-color frames if no camera is found. + */ + +#include "livekit/livekit.h" +#include "livekit/session_manager/session_manager.h" +#include "sdl_media.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace livekit; + +static std::atomic g_running{true}; +static void handleSignal(int) { g_running.store(false); } + +int main(int argc, char *argv[]) { + std::string url, token; + if (argc >= 3) { + url = argv[1]; + token = argv[2]; + } else { + const char *e = std::getenv("LIVEKIT_URL"); + if (e) + url = e; + e = std::getenv("LIVEKIT_TOKEN"); + if (e) + token = e; + } + + if (url.empty() || token.empty()) { + std::cerr << "Usage: video_producer \n" + << " or: LIVEKIT_URL=... LIVEKIT_TOKEN=... video_producer\n"; + return 1; + } + + std::signal(SIGINT, handleSignal); + + if (!SDL_Init(SDL_INIT_CAMERA)) { + LK_LOG_ERROR("[video_producer] SDL_Init failed: {}", SDL_GetError()); + return 1; + } + + livekit::SessionManager sm; + LK_LOG_INFO("[video_producer] Connecting to {} ...", url); + livekit::RoomOptions options; + options.auto_subscribe = true; + if (!sm.connect(url, token, options)) { + LK_LOG_ERROR("[video_producer] Failed to connect."); + SDL_Quit(); + return 1; + } + + livekit::Room *room = sm.getRoom(); + if (!room) { + LK_LOG_ERROR("[video_producer] getRoom() returned nullptr."); + sm.disconnect(); + SDL_Quit(); + return 1; + } + + LK_LOG_INFO("[video_producer] Connected."); + + auto info = room->room_info(); + std::cout << "[video_producer] Room info:\n" + << " SID: " << (info.sid ? *info.sid : "(none)") << "\n" + << " Name: " << info.name << "\n" + << " Num participants: " << info.num_participants << "\n"; + + constexpr int kWidth = 1280; + constexpr int kHeight = 720; + + auto videoSource = std::make_shared(kWidth, kHeight); + auto videoTrack = LocalVideoTrack::createLocalVideoTrack("cam", videoSource); + + TrackPublishOptions videoOpts; + videoOpts.source = TrackSource::SOURCE_CAMERA; + videoOpts.dtx = false; + videoOpts.simulcast = true; + + std::shared_ptr videoPub; + try { + videoPub = room->localParticipant()->publishTrack(videoTrack, videoOpts); + LK_LOG_INFO("[video_producer] Published cam track {}x{} (SID: {}).", kWidth, + kHeight, videoPub->sid()); + } catch (const std::exception &e) { + LK_LOG_ERROR("[video_producer] Failed to publish track: {}", e.what()); + sm.disconnect(); + SDL_Quit(); + return 1; + } + + // ---- SDL Camera capture or fallback ---- + bool cam_using_sdl = false; + std::unique_ptr sdl_cam; + std::atomic cam_running{true}; + std::thread cam_thread; + + { + int camCount = 0; + SDL_CameraID *cams = SDL_GetCameras(&camCount); + bool has_cam = cams && camCount > 0; + if (cams) + SDL_free(cams); + + if (has_cam) { + sdl_cam = std::make_unique( + kWidth, kHeight, 30, SDL_PIXELFORMAT_RGBA32, + [videoSource](const uint8_t *pixels, int pitch, int width, int height, + SDL_PixelFormat /*fmt*/, Uint64 timestampNS) { + auto frame = + VideoFrame::create(width, height, VideoBufferType::RGBA); + uint8_t *dst = frame.data(); + const int dstPitch = width * 4; + for (int y = 0; y < height; ++y) { + std::memcpy(dst + y * dstPitch, pixels + y * pitch, dstPitch); + } + try { + videoSource->captureFrame( + frame, static_cast(timestampNS / 1000), + VideoRotation::VIDEO_ROTATION_0); + } catch (const std::exception &e) { + LK_LOG_ERROR("[video_producer] captureFrame error: {}", e.what()); + } + }); + + if (sdl_cam->init()) { + cam_using_sdl = true; + LK_LOG_INFO("[video_producer] Using SDL webcam."); + cam_thread = std::thread([&]() { + while (cam_running.load()) { + sdl_cam->pump(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }); + } else { + LK_LOG_ERROR("[video_producer] SDL camera init failed."); + sdl_cam.reset(); + } + } + + if (!cam_using_sdl) { + LK_LOG_INFO("[video_producer] No camera found; sending solid blue " + "frames."); + cam_thread = std::thread([videoSource, &cam_running, kWidth, kHeight]() { + auto frame = VideoFrame::create(kWidth, kHeight, VideoBufferType::RGBA); + uint8_t *dst = frame.data(); + for (int i = 0; i < kWidth * kHeight; ++i) { + dst[i * 4 + 0] = 0; + dst[i * 4 + 1] = 0; + dst[i * 4 + 2] = 180; + dst[i * 4 + 3] = 255; + } + std::int64_t ts = 0; + while (cam_running.load()) { + try { + videoSource->captureFrame(frame, ts, + VideoRotation::VIDEO_ROTATION_0); + } catch (...) { + break; + } + ts += 33333; + std::this_thread::sleep_for(std::chrono::milliseconds(33)); + } + }); + } + } + + LK_LOG_INFO("[video_producer] Streaming... press Ctrl-C to stop."); + + while (g_running.load()) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_EVENT_QUIT) { + g_running.store(false); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + // ---- Cleanup ---- + LK_LOG_INFO("[video_producer] Shutting down..."); + cam_running.store(false); + if (cam_thread.joinable()) + cam_thread.join(); + sdl_cam.reset(); + + room->localParticipant()->unpublishTrack(videoPub->sid()); + sm.disconnect(); + SDL_Quit(); + LK_LOG_INFO("[video_producer] Done."); + return 0; +} diff --git a/examples/session_manager_base_sdk/video_viewer.cpp b/examples/session_manager_base_sdk/video_viewer.cpp new file mode 100644 index 0000000..57e5ca6 --- /dev/null +++ b/examples/session_manager_base_sdk/video_viewer.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2025 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. + */ + +/* + * SessionManager + base SDK video viewer. + * + * SessionManager handles connect/disconnect; getRoom() provides the Room. + * All other work uses the Room directly: room_info(), remoteParticipants(), + * track publications, VideoStream::fromTrack(), etc. + * + * Usage: + * video_viewer + * LIVEKIT_URL=... LIVEKIT_TOKEN=... video_viewer + * + * Use identity "viewer" for the token. Run video_producer (identity + * "producer") first. Displays the producer's camera track in an SDL window. + */ + +#include "livekit/livekit.h" +#include "livekit/session_manager/session_manager.h" +#include "sdl_media_manager.h" + +#include + +#include +#include +#include +#include +#include +#include + +using namespace livekit; + +std::atomic g_running{true}; +static void handleSignal(int) { g_running.store(false); } + +int main(int argc, char *argv[]) { + std::string url, token; + if (argc >= 3) { + url = argv[1]; + token = argv[2]; + } else { + const char *e = std::getenv("LIVEKIT_URL"); + if (e) + url = e; + e = std::getenv("LIVEKIT_TOKEN"); + if (e) + token = e; + } + + if (url.empty() || token.empty()) { + std::cerr << "Usage: video_viewer \n" + << " or: LIVEKIT_URL=... LIVEKIT_TOKEN=... video_viewer\n"; + return 1; + } + + std::signal(SIGINT, handleSignal); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + LK_LOG_ERROR("[video_viewer] SDL_Init failed: {}", SDL_GetError()); + return 1; + } + + SDLMediaManager media; + + livekit::SessionManager sm; + LK_LOG_INFO("[video_viewer] Connecting to {} ...", url); + livekit::RoomOptions options; + options.auto_subscribe = true; + if (!sm.connect(url, token, options)) { + LK_LOG_ERROR("[video_viewer] Failed to connect."); + SDL_Quit(); + return 1; + } + + livekit::Room *room = sm.getRoom(); + if (!room) { + LK_LOG_ERROR("[video_viewer] getRoom() returned nullptr."); + sm.disconnect(); + SDL_Quit(); + return 1; + } + + LK_LOG_INFO("[video_viewer] Connected. Waiting for producer..."); + + auto info = room->room_info(); + std::cout << "[video_viewer] Room info:\n" + << " SID: " << (info.sid ? *info.sid : "(none)") << "\n" + << " Name: " << info.name << "\n" + << " Num participants: " << info.num_participants << "\n"; + + std::cout << "[video_viewer] Waiting for video... Ctrl-C or close window to " + "stop.\n"; + + bool renderer_initialized = false; + + while (g_running.load()) { + // Poll room for subscribed video tracks (SessionManager owns the delegate; + // we discover tracks via room->remoteParticipants() and their publications) + if (!renderer_initialized && room) { + for (const auto &rp : room->remoteParticipants()) { + for (const auto &[sid, pub] : rp->trackPublications()) { + if (!pub->subscribed() || pub->kind() != TrackKind::KIND_VIDEO) { + continue; + } + if (pub->source() != TrackSource::SOURCE_CAMERA) { + continue; + } + auto track = pub->track(); + if (!track) { + continue; + } + + VideoStream::Options opts; + opts.format = VideoBufferType::RGBA; + auto video_stream = VideoStream::fromTrack(track, opts); + if (video_stream && media.initRenderer(video_stream)) { + std::cout << "[video_viewer] Subscribed to video track from " + << rp->identity() << "\n"; + renderer_initialized = true; + break; + } + } + if (renderer_initialized) { + break; + } + } + } + + media.render(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + LK_LOG_INFO("[video_viewer] Shutting down..."); + media.shutdownRenderer(); + sm.disconnect(); + SDL_Quit(); + LK_LOG_INFO("[video_viewer] Done."); + return 0; +} diff --git a/include/livekit/livekit.h b/include/livekit/livekit.h index d6f3ec0..a676282 100644 --- a/include/livekit/livekit.h +++ b/include/livekit/livekit.h @@ -26,6 +26,7 @@ #include "local_participant.h" #include "local_track_publication.h" #include "local_video_track.h" +#include "lk_log.h" #include "logging.h" #include "participant.h" #include "remote_participant.h" diff --git a/src/lk_log.h b/include/livekit/lk_log.h similarity index 100% rename from src/lk_log.h rename to include/livekit/lk_log.h diff --git a/bridge/include/livekit_bridge/bridge_audio_track.h b/include/livekit/session_manager/managed_local_audio_track.h similarity index 79% rename from bridge/include/livekit_bridge/bridge_audio_track.h rename to include/livekit/session_manager/managed_local_audio_track.h index 5683e06..fd6a08b 100644 --- a/bridge/include/livekit_bridge/bridge_audio_track.h +++ b/include/livekit/session_manager/managed_local_audio_track.h @@ -14,7 +14,7 @@ * limitations under the License. */ -/// @file bridge_audio_track.h +/// @file managed_local_audio_track.h /// @brief Handle for a published local audio track. #pragma once @@ -32,22 +32,22 @@ class LocalTrackPublication; class LocalParticipant; } // namespace livekit -namespace livekit_bridge { +namespace livekit { namespace test { -class BridgeAudioTrackTest; +class ManagedLocalAudioTrackTest; } // namespace test /** * Handle to a published local audio track. * - * Created via LiveKitBridge::createAudioTrack(). The bridge retains a + * Created via SessionManager::createAudioTrack(). The manager retains a * reference to every track it creates and will automatically release all * tracks when disconnect() is called. To unpublish a track mid-session, * call release() explicitly; dropping the shared_ptr alone is not - * sufficient because the bridge still holds a reference. + * sufficient because the SessionManager still holds a reference. * - * After release() (whether called explicitly or by the bridge on + * After release() (whether called explicitly or by the SessionManager on * disconnect), pushFrame() returns false and mute()/unmute() become * no-ops. The track object remains valid but inert. * @@ -56,19 +56,19 @@ class BridgeAudioTrackTest; * pushFrame() concurrently from multiple threads. * * Usage: - * auto mic = bridge.createAudioTrack("mic", 48000, 2, + * auto mic = session_manager.createAudioTrack("mic", 48000, 2, * livekit::TrackSource::SOURCE_MICROPHONE); * mic->pushFrame(pcm_data, samples_per_channel); * mic->mute(); * mic->release(); // unpublishes the track mid-session */ -class BridgeAudioTrack { +class ManagedLocalAudioTrack { public: - ~BridgeAudioTrack(); + ~ManagedLocalAudioTrack(); // Non-copyable - BridgeAudioTrack(const BridgeAudioTrack &) = delete; - BridgeAudioTrack &operator=(const BridgeAudioTrack &) = delete; + ManagedLocalAudioTrack(const ManagedLocalAudioTrack &) = delete; + ManagedLocalAudioTrack &operator=(const ManagedLocalAudioTrack &) = delete; /** * Push a PCM audio frame to the track. @@ -118,19 +118,20 @@ class BridgeAudioTrack { * * After this call, pushFrame() returns false and mute()/unmute() are * no-ops. Called automatically by the destructor and by - * LiveKitBridge::disconnect(). Safe to call multiple times (idempotent). + * SessionManager::disconnect(). Safe to call multiple times (idempotent). */ void release(); private: - friend class LiveKitBridge; - friend class test::BridgeAudioTrackTest; + friend class SessionManager; + friend class test::ManagedLocalAudioTrackTest; - BridgeAudioTrack(std::string name, int sample_rate, int num_channels, - std::shared_ptr source, - std::shared_ptr track, - std::shared_ptr publication, - livekit::LocalParticipant *participant); + ManagedLocalAudioTrack( + std::string name, int sample_rate, int num_channels, + std::shared_ptr source, + std::shared_ptr track, + std::shared_ptr publication, + livekit::LocalParticipant *participant); mutable std::mutex mutex_; std::string name_; @@ -144,4 +145,4 @@ class BridgeAudioTrack { livekit::LocalParticipant *participant_ = nullptr; // not owned }; -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/include/livekit_bridge/bridge_video_track.h b/include/livekit/session_manager/managed_local_video_track.h similarity index 77% rename from bridge/include/livekit_bridge/bridge_video_track.h rename to include/livekit/session_manager/managed_local_video_track.h index 8057b7a..cdd6ddb 100644 --- a/bridge/include/livekit_bridge/bridge_video_track.h +++ b/include/livekit/session_manager/managed_local_video_track.h @@ -14,7 +14,7 @@ * limitations under the License. */ -/// @file bridge_video_track.h +/// @file managed_local_video_track.h /// @brief Handle for a published local video track. #pragma once @@ -32,22 +32,22 @@ class LocalTrackPublication; class LocalParticipant; } // namespace livekit -namespace livekit_bridge { +namespace livekit { namespace test { -class BridgeVideoTrackTest; +class ManagedLocalVideoTrackTest; } // namespace test /** * Handle to a published local video track. * - * Created via LiveKitBridge::createVideoTrack(). The bridge retains a + * Created via SessionManager::createVideoTrack(). The manager retains a * reference to every track it creates and will automatically release all * tracks when disconnect() is called. To unpublish a track mid-session, * call release() explicitly; dropping the shared_ptr alone is not - * sufficient because the bridge still holds a reference. + * sufficient because the SessionManager still holds a reference. * - * After release() (whether called explicitly or by the bridge on + * After release() (whether called explicitly or by the SessionManager on * disconnect), pushFrame() returns false and mute()/unmute() become * no-ops. The track object remains valid but inert. * @@ -56,19 +56,19 @@ class BridgeVideoTrackTest; * pushFrame() concurrently from multiple threads. * * Usage: - * auto cam = bridge.createVideoTrack("cam", 1280, 720, + * auto cam = session_manager.createVideoTrack("cam", 1280, 720, * livekit::TrackSource::SOURCE_CAMERA); * cam->pushFrame(rgba_data, timestamp_us); * cam->mute(); * cam->release(); // unpublishes the track mid-session */ -class BridgeVideoTrack { +class ManagedLocalVideoTrack { public: - ~BridgeVideoTrack(); + ~ManagedLocalVideoTrack(); // Non-copyable - BridgeVideoTrack(const BridgeVideoTrack &) = delete; - BridgeVideoTrack &operator=(const BridgeVideoTrack &) = delete; + ManagedLocalVideoTrack(const ManagedLocalVideoTrack &) = delete; + ManagedLocalVideoTrack &operator=(const ManagedLocalVideoTrack &) = delete; /** * Push an RGBA video frame to the track. @@ -116,19 +116,19 @@ class BridgeVideoTrack { * * After this call, pushFrame() returns false and mute()/unmute() are * no-ops. Called automatically by the destructor and by - * LiveKitBridge::disconnect(). Safe to call multiple times (idempotent). + * SessionManager::disconnect(). Safe to call multiple times (idempotent). */ void release(); private: - friend class LiveKitBridge; - friend class test::BridgeVideoTrackTest; + friend class SessionManager; + friend class test::ManagedLocalVideoTrackTest; - BridgeVideoTrack(std::string name, int width, int height, - std::shared_ptr source, - std::shared_ptr track, - std::shared_ptr publication, - livekit::LocalParticipant *participant); + ManagedLocalVideoTrack(std::string name, int width, int height, + std::shared_ptr source, + std::shared_ptr track, + std::shared_ptr publication, + livekit::LocalParticipant *participant); mutable std::mutex mutex_; std::string name_; @@ -142,4 +142,4 @@ class BridgeVideoTrack { livekit::LocalParticipant *participant_ = nullptr; // not owned }; -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/include/livekit_bridge/rpc_constants.h b/include/livekit/session_manager/rpc_constants.h similarity index 63% rename from bridge/include/livekit_bridge/rpc_constants.h rename to include/livekit/session_manager/rpc_constants.h index 4bd8109..d81b147 100644 --- a/bridge/include/livekit_bridge/rpc_constants.h +++ b/include/livekit/session_manager/rpc_constants.h @@ -15,13 +15,13 @@ */ /// @file rpc_constants.h -/// @brief Constants for built-in bridge RPC methods. +/// @brief Constants for built-in SessionManager RPC methods. #pragma once #include -namespace livekit_bridge { +namespace livekit { namespace rpc { /// Built-in RPC method name used by remote track control. @@ -33,31 +33,22 @@ namespace track_control { enum class Action { kActionMute, kActionUnmute }; -/// RPC method name registered by the bridge for remote track control. -constexpr const char *kMethod = "lk.bridge.track-control"; +/// RPC method name registered by the SessionManager for remote track control. +extern const char *kMethod; /// Payload action strings. -constexpr const char *kActionMute = "mute"; -constexpr const char *kActionUnmute = "unmute"; +extern const char *kActionMute; +extern const char *kActionUnmute; /// Delimiter between action and track name in the payload (e.g. "mute:cam"). -constexpr char kDelimiter = ':'; +extern const char kDelimiter; /// Response payload returned on success. -constexpr const char *kResponseOk = "ok"; +extern const char *kResponseOk; /// Build a track-control RPC payload: ":". -inline std::string formatPayload(const char *action, - const std::string &track_name) { - std::string payload; - payload.reserve(std::char_traits::length(action) + 1 + - track_name.size()); - payload += action; - payload += kDelimiter; - payload += track_name; - return payload; -} +std::string formatPayload(const char *action, const std::string &track_name); } // namespace track_control } // namespace rpc -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/include/livekit_bridge/livekit_bridge.h b/include/livekit/session_manager/session_manager.h similarity index 78% rename from bridge/include/livekit_bridge/livekit_bridge.h rename to include/livekit/session_manager/session_manager.h index f366c10..428c019 100644 --- a/bridge/include/livekit_bridge/livekit_bridge.h +++ b/include/livekit/session_manager/session_manager.h @@ -14,14 +14,15 @@ * limitations under the License. */ -/// @file livekit_bridge.h -/// @brief High-level bridge API for the LiveKit C++ SDK. +/// @file session_manager.h +/// @brief High-level SessionManager API for the LiveKit C++ SDK. #pragma once -#include "livekit_bridge/bridge_audio_track.h" -#include "livekit_bridge/bridge_video_track.h" -#include "livekit_bridge/rpc_constants.h" +#include "livekit/lk_log.h" +#include "livekit/session_manager/managed_local_audio_track.h" +#include "livekit/session_manager/managed_local_video_track.h" +#include "livekit/session_manager/rpc_constants.h" #include "livekit/local_participant.h" #include "livekit/room.h" @@ -37,6 +38,7 @@ #include namespace livekit { + class Room; class AudioFrame; class VideoFrame; @@ -44,16 +46,13 @@ class AudioStream; class VideoStream; class Track; enum class TrackSource; -} // namespace livekit - -namespace livekit_bridge { -class BridgeRoomDelegate; +class SessionManagerRoomDelegate; class RpcController; namespace test { class CallbackKeyTest; -class LiveKitBridgeTest; +class SessionManagerTest; } // namespace test /// Callback type for incoming audio frames. @@ -68,12 +67,12 @@ using VideoFrameCallback = std::function; /** - * High-level bridge to the LiveKit C++ SDK. + * High-level SessionManager to the LiveKit C++ SDK. * * Owns the full room lifecycle: initialize SDK, create Room, connect, * publish tracks, and manage incoming frame callbacks. * - * The bridge retains a shared_ptr to every track it creates. On + * The SessionManager retains a shared_ptr to every track it creates. On * disconnect(), all tracks are released (unpublished) before the room * is torn down, guaranteeing safe teardown order. To unpublish a track * mid-session, call release() on the track explicitly; dropping the @@ -81,24 +80,24 @@ using VideoFrameCallback = std::functionpushFrame(pcm_data, samples_per_channel); * cam->pushFrame(rgba_data, timestamp_us); * - * bridge.setOnAudioFrameCallback("remote-participant", + * manager.setOnAudioFrameCallback("remote-participant", * livekit::TrackSource::SOURCE_MICROPHONE, * [](const livekit::AudioFrame& f) { process(f); }); * - * bridge.setOnVideoFrameCallback("remote-participant", + * manager.setOnVideoFrameCallback("remote-participant", * livekit::TrackSource::SOURCE_CAMERA, * [](const livekit::VideoFrame& f, int64_t ts) { render(f); }); * @@ -106,18 +105,18 @@ using VideoFrameCallback = std::functionrelease(); * * // Disconnect releases all remaining tracks and tears down the room: - * bridge.disconnect(); + * manager.disconnect(); */ -class LiveKitBridge { +class SessionManager { public: - LiveKitBridge(); - ~LiveKitBridge(); + SessionManager(); + ~SessionManager(); // Non-copyable, non-movable (owns threads, callbacks, room) - LiveKitBridge(const LiveKitBridge &) = delete; - LiveKitBridge &operator=(const LiveKitBridge &) = delete; - LiveKitBridge(LiveKitBridge &&) = delete; - LiveKitBridge &operator=(LiveKitBridge &&) = delete; + SessionManager(const SessionManager &) = delete; + SessionManager &operator=(const SessionManager &) = delete; + SessionManager(SessionManager &&) = delete; + SessionManager &operator=(SessionManager &&) = delete; // --------------------------------------------------------------- // Connection @@ -131,7 +130,7 @@ class LiveKitBridge { * succeeds or fails. auto_subscribe is enabled so that remote tracks * are subscribed automatically. * - * If the bridge is already connected, returns true immediately. + * If the SessionManager is already connected, returns true immediately. * If another thread is already in the process of connecting, returns * false without blocking. * @@ -152,9 +151,30 @@ class LiveKitBridge { */ void disconnect(); - /// Whether the bridge is currently connected to a room. + /// Whether the SessionManager is currently connected to a room. bool isConnected() const; + /** + * Get the underlying Room for direct base SDK access. + * + * Use this to access room_info(), remoteParticipants(), + * registerTextStreamHandler, registerByteStreamHandler, e2eeManager(), etc. + * + * @return Non-null pointer to the Room while connected; nullptr otherwise. + * + * @note The pointer is valid only while isConnected() is true. Do not store + * the pointer across disconnect/reconnect cycles. + * + * @warning Do NOT call setDelegate() on the returned Room — SessionManager + * manages the delegate for room lifecycle and track events. + * + * @note For publishing audio/video tracks, prefer createAudioTrack() and + * createVideoTrack() so SessionManager can manage track lifecycle on + * disconnect. Direct publishTrack() via localParticipant() bypasses + * that management. + */ + livekit::Room *getRoom() const; + // --------------------------------------------------------------- // Track creation (publishing) // --------------------------------------------------------------- @@ -162,12 +182,12 @@ class LiveKitBridge { /** * Create and publish a local audio track. * - * The bridge retains a reference to the track internally. To unpublish - * mid-session, call release() on the returned track. All surviving + * The SessionManager retains a reference to the track internally. To + * unpublish mid-session, call release() on the returned track. All surviving * tracks are automatically released on disconnect(). * - * @pre The bridge must be connected (via connect()). Calling this on a - * disconnected bridge is a programming error. + * @pre The SessionManager must be connected (via connect()). Calling this on + * a disconnected SessionManager is a programming error. * * @param name Human-readable track name. * @param sample_rate Sample rate in Hz (e.g. 48000). @@ -177,21 +197,21 @@ class LiveKitBridge { * publish multiple audio tracks from the same * participant that can be independently subscribed to. * @return Shared pointer to the published audio track handle (never null). - * @throws std::runtime_error if the bridge is not connected. + * @throws std::runtime_error if the SessionManager is not connected. */ - std::shared_ptr + std::shared_ptr createAudioTrack(const std::string &name, int sample_rate, int num_channels, livekit::TrackSource source); /** * Create and publish a local video track. * - * The bridge retains a reference to the track internally. To unpublish - * mid-session, call release() on the returned track. All surviving + * The SessionManager retains a reference to the track internally. To + * unpublish mid-session, call release() on the returned track. All surviving * tracks are automatically released on disconnect(). * - * @pre The bridge must be connected (via connect()). Calling this on a - * disconnected bridge is a programming error. + * @pre The SessionManager must be connected (via connect()). Calling this on + * a disconnected SessionManager is a programming error. * * @param name Human-readable track name. * @param width Video width in pixels. @@ -201,9 +221,9 @@ class LiveKitBridge { * multiple video tracks from the same participant that * can be independently subscribed to. * @return Shared pointer to the published video track handle (never null). - * @throws std::runtime_error if the bridge is not connected. + * @throws std::runtime_error if the SessionManager is not connected. */ - std::shared_ptr + std::shared_ptr createVideoTrack(const std::string &name, int width, int height, livekit::TrackSource source); @@ -285,7 +305,7 @@ class LiveKitBridge { * @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. + * RPC call fails, or the SessionManager is not connected. */ std::optional performRpc(const std::string &destination_identity, const std::string &method, @@ -296,7 +316,7 @@ class LiveKitBridge { * 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 + * participant, the SessionManager invokes @p handler. The handler may return * an optional response payload or throw a @c livekit::RpcError to * signal failure to the caller. * @@ -329,7 +349,7 @@ class LiveKitBridge { /** * Request a remote participant to mute a published track. * - * The remote participant must be a LiveKitBridge instance (which + * The remote participant must be a SessionManager instance (which * automatically registers the built-in track-control RPC handler). * * @param destination_identity Identity of the remote participant. @@ -342,7 +362,7 @@ class LiveKitBridge { /** * Request a remote participant to unmute a published track. * - * The remote participant must be a LiveKitBridge instance (which + * The remote participant must be a SessionManager instance (which * automatically registers the built-in track-control RPC handler). * * @param destination_identity Identity of the remote participant. @@ -353,9 +373,9 @@ class LiveKitBridge { const std::string &track_name); private: - friend class BridgeRoomDelegate; + friend class SessionManagerRoomDelegate; friend class test::CallbackKeyTest; - friend class test::LiveKitBridgeTest; + friend class test::SessionManagerTest; /// Composite key for the callback map: (participant_identity, source). /// Only one callback can exist per key -- re-registering overwrites. @@ -378,12 +398,12 @@ class LiveKitBridge { bool is_audio = false; }; - /// Called by BridgeRoomDelegate when a remote track is subscribed. + /// Called by SessionManagerRoomDelegate when a remote track is subscribed. void onTrackSubscribed(const std::string &participant_identity, livekit::TrackSource source, const std::shared_ptr &track); - /// Called by BridgeRoomDelegate when a remote track is unsubscribed. + /// Called by SessionManagerRoomDelegate when a remote track is unsubscribed. void onTrackUnsubscribed(const std::string &participant_identity, livekit::TrackSource source); @@ -417,7 +437,7 @@ class LiveKitBridge { static constexpr int kMaxActiveReaders = 20; std::unique_ptr room_; - std::unique_ptr delegate_; + std::unique_ptr delegate_; std::unique_ptr rpc_controller_; /// Registered callbacks (may be registered before tracks are subscribed). @@ -431,12 +451,12 @@ class LiveKitBridge { std::unordered_map active_readers_; - /// All tracks created by this bridge. The bridge retains a shared_ptr so - /// it can force-release every track on disconnect() before the room is - /// destroyed, preventing dangling @c participant_ pointers. - std::vector> published_audio_tracks_; + /// All tracks created by this SessionManager. The SessionManager retains a + /// shared_ptr so it can force-release every track on disconnect() before the + /// room is destroyed, preventing dangling @c participant_ pointers. + std::vector> published_audio_tracks_; /// @copydoc published_audio_tracks_ - std::vector> published_video_tracks_; + std::vector> published_video_tracks_; }; -} // namespace livekit_bridge +} // namespace livekit diff --git a/src/audio_source.cpp b/src/audio_source.cpp index 4e1a48a..bc648e5 100644 --- a/src/audio_source.cpp +++ b/src/audio_source.cpp @@ -24,7 +24,7 @@ #include "ffi.pb.h" #include "ffi_client.h" #include "livekit/audio_frame.h" -#include "lk_log.h" +#include "livekit/lk_log.h" namespace livekit { diff --git a/src/data_stream.cpp b/src/data_stream.cpp index 3376466..edd03d0 100644 --- a/src/data_stream.cpp +++ b/src/data_stream.cpp @@ -7,7 +7,7 @@ #include "ffi_client.h" #include "livekit/local_participant.h" -#include "lk_log.h" +#include "livekit/lk_log.h" #include "room.pb.h" namespace livekit { diff --git a/src/ffi_client.cpp b/src/ffi_client.cpp index 916ab88..e8443d0 100644 --- a/src/ffi_client.cpp +++ b/src/ffi_client.cpp @@ -26,7 +26,7 @@ #include "livekit/rpc_error.h" #include "livekit/track.h" #include "livekit_ffi.h" -#include "lk_log.h" +#include "livekit/lk_log.h" #include "room.pb.h" #include "room_proto_converter.h" diff --git a/src/livekit.cpp b/src/livekit.cpp index b9132ef..86b9ebd 100644 --- a/src/livekit.cpp +++ b/src/livekit.cpp @@ -16,7 +16,7 @@ #include "livekit/livekit.h" #include "ffi_client.h" -#include "lk_log.h" +#include "livekit/lk_log.h" namespace livekit { diff --git a/src/room.cpp b/src/room.cpp index 7eed7ed..9d0249e 100644 --- a/src/room.cpp +++ b/src/room.cpp @@ -31,7 +31,7 @@ #include "ffi.pb.h" #include "ffi_client.h" #include "livekit_ffi.h" -#include "lk_log.h" +#include "livekit/lk_log.h" #include "room.pb.h" #include "room_proto_converter.h" #include "track.pb.h" diff --git a/bridge/README.md b/src/session_manager/README.md similarity index 60% rename from bridge/README.md rename to src/session_manager/README.md index cebb09b..2caf1b4 100644 --- a/bridge/README.md +++ b/src/session_manager/README.md @@ -1,32 +1,27 @@ -# LiveKit Bridge +# LiveKit SessionManager -A simplified, high-level C++ wrapper around the [LiveKit C++ SDK](../README.md). The bridge abstracts away room lifecycle management, track creation, publishing, and subscription boilerplate so that external codebases can interface with LiveKit in just a few lines. It is intended that this library will be used to bridge the LiveKit C++ SDK into other SDKs such as, but not limited to, Foxglove, ROS, and Rerun. - -It is intended that this library closely matches the style of the core LiveKit C++ SDK. - -# Prerequisites -Since this is an extention of the LiveKit C++ SDK, go through the LiveKit C++ SDK installation instructions first: -*__[LiveKit C++ SDK](../README.md)__* +A simplified, high-level C++ wrapper around the [LiveKit C++ SDK](../README.md). +The SessionManager abstracts away room lifecycle management, track creation, publishing, and subscription boilerplate so interfacing with LiveKit is just a few lines of code. ## Usage Overview ```cpp -#include "livekit_bridge/livekit_bridge.h" +#include "livekit/session_manager/session_manager.h" #include "livekit/audio_frame.h" #include "livekit/video_frame.h" #include "livekit/track.h" // 1. Connect -livekit_bridge::LiveKitBridge bridge; +livekit::SessionManager sm; livekit::RoomOptions options; options.auto_subscribe = true; // automatically subscribe to all remote tracks options.dynacast = false; -bridge.connect("wss://my-server.livekit.cloud", token, options); +sm.connect("wss://my-server.livekit.cloud", token, options); // 2. Create outgoing tracks (RAII-managed) -auto mic = bridge.createAudioTrack("mic", 48000, 2, +auto mic = sm.createAudioTrack("mic", 48000, 2, livekit::TrackSource::SOURCE_MICROPHONE); // name, sample_rate, channels, source -auto cam = bridge.createVideoTrack("cam", 1280, 720, +auto cam = sm.createVideoTrack("cam", 1280, 720, livekit::TrackSource::SOURCE_CAMERA); // name, width, height, source // 3. Push frames to remote participants @@ -34,25 +29,25 @@ mic->pushFrame(pcm_data, samples_per_channel); cam->pushFrame(rgba_data, timestamp_us); // 4. Receive frames from a remote participant -bridge.setOnAudioFrameCallback("remote-peer", livekit::TrackSource::SOURCE_MICROPHONE, +sm.setOnAudioFrameCallback("remote-peer", livekit::TrackSource::SOURCE_MICROPHONE, [](const livekit::AudioFrame& frame) { // Called on a background reader thread }); -bridge.setOnVideoFrameCallback("remote-peer", livekit::TrackSource::SOURCE_CAMERA, +sm.setOnVideoFrameCallback("remote-peer", livekit::TrackSource::SOURCE_CAMERA, [](const livekit::VideoFrame& frame, int64_t timestamp_us) { // Called on a background reader thread }); // 5. RPC (Remote Procedure Call) -bridge.registerRpcMethod("greet", +sm.registerRpcMethod("greet", [](const livekit::RpcInvocationData& data) -> std::optional { return "Hello, " + data.caller_identity + "!"; }); -std::string response = bridge.performRpc("remote-peer", "greet", ""); +std::string response = sm.performRpc("remote-peer", "greet", ""); -bridge.unregisterRpcMethod("greet"); +sm.unregisterRpcMethod("greet"); // Controller side: send commands to the publisher controller_bridge.requestRemoteTrackMute("robot-1", "mic"); // mute audio track "mic" @@ -61,17 +56,29 @@ controller_bridge.requestRemoteTrackUnmute("robot-1", "mic"); // unmute it // 7. Cleanup is automatic (RAII), or explicit: mic.reset(); // unpublishes the audio track cam.reset(); // unpublishes the video track -bridge.disconnect(); +sm.disconnect(); ``` -## Building +### Accessing the Base SDK via getRoom() -The bridge is a component of the `client-sdk-cpp` build. See the "⚙️ BUILD" section of the [LiveKit C++ SDK README](../README.md) for instructions on how to build the bridge. +The SessionManager owns the underlying `livekit::Room` internally. Use `getRoom()` to access base SDK features while the SessionManager manages the connection lifecycle: -This produces `liblivekit_bridge` (shared library) and optional `robot_stub`, `human_stub`, `robot`, and `human` executables. +```cpp +if (auto* room = sm.getRoom()) { + auto info = room->room_info(); + // ... use info.sid, info.name, info.num_participants, etc. + + for (const auto& rp : room->remoteParticipants()) { + // ... enumerate remote participants + } + + room->registerTextStreamHandler("topic", [](auto reader, auto identity) { + // ... handle incoming text streams + }); +} +``` -### Using the bridge in your own CMake project -TODO(sderosa): add instructions on how to use the bridge in your own CMake project. +The returned pointer is valid only while `isConnected()` is true. Do not call `setDelegate()` on the returned Room — SessionManager manages the delegate. For publishing audio/video tracks, prefer `createAudioTrack()` and `createVideoTrack()` so SessionManager can manage track lifecycle on disconnect. ## Architecture @@ -80,12 +87,12 @@ TODO(sderosa): add instructions on how to use the bridge in your own CMake proje ``` Your Application | | - | pushFrame() -----> BridgeAudioTrack | (sending to remote participants) - | pushFrame() -----> BridgeVideoTrack | + | pushFrame() -----> ManagedLocalAudioTrack | (sending to remote participants) + | pushFrame() -----> ManagedLocalVideoTrack | | | | callback() <------ Reader Thread | (receiving from remote participants) | | - +------- LiveKitBridge -----------------+ + +------- SessionManager ----------------+ | LiveKit Room | @@ -94,28 +101,28 @@ Your Application ### Core Components -**`LiveKitBridge`** -- The main entry point. Owns the full room lifecycle: SDK initialization, room connection, track publishing, and frame callback management. +**`SessionManager`** -- The main entry point. Owns the full room lifecycle: SDK initialization, room connection, track publishing, and frame callback management. -**`BridgeAudioTrack` / `BridgeVideoTrack`** -- RAII handles for published local tracks. Created via `createAudioTrack()` / `createVideoTrack()`. When the `shared_ptr` is dropped, the track is automatically unpublished and all underlying SDK resources are freed. Call `pushFrame()` to send audio/video data to remote participants. +**`ManagedLocalAudioTrack` / `ManagedLocalVideoTrack`** -- RAII handles for published local tracks. Created via `createAudioTrack()` / `createVideoTrack()`. When the `shared_ptr` is dropped, the track is automatically unpublished and all underlying SDK resources are freed. Call `pushFrame()` to send audio/video data to remote participants. -**`BridgeRoomDelegate`** -- Internal (not part of the public API; lives in `src/`). Listens for `onTrackSubscribed` / `onTrackUnsubscribed` events from the LiveKit SDK and wires up reader threads automatically. +**`SessionManagerRoomDelegate`** -- Internal (not part of the public API; lives in `src/`). Listens for `onTrackSubscribed` / `onTrackUnsubscribed` events from the LiveKit SDK and wires up reader threads automatically. ### What is a Reader? A **reader** is a background thread that receives decoded media frames from a remote participant. -When a remote participant publishes an audio or video track and the bridge subscribes to it (auto-subscribe is enabled by default), the bridge creates an `AudioStream` or `VideoStream` from that track and spins up a dedicated thread. This thread loops on `stream->read()`, which blocks until a new frame arrives. Each received frame is forwarded to the user's registered callback. +When a remote participant publishes an audio or video track and the SessionManager subscribes to it (auto-subscribe is enabled by default), the SessionManager creates an `AudioStream` or `VideoStream` from that track and spins up a dedicated thread. This thread loops on `stream->read()`, which blocks until a new frame arrives. Each received frame is forwarded to the user's registered callback. In short: -- **Sending** (you -> remote): `BridgeAudioTrack::pushFrame()` / `BridgeVideoTrack::pushFrame()` +- **Sending** (you -> remote): `ManagedLocalAudioTrack::pushFrame()` / `ManagedLocalVideoTrack::pushFrame()` - **Receiving** (remote -> you): reader threads invoke your registered callbacks -Reader threads are managed entirely by the bridge. They are created when a matching remote track is subscribed, and torn down (stream closed, thread joined) when the track is unsubscribed, the callback is unregistered, or `disconnect()` is called. +Reader threads are managed entirely by the SessionManager. They are created when a matching remote track is subscribed, and torn down (stream closed, thread joined) when the track is unsubscribed, the callback is unregistered, or `disconnect()` is called. ### Callback Registration Timing -Callbacks are keyed by `(participant_identity, track_source)`. You can register them **before** the remote participant has joined the room. The bridge stores the callback and automatically wires it up when the matching track is subscribed. +Callbacks are keyed by `(participant_identity, track_source)`. You can register them **before** the remote participant has joined the room. The SessionManager stores the callback and automatically wires it up when the matching track is subscribed. > **Note:** Only one callback may be set per `(participant_identity, track_source)` pair. Calling `setOnAudioFrameCallback` or `setOnVideoFrameCallback` again with the same identity and source will silently replace the previous callback. If you need to fan-out a single stream to multiple consumers, do so inside your callback. @@ -124,30 +131,31 @@ This means the typical pattern is: ```cpp // Register first, connect second -- or register after connect but before // the remote participant joins. -bridge.setOnAudioFrameCallback("robot-1", livekit::TrackSource::SOURCE_MICROPHONE, my_callback); +sm.setOnAudioFrameCallback("robot-1", livekit::TrackSource::SOURCE_MICROPHONE, my_callback); livekit::RoomOptions options; options.auto_subscribe = true; -bridge.connect(url, token, options); +sm.connect(url, token, options); // When robot-1 joins and publishes a mic track, my_callback starts firing. ``` ### Thread Safety -- `LiveKitBridge` uses a mutex to protect the callback map and active reader state. +- `SessionManager` uses a mutex to protect the callback map and active reader state. - Frame callbacks fire on background reader threads. If your callback accesses shared application state, you are responsible for synchronization. -- `disconnect()` closes all streams and joins all reader threads before returning -- it is safe to destroy the bridge immediately after. +- `disconnect()` closes all streams and joins all reader threads before returning -- it is safe to destroy the SessionManager immediately after. ## API Reference -### `LiveKitBridge` +### `SessionManager` | Method | Description | |---|---| | `connect(url, token, options)` | Connect to a LiveKit room. Initializes the SDK, creates a Room, and connects with auto-subscribe enabled. | | `disconnect()` | Disconnect and release all resources. Joins all reader threads. Safe to call multiple times. | -| `isConnected()` | Returns whether the bridge is currently connected. | -| `createAudioTrack(name, sample_rate, num_channels, source)` | Create and publish a local audio track with the given `TrackSource` (e.g. `SOURCE_MICROPHONE`, `SOURCE_SCREENSHARE_AUDIO`). Returns an RAII `shared_ptr`. | -| `createVideoTrack(name, width, height, source)` | Create and publish a local video track with the given `TrackSource` (e.g. `SOURCE_CAMERA`, `SOURCE_SCREENSHARE`). Returns an RAII `shared_ptr`. | +| `isConnected()` | Returns whether the SessionManager is currently connected. | +| `getRoom()` | Returns a raw pointer to the underlying `livekit::Room` for direct base SDK access (e.g. `room_info()`, `remoteParticipants()`, `registerTextStreamHandler`). Returns `nullptr` when disconnected. Do not call `setDelegate()` on the returned Room. | +| `createAudioTrack(name, sample_rate, num_channels, source)` | Create and publish a local audio track with the given `TrackSource` (e.g. `SOURCE_MICROPHONE`, `SOURCE_SCREENSHARE_AUDIO`). Returns an RAII `shared_ptr`. | +| `createVideoTrack(name, width, height, source)` | Create and publish a local video track with the given `TrackSource` (e.g. `SOURCE_CAMERA`, `SOURCE_SCREENSHARE`). Returns an RAII `shared_ptr`. | | `setOnAudioFrameCallback(identity, source, callback)` | Register a callback for audio frames from a specific remote participant + track source. | | `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. | @@ -158,7 +166,7 @@ bridge.connect(url, token, options); | `requestRemoteTrackMute(identity, track_name)` | Ask a remote participant to mute a track by name. Throws `livekit::RpcError` on failure. | | `requestRemoteTrackUnmute(identity, track_name)` | Ask a remote participant to unmute a track by name. Throws `livekit::RpcError` on failure. | -### `BridgeAudioTrack` +### `ManagedLocalAudioTrack` | Method | Description | |---|---| @@ -167,7 +175,7 @@ bridge.connect(url, token, options); | `release()` | Explicitly unpublish and free resources. Called automatically by the destructor. | | `name()` / `sampleRate()` / `numChannels()` | Accessors for track configuration. | -### `BridgeVideoTrack` +### `ManagedLocalVideoTrack` | Method | Description | |---|---| @@ -176,12 +184,14 @@ bridge.connect(url, token, options); | `release()` | Explicitly unpublish and free resources. Called automatically by the destructor. | | `name()` / `width()` / `height()` | Accessors for track configuration. | -## Examples -- examples/robot.cpp: publishes video and audio from a webcam and microphone. This requires a webcam and microphone to be available. -- examples/human.cpp: receives and renders video to the screen, receives and plays audio through the speaker. +## Running the examples -### Running the examples: -Note: the following workflow works for both `human` and `robot`. +``` +export LIVEKIT_URL="wss://your-server.livekit.cloud" +export LIVEKIT_TOKEN= +./build-release/bin/ +``` +(Or pass ` ` as positional arguments.) 1. create a `robo_room` ``` @@ -212,7 +222,7 @@ export LIVEKIT_TOKEN= ``` export LIVEKIT_URL="wss://your-server.livekit.cloud" export LIVEKIT_TOKEN= -./build-release/bin/human +./build-release/bin/HumanRobotHuman ``` The human will print periodic summaries like: @@ -225,14 +235,14 @@ The human will print periodic summaries like: ## Testing -The bridge includes a unit test suite built with [Google Test](https://github.com/google/googletest). Tests cover +The SessionManager includes a unit test suite built with [Google Test](https://github.com/google/googletest). Tests cover 1. `CallbackKey` hashing/equality, -2. `BridgeAudioTrack`/`BridgeVideoTrack` state management, and -3. `LiveKitBridge` pre-connection behaviour (callback registration, error handling). +2. `ManagedLocalAudioTrack`/`ManagedLocalVideoTrack` state management, and +3. `SessionManager` pre-connection behaviour (callback registration, error handling). ### Building and running tests -Bridge tests are automatically included when you build with the `debug-tests` or `release-tests` command: +SessionManager tests are automatically included when you build with the `debug-tests` or `release-tests` command: ```bash ./build.sh debug-tests @@ -241,21 +251,21 @@ Bridge tests are automatically included when you build with the `debug-tests` or Then run them directly: ```bash -./build-debug/bin/livekit_bridge_tests +./build-debug/bin/session_manager_tests ``` -### Standalone bridge tests only +### Standalone SessionManager tests only -If you want to build bridge tests independently (without the parent SDK tests), set `LIVEKIT_BRIDGE_BUILD_TESTS=ON`: +If you want to build SessionManager tests independently (without the parent SDK tests), set `SESSION_MANAGER_BUILD_TESTS=ON`: ```bash -cmake --preset macos-debug -DLIVEKIT_BRIDGE_BUILD_TESTS=ON -cmake --build build-debug --target livekit_bridge_tests +cmake --preset macos-debug -DSESSION_MANAGER_BUILD_TESTS=ON +cmake --build build-debug --target session_manager_tests ``` ## Limitations -The bridge is designed for simplicity and currently only supports limited audio and video features. It does not expose: +The SessionManager is designed for simplicity and currently only supports limited audio and video features. It does not expose: - We dont support all events defined in the RoomDelegate interface. - E2EE configuration @@ -265,4 +275,4 @@ The bridge is designed for simplicity and currently only supports limited audio - Custom `RoomOptions` or `TrackPublishOptions` - **One callback per (participant, source):** Only a single callback can be registered for each `(participant_identity, track_source)` pair. Re-registering with the same key silently replaces the previous callback. To fan-out a stream to multiple consumers, dispatch from within your single callback. -For advanced use cases, use the full `client-sdk-cpp` API directly, or expand the bridge to support your use case. +For advanced use cases, use the full `client-sdk-cpp` API directly, or expand the SessionManager to support your use case. diff --git a/bridge/src/bridge_audio_track.cpp b/src/session_manager/managed_local_audio_track.cpp similarity index 73% rename from bridge/src/bridge_audio_track.cpp rename to src/session_manager/managed_local_audio_track.cpp index 654129c..5fe2c8f 100644 --- a/bridge/src/bridge_audio_track.cpp +++ b/src/session_manager/managed_local_audio_track.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ -/// @file bridge_audio_track.cpp -/// @brief Implementation of BridgeAudioTrack. +/// @file managed_local_audio_track.cpp +/// @brief Implementation of ManagedLocalAudioTrack. -#include "livekit_bridge/bridge_audio_track.h" +#include "livekit/session_manager/managed_local_audio_track.h" #include "livekit/audio_frame.h" #include "livekit/audio_source.h" @@ -27,11 +27,11 @@ #include -#include "lk_log.h" +#include "livekit/lk_log.h" -namespace livekit_bridge { +namespace livekit { -BridgeAudioTrack::BridgeAudioTrack( +ManagedLocalAudioTrack::ManagedLocalAudioTrack( std::string name, int sample_rate, int num_channels, std::shared_ptr source, std::shared_ptr track, @@ -42,10 +42,10 @@ BridgeAudioTrack::BridgeAudioTrack( track_(std::move(track)), publication_(std::move(publication)), participant_(participant) {} -BridgeAudioTrack::~BridgeAudioTrack() { release(); } +ManagedLocalAudioTrack::~ManagedLocalAudioTrack() { release(); } -bool BridgeAudioTrack::pushFrame(const std::vector &data, - int samples_per_channel, int timeout_ms) { +bool ManagedLocalAudioTrack::pushFrame(const std::vector &data, + int samples_per_channel, int timeout_ms) { livekit::AudioFrame frame(std::vector(data.begin(), data.end()), sample_rate_, num_channels_, samples_per_channel); @@ -57,14 +57,14 @@ bool BridgeAudioTrack::pushFrame(const std::vector &data, try { source_->captureFrame(frame, timeout_ms); } catch (const std::exception &e) { - LK_LOG_ERROR("BridgeAudioTrack captureFrame error: {}", e.what()); + LK_LOG_ERROR("ManagedLocalAudioTrack captureFrame error: {}", e.what()); return false; } return true; } -bool BridgeAudioTrack::pushFrame(const std::int16_t *data, - int samples_per_channel, int timeout_ms) { +bool ManagedLocalAudioTrack::pushFrame(const std::int16_t *data, + int samples_per_channel, int timeout_ms) { const int total_samples = samples_per_channel * num_channels_; livekit::AudioFrame frame( std::vector(data, data + total_samples), sample_rate_, @@ -78,32 +78,32 @@ bool BridgeAudioTrack::pushFrame(const std::int16_t *data, try { source_->captureFrame(frame, timeout_ms); } catch (const std::exception &e) { - LK_LOG_ERROR("BridgeAudioTrack captureFrame error: {}", e.what()); + LK_LOG_ERROR("ManagedLocalAudioTrack captureFrame error: {}", e.what()); return false; } return true; } -void BridgeAudioTrack::mute() { +void ManagedLocalAudioTrack::mute() { std::lock_guard lock(mutex_); if (!released_ && track_) { track_->mute(); } } -void BridgeAudioTrack::unmute() { +void ManagedLocalAudioTrack::unmute() { std::lock_guard lock(mutex_); if (!released_ && track_) { track_->unmute(); } } -bool BridgeAudioTrack::isReleased() const noexcept { +bool ManagedLocalAudioTrack::isReleased() const noexcept { std::lock_guard lock(mutex_); return released_; } -void BridgeAudioTrack::release() { +void ManagedLocalAudioTrack::release() { std::lock_guard lock(mutex_); if (released_) { return; @@ -116,7 +116,7 @@ void BridgeAudioTrack::release() { participant_->unpublishTrack(publication_->sid()); } catch (...) { // Best-effort cleanup; ignore errors during teardown - LK_LOG_WARN("BridgeAudioTrack unpublishTrack error, continuing with " + LK_LOG_WARN("ManagedLocalAudioTrack unpublishTrack error, continuing with " "cleanup"); } } @@ -128,4 +128,4 @@ void BridgeAudioTrack::release() { participant_ = nullptr; } -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/src/bridge_video_track.cpp b/src/session_manager/managed_local_video_track.cpp similarity index 71% rename from bridge/src/bridge_video_track.cpp rename to src/session_manager/managed_local_video_track.cpp index d78d232..27a97f5 100644 --- a/bridge/src/bridge_video_track.cpp +++ b/src/session_manager/managed_local_video_track.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ -/// @file bridge_video_track.cpp -/// @brief Implementation of BridgeVideoTrack. +/// @file managed_local_video_track.cpp +/// @brief Implementation of ManagedLocalVideoTrack. -#include "livekit_bridge/bridge_video_track.h" +#include "livekit/session_manager/managed_local_video_track.h" #include "livekit/local_participant.h" #include "livekit/local_track_publication.h" @@ -27,11 +27,11 @@ #include -#include "lk_log.h" +#include "livekit/lk_log.h" -namespace livekit_bridge { +namespace livekit { -BridgeVideoTrack::BridgeVideoTrack( +ManagedLocalVideoTrack::ManagedLocalVideoTrack( std::string name, int width, int height, std::shared_ptr source, std::shared_ptr track, @@ -41,10 +41,10 @@ BridgeVideoTrack::BridgeVideoTrack( source_(std::move(source)), track_(std::move(track)), publication_(std::move(publication)), participant_(participant) {} -BridgeVideoTrack::~BridgeVideoTrack() { release(); } +ManagedLocalVideoTrack::~ManagedLocalVideoTrack() { release(); } -bool BridgeVideoTrack::pushFrame(const std::vector &rgba, - std::int64_t timestamp_us) { +bool ManagedLocalVideoTrack::pushFrame(const std::vector &rgba, + std::int64_t timestamp_us) { livekit::VideoFrame frame( width_, height_, livekit::VideoBufferType::RGBA, std::vector(rgba.begin(), rgba.end())); @@ -57,15 +57,15 @@ bool BridgeVideoTrack::pushFrame(const std::vector &rgba, try { source_->captureFrame(frame, timestamp_us); } catch (const std::exception &e) { - LK_LOG_ERROR("BridgeVideoTrack captureFrame error: {}", e.what()); + LK_LOG_ERROR("ManagedLocalVideoTrack captureFrame error: {}", e.what()); return false; } return true; } -bool BridgeVideoTrack::pushFrame(const std::uint8_t *rgba, - std::size_t rgba_size, - std::int64_t timestamp_us) { +bool ManagedLocalVideoTrack::pushFrame(const std::uint8_t *rgba, + std::size_t rgba_size, + std::int64_t timestamp_us) { livekit::VideoFrame frame(width_, height_, livekit::VideoBufferType::RGBA, std::vector(rgba, rgba + rgba_size)); @@ -77,32 +77,32 @@ bool BridgeVideoTrack::pushFrame(const std::uint8_t *rgba, try { source_->captureFrame(frame, timestamp_us); } catch (const std::exception &e) { - LK_LOG_ERROR("BridgeVideoTrack captureFrame error: {}", e.what()); + LK_LOG_ERROR("ManagedLocalVideoTrack captureFrame error: {}", e.what()); return false; } return true; } -void BridgeVideoTrack::mute() { +void ManagedLocalVideoTrack::mute() { std::lock_guard lock(mutex_); if (!released_ && track_) { track_->mute(); } } -void BridgeVideoTrack::unmute() { +void ManagedLocalVideoTrack::unmute() { std::lock_guard lock(mutex_); if (!released_ && track_) { track_->unmute(); } } -bool BridgeVideoTrack::isReleased() const noexcept { +bool ManagedLocalVideoTrack::isReleased() const noexcept { std::lock_guard lock(mutex_); return released_; } -void BridgeVideoTrack::release() { +void ManagedLocalVideoTrack::release() { std::lock_guard lock(mutex_); if (released_) { return; @@ -115,7 +115,7 @@ void BridgeVideoTrack::release() { participant_->unpublishTrack(publication_->sid()); } catch (...) { // Best-effort cleanup; ignore errors during teardown - LK_LOG_WARN("BridgeVideoTrack unpublishTrack error, continuing with " + LK_LOG_WARN("ManagedLocalVideoTrack unpublishTrack error, continuing with " "cleanup"); } } @@ -127,4 +127,4 @@ void BridgeVideoTrack::release() { participant_ = nullptr; } -} // namespace livekit_bridge +} // namespace livekit diff --git a/src/session_manager/rpc_constants.cpp b/src/session_manager/rpc_constants.cpp new file mode 100644 index 0000000..6ca1010 --- /dev/null +++ b/src/session_manager/rpc_constants.cpp @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#include "livekit/session_manager/rpc_constants.h" + +namespace livekit { +namespace rpc { +namespace track_control { + +const char *kMethod = "lk.session_manager.track-control"; +const char *kActionMute = "mute"; +const char *kActionUnmute = "unmute"; +const char kDelimiter = ':'; +const char *kResponseOk = "ok"; + +std::string formatPayload(const char *action, const std::string &track_name) { + std::string payload; + payload.reserve(std::char_traits::length(action) + 1 + + track_name.size()); + payload += action; + payload += kDelimiter; + payload += track_name; + return payload; +} + +} // namespace track_control +} // namespace rpc +} // namespace livekit diff --git a/bridge/src/rpc_controller.cpp b/src/session_manager/rpc_controller.cpp similarity index 91% rename from bridge/src/rpc_controller.cpp rename to src/session_manager/rpc_controller.cpp index 3151466..fc9ac07 100644 --- a/bridge/src/rpc_controller.cpp +++ b/src/session_manager/rpc_controller.cpp @@ -18,7 +18,7 @@ /// @brief Implementation of RpcController. #include "rpc_controller.h" -#include "livekit_bridge/rpc_constants.h" +#include "livekit/session_manager/rpc_constants.h" #include "livekit/local_participant.h" #include "livekit/rpc_error.h" @@ -26,7 +26,7 @@ #include #include -namespace livekit_bridge { +namespace livekit { RpcController::RpcController(TrackActionFn track_action_fn) : track_action_fn_(std::move(track_action_fn)), lp_(nullptr) {} @@ -50,8 +50,8 @@ void RpcController::disable() { std::string RpcController::performRpc(const std::string &destination_identity, - const std::string &method, const std::string &payload, - const std::optional &response_timeout) { + const std::string &method, const std::string &payload, + const std::optional &response_timeout) { assert(lp_ != nullptr); return lp_->performRpc(destination_identity, method, payload, response_timeout); @@ -77,8 +77,8 @@ void RpcController::unregisterRpcMethod(const std::string &method_name) { // Built-in outgoing convenience (track control) // --------------------------------------------------------------- -void RpcController::requestRemoteTrackMute(const std::string &destination_identity, - const std::string &track_name) { +void RpcController::requestRemoteTrackMute( + const std::string &destination_identity, const std::string &track_name) { namespace tc = rpc::track_control; performRpc(destination_identity, tc::kMethod, tc::formatPayload(tc::kActionMute, track_name), std::nullopt); @@ -141,4 +141,4 @@ RpcController::handleTrackControlRpc(const livekit::RpcInvocationData &data) { return tc::kResponseOk; } -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/src/rpc_controller.h b/src/session_manager/rpc_controller.h similarity index 95% rename from bridge/src/rpc_controller.h rename to src/session_manager/rpc_controller.h index 97d096d..cd656b5 100644 --- a/bridge/src/rpc_controller.h +++ b/src/session_manager/rpc_controller.h @@ -15,12 +15,13 @@ */ /// @file rpc_controller.h -/// @brief Internal RPC controller that owns all RPC concerns for the bridge. +/// @brief Internal RPC controller that owns all RPC concerns for the +/// SessionManager. #pragma once #include "livekit/local_participant.h" -#include "livekit_bridge/rpc_constants.h" +#include "livekit/session_manager/rpc_constants.h" #include #include @@ -28,17 +29,15 @@ #include namespace livekit { -struct RpcInvocationData; -} // namespace livekit -namespace livekit_bridge { +struct RpcInvocationData; namespace test { class RpcControllerTest; } // namespace test /** - * Owns all RPC concerns for the LiveKitBridge: built-in handler registration + * Owns all RPC concerns for the SessionManager: built-in handler registration * and dispatch, user-registered custom handlers, and outgoing RPC calls. * * The controller is bound to a LocalParticipant via enable() and unbound via @@ -49,7 +48,7 @@ class RpcControllerTest; * enable() and unregistered on disable(). User-registered handlers are * forwarded directly to the underlying LocalParticipant. * - * Not part of the public API; lives in bridge/src/. + * Not part of the public API; lives in session_manager/. */ class RpcController { public: @@ -142,4 +141,4 @@ class RpcController { livekit::LocalParticipant *lp_; }; -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/src/livekit_bridge.cpp b/src/session_manager/session_manager.cpp similarity index 78% rename from bridge/src/livekit_bridge.cpp rename to src/session_manager/session_manager.cpp index b6c495c..7389564 100644 --- a/bridge/src/livekit_bridge.cpp +++ b/src/session_manager/session_manager.cpp @@ -14,13 +14,13 @@ * limitations under the License. */ -/// @file livekit_bridge.cpp -/// @brief Implementation of the LiveKitBridge high-level API. +/// @file session_manager.cpp +/// @brief Implementation of the SessionManager high-level API. -#include "livekit_bridge/livekit_bridge.h" -#include "bridge_room_delegate.h" -#include "livekit_bridge/rpc_constants.h" +#include "livekit/session_manager/session_manager.h" +#include "livekit/session_manager/rpc_constants.h" #include "rpc_controller.h" +#include "session_manager_room_delegate.h" #include "livekit/audio_frame.h" #include "livekit/audio_source.h" @@ -39,20 +39,20 @@ #include #include -#include "lk_log.h" +#include "livekit/lk_log.h" -namespace livekit_bridge { +namespace livekit { // --------------------------------------------------------------- // CallbackKey // --------------------------------------------------------------- -bool LiveKitBridge::CallbackKey::operator==(const CallbackKey &o) const { +bool SessionManager::CallbackKey::operator==(const CallbackKey &o) const { return identity == o.identity && source == o.source; } std::size_t -LiveKitBridge::CallbackKeyHash::operator()(const CallbackKey &k) const { +SessionManager::CallbackKeyHash::operator()(const CallbackKey &k) const { std::size_t h1 = std::hash{}(k.identity); std::size_t h2 = std::hash{}(static_cast(k.source)); return h1 ^ (h2 << 1); @@ -62,7 +62,7 @@ LiveKitBridge::CallbackKeyHash::operator()(const CallbackKey &k) const { // Construction / Destruction // --------------------------------------------------------------- -LiveKitBridge::LiveKitBridge() +SessionManager::SessionManager() : connected_(false), connecting_(false), sdk_initialized_(false), rpc_controller_(std::make_unique( [this](const rpc::track_control::Action &action, @@ -70,14 +70,14 @@ LiveKitBridge::LiveKitBridge() executeTrackAction(action, track_name); })) {} -LiveKitBridge::~LiveKitBridge() { disconnect(); } +SessionManager::~SessionManager() { disconnect(); } // --------------------------------------------------------------- // Connection // --------------------------------------------------------------- -bool LiveKitBridge::connect(const std::string &url, const std::string &token, - const livekit::RoomOptions &options) { +bool SessionManager::connect(const std::string &url, const std::string &token, + const livekit::RoomOptions &options) { // ---- Phase 1: quick check under lock ---- { std::lock_guard lock(mutex_); @@ -118,7 +118,7 @@ bool LiveKitBridge::connect(const std::string &url, const std::string &token, // onTrackSubscribed events are delivered only after // room_/delegate_/connected_ are all in a consistent state. - auto delegate = std::make_unique(*this); + auto delegate = std::make_unique(*this); assert(delegate != nullptr); room->setDelegate(delegate.get()); livekit::LocalParticipant *lp = nullptr; @@ -137,7 +137,7 @@ bool LiveKitBridge::connect(const std::string &url, const std::string &token, return true; } -void LiveKitBridge::disconnect() { +void SessionManager::disconnect() { // Disable the RPC controller before tearing down the room. This unregisters // built-in handlers while the LocalParticipant is still alive. if (rpc_controller_ && rpc_controller_->isEnabled()) { @@ -152,8 +152,9 @@ void LiveKitBridge::disconnect() { std::lock_guard lock(mutex_); if (!connected_) { - LK_LOG_WARN("Attempting to disconnect an already disconnected bridge. " - "Things may not disconnect properly."); + LK_LOG_WARN( + "Attempting to disconnect an already disconnected SessionManager. " + "Things may not disconnect properly."); } connected_ = false; @@ -214,18 +215,24 @@ void LiveKitBridge::disconnect() { } } -bool LiveKitBridge::isConnected() const { +bool SessionManager::isConnected() const { std::lock_guard lock(mutex_); return connected_; } +livekit::Room *SessionManager::getRoom() const { + std::lock_guard lock(mutex_); + return (connected_ && room_) ? room_.get() : nullptr; +} + // --------------------------------------------------------------- // Track creation (publishing) // --------------------------------------------------------------- -std::shared_ptr -LiveKitBridge::createAudioTrack(const std::string &name, int sample_rate, - int num_channels, livekit::TrackSource source) { +std::shared_ptr +SessionManager::createAudioTrack(const std::string &name, int sample_rate, + int num_channels, + livekit::TrackSource source) { std::lock_guard lock(mutex_); if (!connected_ || !room_) { @@ -251,16 +258,17 @@ LiveKitBridge::createAudioTrack(const std::string &name, int sample_rate, auto publication = lp->publishTrack(track, opts); // 4. Wrap in handle and retain a reference - auto bridge_track = std::shared_ptr(new BridgeAudioTrack( - name, sample_rate, num_channels, std::move(audio_source), - std::move(track), std::move(publication), lp)); - published_audio_tracks_.emplace_back(bridge_track); - return bridge_track; + auto managed = + std::shared_ptr(new ManagedLocalAudioTrack( + name, sample_rate, num_channels, std::move(audio_source), + std::move(track), std::move(publication), lp)); + published_audio_tracks_.emplace_back(managed); + return managed; } -std::shared_ptr -LiveKitBridge::createVideoTrack(const std::string &name, int width, int height, - livekit::TrackSource source) { +std::shared_ptr +SessionManager::createVideoTrack(const std::string &name, int width, int height, + livekit::TrackSource source) { std::lock_guard lock(mutex_); if (!connected_ || !room_) { @@ -285,18 +293,18 @@ LiveKitBridge::createVideoTrack(const std::string &name, int width, int height, auto publication = lp->publishTrack(track, opts); // 4. Wrap in handle and retain a reference - auto bridge_track = std::shared_ptr( - new BridgeVideoTrack(name, width, height, std::move(video_source), - std::move(track), std::move(publication), lp)); - published_video_tracks_.emplace_back(bridge_track); - return bridge_track; + auto managed = std::shared_ptr( + new ManagedLocalVideoTrack(name, width, height, std::move(video_source), + std::move(track), std::move(publication), lp)); + published_video_tracks_.emplace_back(managed); + return managed; } // --------------------------------------------------------------- // Incoming frame callbacks // --------------------------------------------------------------- -void LiveKitBridge::setOnAudioFrameCallback( +void SessionManager::setOnAudioFrameCallback( const std::string &participant_identity, livekit::TrackSource source, AudioFrameCallback callback) { std::lock_guard lock(mutex_); @@ -316,7 +324,7 @@ void LiveKitBridge::setOnAudioFrameCallback( // be picked up. } -void LiveKitBridge::setOnVideoFrameCallback( +void SessionManager::setOnVideoFrameCallback( const std::string &participant_identity, livekit::TrackSource source, VideoFrameCallback callback) { std::lock_guard lock(mutex_); @@ -325,7 +333,7 @@ void LiveKitBridge::setOnVideoFrameCallback( video_callbacks_[key] = std::move(callback); } -void LiveKitBridge::clearOnAudioFrameCallback( +void SessionManager::clearOnAudioFrameCallback( const std::string &participant_identity, livekit::TrackSource source) { std::thread thread_to_join; { @@ -339,7 +347,7 @@ void LiveKitBridge::clearOnAudioFrameCallback( } } -void LiveKitBridge::clearOnVideoFrameCallback( +void SessionManager::clearOnVideoFrameCallback( const std::string &participant_identity, livekit::TrackSource source) { std::thread thread_to_join; { @@ -357,10 +365,9 @@ void LiveKitBridge::clearOnVideoFrameCallback( // RPC (delegates to RpcController) // --------------------------------------------------------------- -std::optional -LiveKitBridge::performRpc(const std::string &destination_identity, - const std::string &method, const std::string &payload, - const std::optional &response_timeout) { +std::optional SessionManager::performRpc( + const std::string &destination_identity, const std::string &method, + const std::string &payload, const std::optional &response_timeout) { if (!isConnected()) { return std::nullopt; @@ -370,18 +377,18 @@ LiveKitBridge::performRpc(const std::string &destination_identity, return rpc_controller_->performRpc(destination_identity, method, payload, response_timeout); } catch (const std::exception &e) { - std::cerr << "[LiveKitBridge] Exception: " << e.what() << "\n"; + std::cerr << "[SessionManager] Exception: " << e.what() << "\n"; return std::nullopt; } catch (const std::runtime_error &e) { - std::cerr << "[LiveKitBridge] Runtime error: " << e.what() << "\n"; + std::cerr << "[SessionManager] Runtime error: " << e.what() << "\n"; return std::nullopt; } catch (const livekit::RpcError &e) { - std::cerr << "[LiveKitBridge] RPC error: " << e.what() << "\n"; + std::cerr << "[SessionManager] RPC error: " << e.what() << "\n"; return std::nullopt; } } -bool LiveKitBridge::registerRpcMethod( +bool SessionManager::registerRpcMethod( const std::string &method_name, livekit::LocalParticipant::RpcHandler handler) { @@ -392,18 +399,18 @@ bool LiveKitBridge::registerRpcMethod( rpc_controller_->registerRpcMethod(method_name, std::move(handler)); return true; } catch (const std::exception &e) { - std::cerr << "[LiveKitBridge] Exception: " << e.what() << "\n"; + std::cerr << "[SessionManager] Exception: " << e.what() << "\n"; return false; } catch (const std::runtime_error &e) { - std::cerr << "[LiveKitBridge] Runtime error: " << e.what() << "\n"; + std::cerr << "[SessionManager] Runtime error: " << e.what() << "\n"; return false; } catch (const livekit::RpcError &e) { - std::cerr << "[LiveKitBridge] RPC error: " << e.what() << "\n"; + std::cerr << "[SessionManager] RPC error: " << e.what() << "\n"; return false; } } -bool LiveKitBridge::unregisterRpcMethod(const std::string &method_name) { +bool SessionManager::unregisterRpcMethod(const std::string &method_name) { if (!isConnected()) { return false; } @@ -411,18 +418,18 @@ bool LiveKitBridge::unregisterRpcMethod(const std::string &method_name) { rpc_controller_->unregisterRpcMethod(method_name); return true; } catch (const std::exception &e) { - std::cerr << "[LiveKitBridge] Exception: " << e.what() << "\n"; + std::cerr << "[SessionManager] Exception: " << e.what() << "\n"; return false; } catch (const std::runtime_error &e) { - std::cerr << "[LiveKitBridge] Runtime error: " << e.what() << "\n"; + std::cerr << "[SessionManager] Runtime error: " << e.what() << "\n"; return false; } catch (const livekit::RpcError &e) { - std::cerr << "[LiveKitBridge] RPC error: " << e.what() << "\n"; + std::cerr << "[SessionManager] RPC error: " << e.what() << "\n"; return false; } } -bool LiveKitBridge::requestRemoteTrackMute( +bool SessionManager::requestRemoteTrackMute( const std::string &destination_identity, const std::string &track_name) { if (!isConnected()) { return false; @@ -431,18 +438,18 @@ bool LiveKitBridge::requestRemoteTrackMute( rpc_controller_->requestRemoteTrackMute(destination_identity, track_name); return true; } catch (const std::exception &e) { - std::cerr << "[LiveKitBridge] Exception: " << e.what() << "\n"; + std::cerr << "[SessionManager] Exception: " << e.what() << "\n"; return false; } catch (const std::runtime_error &e) { - std::cerr << "[LiveKitBridge] Runtime error: " << e.what() << "\n"; + std::cerr << "[SessionManager] Runtime error: " << e.what() << "\n"; return false; } catch (const livekit::RpcError &e) { - std::cerr << "[LiveKitBridge] RPC error: " << e.what() << "\n"; + std::cerr << "[SessionManager] RPC error: " << e.what() << "\n"; return false; } } -bool LiveKitBridge::requestRemoteTrackUnmute( +bool SessionManager::requestRemoteTrackUnmute( const std::string &destination_identity, const std::string &track_name) { if (!isConnected()) { return false; @@ -451,13 +458,13 @@ bool LiveKitBridge::requestRemoteTrackUnmute( rpc_controller_->requestRemoteTrackUnmute(destination_identity, track_name); return true; } catch (const std::exception &e) { - std::cerr << "[LiveKitBridge] Exception: " << e.what() << "\n"; + std::cerr << "[SessionManager] Exception: " << e.what() << "\n"; return false; } catch (const std::runtime_error &e) { - std::cerr << "[LiveKitBridge] Runtime error: " << e.what() << "\n"; + std::cerr << "[SessionManager] Runtime error: " << e.what() << "\n"; return false; } catch (const livekit::RpcError &e) { - std::cerr << "[LiveKitBridge] RPC error: " << e.what() << "\n"; + std::cerr << "[SessionManager] RPC error: " << e.what() << "\n"; return false; } } @@ -466,8 +473,8 @@ bool LiveKitBridge::requestRemoteTrackUnmute( // Track action callback for RpcController // --------------------------------------------------------------- -void LiveKitBridge::executeTrackAction(const rpc::track_control::Action &action, - const std::string &track_name) { +void SessionManager::executeTrackAction( + const rpc::track_control::Action &action, const std::string &track_name) { std::lock_guard lock(mutex_); for (auto &track : published_audio_tracks_) { @@ -500,7 +507,7 @@ void LiveKitBridge::executeTrackAction(const rpc::track_control::Action &action, // Internal: track subscribe / unsubscribe from delegate // --------------------------------------------------------------- -void LiveKitBridge::onTrackSubscribed( +void SessionManager::onTrackSubscribed( const std::string &participant_identity, livekit::TrackSource source, const std::shared_ptr &track) { std::thread old_thread; @@ -530,8 +537,8 @@ void LiveKitBridge::onTrackSubscribed( } } -void LiveKitBridge::onTrackUnsubscribed(const std::string &participant_identity, - livekit::TrackSource source) { +void SessionManager::onTrackUnsubscribed( + const std::string &participant_identity, livekit::TrackSource source) { std::thread thread_to_join; { std::lock_guard lock(mutex_); @@ -547,7 +554,7 @@ void LiveKitBridge::onTrackUnsubscribed(const std::string &participant_identity, // Internal: reader thread management // --------------------------------------------------------------- -std::thread LiveKitBridge::extractReaderThread(const CallbackKey &key) { +std::thread SessionManager::extractReaderThread(const CallbackKey &key) { // Caller must hold mutex_. // Closes the stream and extracts the thread for the caller to join. auto it = active_readers_.find(key); @@ -571,9 +578,9 @@ std::thread LiveKitBridge::extractReaderThread(const CallbackKey &key) { } std::thread -LiveKitBridge::startAudioReader(const CallbackKey &key, - const std::shared_ptr &track, - AudioFrameCallback cb) { +SessionManager::startAudioReader(const CallbackKey &key, + const std::shared_ptr &track, + AudioFrameCallback cb) { // Caller must hold mutex_. // Returns the old reader thread (if any) for the caller to join outside // the lock. @@ -611,9 +618,9 @@ LiveKitBridge::startAudioReader(const CallbackKey &key, } std::thread -LiveKitBridge::startVideoReader(const CallbackKey &key, - const std::shared_ptr &track, - VideoFrameCallback cb) { +SessionManager::startVideoReader(const CallbackKey &key, + const std::shared_ptr &track, + VideoFrameCallback cb) { // Caller must hold mutex_. // Returns the old reader thread (if any) for the caller to join outside // the lock. @@ -651,4 +658,4 @@ LiveKitBridge::startVideoReader(const CallbackKey &key, return old_thread; } -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/src/bridge_room_delegate.cpp b/src/session_manager/session_manager_room_delegate.cpp similarity index 72% rename from bridge/src/bridge_room_delegate.cpp rename to src/session_manager/session_manager_room_delegate.cpp index 118627c..e82743c 100644 --- a/bridge/src/bridge_room_delegate.cpp +++ b/src/session_manager/session_manager_room_delegate.cpp @@ -14,19 +14,19 @@ * limitations under the License. */ -/// @file bridge_room_delegate.cpp -/// @brief Implementation of BridgeRoomDelegate event forwarding. +/// @file session_manager_room_delegate.cpp +/// @brief Implementation of SessionManagerRoomDelegate event forwarding. -#include "bridge_room_delegate.h" +#include "session_manager_room_delegate.h" #include "livekit/remote_participant.h" #include "livekit/remote_track_publication.h" +#include "livekit/session_manager/session_manager.h" #include "livekit/track.h" -#include "livekit_bridge/livekit_bridge.h" -namespace livekit_bridge { +namespace livekit { -void BridgeRoomDelegate::onTrackSubscribed( +void SessionManagerRoomDelegate::onTrackSubscribed( livekit::Room & /*room*/, const livekit::TrackSubscribedEvent &ev) { if (!ev.track || !ev.participant || !ev.publication) { return; @@ -35,10 +35,10 @@ void BridgeRoomDelegate::onTrackSubscribed( const std::string identity = ev.participant->identity(); const livekit::TrackSource source = ev.publication->source(); - bridge_.onTrackSubscribed(identity, source, ev.track); + manager_.onTrackSubscribed(identity, source, ev.track); } -void BridgeRoomDelegate::onTrackUnsubscribed( +void SessionManagerRoomDelegate::onTrackUnsubscribed( livekit::Room & /*room*/, const livekit::TrackUnsubscribedEvent &ev) { if (!ev.participant || !ev.publication) { return; @@ -47,7 +47,7 @@ void BridgeRoomDelegate::onTrackUnsubscribed( const std::string identity = ev.participant->identity(); const livekit::TrackSource source = ev.publication->source(); - bridge_.onTrackUnsubscribed(identity, source); + manager_.onTrackUnsubscribed(identity, source); } -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/src/bridge_room_delegate.h b/src/session_manager/session_manager_room_delegate.h similarity index 65% rename from bridge/src/bridge_room_delegate.h rename to src/session_manager/session_manager_room_delegate.h index 8f5dd31..d4c6851 100644 --- a/bridge/src/bridge_room_delegate.h +++ b/src/session_manager/session_manager_room_delegate.h @@ -14,38 +14,39 @@ * limitations under the License. */ -/// @file bridge_room_delegate.h -/// @brief Internal RoomDelegate forwarding SDK events to LiveKitBridge. +/// @file session_manager_room_delegate.h +/// @brief Internal RoomDelegate forwarding SDK events to SessionManager. #pragma once #include "livekit/room_delegate.h" -namespace livekit_bridge { +namespace livekit { -class LiveKitBridge; +class SessionManager; /** - * Internal RoomDelegate that forwards SDK room events to the LiveKitBridge. + * Internal RoomDelegate that forwards SDK room events to the SessionManager. * * Handles track subscribe/unsubscribe lifecycle. Not part of the public API, * so its in src/ instead of include/. */ -class BridgeRoomDelegate : public livekit::RoomDelegate { +class SessionManagerRoomDelegate : public livekit::RoomDelegate { public: - explicit BridgeRoomDelegate(LiveKitBridge &bridge) : bridge_(bridge) {} + explicit SessionManagerRoomDelegate(SessionManager &manager) + : manager_(manager) {} - /// Forwards a track-subscribed event to LiveKitBridge::onTrackSubscribed(). + /// Forwards a track-subscribed event to SessionManager::onTrackSubscribed(). void onTrackSubscribed(livekit::Room &room, const livekit::TrackSubscribedEvent &ev) override; /// Forwards a track-unsubscribed event to - /// LiveKitBridge::onTrackUnsubscribed(). + /// SessionManager::onTrackUnsubscribed(). void onTrackUnsubscribed(livekit::Room &room, const livekit::TrackUnsubscribedEvent &ev) override; private: - LiveKitBridge &bridge_; + SessionManager &manager_; }; -} // namespace livekit_bridge +} // namespace livekit diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index af6aed1..6ff68ef 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -41,7 +41,6 @@ if(INTEGRATION_TEST_SOURCES) target_link_libraries(livekit_integration_tests PRIVATE livekit - spdlog::spdlog GTest::gtest_main GTest::gmock ) diff --git a/src/tests/integration/test_logging.cpp b/src/tests/integration/test_logging.cpp index 88e1616..5ccb0fa 100644 --- a/src/tests/integration/test_logging.cpp +++ b/src/tests/integration/test_logging.cpp @@ -17,7 +17,7 @@ #include #include -#include "lk_log.h" +#include "livekit/lk_log.h" #include #include diff --git a/bridge/tests/CMakeLists.txt b/src/tests/session_manager/CMakeLists.txt similarity index 55% rename from bridge/tests/CMakeLists.txt rename to src/tests/session_manager/CMakeLists.txt index c42274b..e78a1ad 100644 --- a/bridge/tests/CMakeLists.txt +++ b/src/tests/session_manager/CMakeLists.txt @@ -25,75 +25,66 @@ enable_testing() include(GoogleTest) # ============================================================================ -# Bridge Unit Tests +# SessionManager Unit Tests # ============================================================================ -file(GLOB BRIDGE_TEST_SOURCES +file(GLOB SESSION_MANAGER_TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" ) -if(BRIDGE_TEST_SOURCES) - add_executable(livekit_bridge_tests - ${BRIDGE_TEST_SOURCES} +if(SESSION_MANAGER_TEST_SOURCES) + add_executable(session_manager_tests + ${SESSION_MANAGER_TEST_SOURCES} ) - target_include_directories(livekit_bridge_tests + target_include_directories(session_manager_tests PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/../src + ${LIVEKIT_ROOT_DIR}/src/session_manager/ ) - target_link_libraries(livekit_bridge_tests + target_link_libraries(session_manager_tests PRIVATE - livekit_bridge + livekit GTest::gtest_main ) # Copy shared libraries to test executable directory if(WIN32) - add_custom_command(TARGET livekit_bridge_tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ + add_custom_command(TARGET session_manager_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $ + $ COMMAND ${CMAKE_COMMAND} -E copy_if_different "$/livekit_ffi.dll" - $ - COMMENT "Copying DLLs to bridge test directory" + $ + COMMENT "Copying DLLs to session_manager test directory" ) elseif(APPLE) - add_custom_command(TARGET livekit_bridge_tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ + add_custom_command(TARGET session_manager_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $ + $ COMMAND ${CMAKE_COMMAND} -E copy_if_different "$/liblivekit_ffi.dylib" - $ - COMMENT "Copying dylibs to bridge test directory" + $ + COMMENT "Copying dylibs to session_manager test directory" ) else() - add_custom_command(TARGET livekit_bridge_tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ + add_custom_command(TARGET session_manager_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - $ + $ COMMAND ${CMAKE_COMMAND} -E copy_if_different "$/liblivekit_ffi.so" - $ - COMMENT "Copying shared libraries to bridge test directory" + $ + COMMENT "Copying shared libraries to session_manager test directory" ) endif() # Register tests with CTest - gtest_discover_tests(livekit_bridge_tests + gtest_discover_tests(session_manager_tests WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} PROPERTIES - LABELS "bridge_unit" + LABELS "session_manager_unit" ) endif() diff --git a/bridge/tests/integration/test_bridge_rpc_roundtrip.cpp b/src/tests/session_manager/integration/test_session_manager_rpc_roundtrip.cpp similarity index 84% rename from bridge/tests/integration/test_bridge_rpc_roundtrip.cpp rename to src/tests/session_manager/integration/test_session_manager_rpc_roundtrip.cpp index e649450..9b3718e 100644 --- a/bridge/tests/integration/test_bridge_rpc_roundtrip.cpp +++ b/src/tests/session_manager/integration/test_session_manager_rpc_roundtrip.cpp @@ -14,27 +14,26 @@ * limitations under the License. */ -#include "../common/bridge_test_common.h" #include -namespace livekit_bridge { +namespace livekit { namespace test { -class BridgeRpcRoundtripTest : public BridgeTestBase {}; +class SessionManagerRpcRoundtripTest : public SessionManagerTestBase {}; // --------------------------------------------------------------------------- -// Test 1: Basic RPC round-trip through the bridge. +// Test 1: Basic RPC round-trip through the SessionManager. // // Receiver registers an "echo" handler, caller performs an RPC call, and the // response is verified. // --------------------------------------------------------------------------- -TEST_F(BridgeRpcRoundtripTest, BasicRpcRoundTrip) { +TEST_F(SessionManagerRpcRoundtripTest, BasicRpcRoundTrip) { skipIfNotConfigured(); - std::cout << "\n=== Bridge RPC Round-Trip Test ===" << std::endl; + std::cout << "\n=== SessionManager RPC Round-Trip Test ===" << std::endl; - LiveKitBridge caller; - LiveKitBridge receiver; + SessionManager caller; + SessionManager receiver; ASSERT_TRUE(connectPair(caller, receiver)); @@ -56,7 +55,7 @@ TEST_F(BridgeRpcRoundtripTest, BasicRpcRoundTrip) { std::cout << "RPC handler registered, performing call..." << std::endl; - std::string test_payload = "hello from bridge"; + std::string test_payload = "hello from SessionManager"; std::string response = caller.performRpc(receiver_identity, "echo", test_payload, 10.0); @@ -83,13 +82,14 @@ TEST_F(BridgeRpcRoundtripTest, BasicRpcRoundTrip) { // The handler throws an RpcError with a custom code and message. The caller // should catch the same error code, message, and data. // --------------------------------------------------------------------------- -TEST_F(BridgeRpcRoundtripTest, RpcErrorPropagation) { +TEST_F(SessionManagerRpcRoundtripTest, RpcErrorPropagation) { skipIfNotConfigured(); - std::cout << "\n=== Bridge RPC Error Propagation Test ===" << std::endl; + std::cout << "\n=== SessionManager RPC Error Propagation Test ===" + << std::endl; - LiveKitBridge caller; - LiveKitBridge receiver; + SessionManager caller; + SessionManager receiver; ASSERT_TRUE(connectPair(caller, receiver)); @@ -124,13 +124,14 @@ TEST_F(BridgeRpcRoundtripTest, RpcErrorPropagation) { // --------------------------------------------------------------------------- // Test 3: Calling an unregistered method returns UNSUPPORTED_METHOD. // --------------------------------------------------------------------------- -TEST_F(BridgeRpcRoundtripTest, UnregisteredMethod) { +TEST_F(SessionManagerRpcRoundtripTest, UnregisteredMethod) { skipIfNotConfigured(); - std::cout << "\n=== Bridge RPC Unsupported Method Test ===" << std::endl; + std::cout << "\n=== SessionManager RPC Unsupported Method Test ===" + << std::endl; - LiveKitBridge caller; - LiveKitBridge receiver; + SessionManager caller; + SessionManager receiver; ASSERT_TRUE(connectPair(caller, receiver)); @@ -154,7 +155,7 @@ TEST_F(BridgeRpcRoundtripTest, UnregisteredMethod) { // Remote Track Control Tests // =========================================================================== -class BridgeRemoteTrackControlTest : public BridgeTestBase {}; +class SessionManagerRemoteTrackControlTest : public SessionManagerTestBase {}; // --------------------------------------------------------------------------- // Test 4: Remote mute of an audio track. @@ -162,13 +163,14 @@ class BridgeRemoteTrackControlTest : public BridgeTestBase {}; // Publisher creates an audio track, enables remote track control. Controller // requests mute, then unmute. // --------------------------------------------------------------------------- -TEST_F(BridgeRemoteTrackControlTest, RemoteMuteAudioTrack) { +TEST_F(SessionManagerRemoteTrackControlTest, RemoteMuteAudioTrack) { skipIfNotConfigured(); - std::cout << "\n=== Bridge Remote Mute Audio Track Test ===" << std::endl; + std::cout << "\n=== SessionManager Remote Mute Audio Track Test ===" + << std::endl; - LiveKitBridge publisher; - LiveKitBridge controller; + SessionManager publisher; + SessionManager controller; ASSERT_TRUE(connectPair(controller, publisher)); @@ -201,13 +203,14 @@ TEST_F(BridgeRemoteTrackControlTest, RemoteMuteAudioTrack) { // --------------------------------------------------------------------------- // Test 5: Remote mute of a video track. // --------------------------------------------------------------------------- -TEST_F(BridgeRemoteTrackControlTest, RemoteMuteVideoTrack) { +TEST_F(SessionManagerRemoteTrackControlTest, RemoteMuteVideoTrack) { skipIfNotConfigured(); - std::cout << "\n=== Bridge Remote Mute Video Track Test ===" << std::endl; + std::cout << "\n=== SessionManager Remote Mute Video Track Test ===" + << std::endl; - LiveKitBridge publisher; - LiveKitBridge controller; + SessionManager publisher; + SessionManager controller; ASSERT_TRUE(connectPair(controller, publisher)); @@ -237,14 +240,14 @@ TEST_F(BridgeRemoteTrackControlTest, RemoteMuteVideoTrack) { // --------------------------------------------------------------------------- // Test 7: Remote mute on a nonexistent track returns an error. // --------------------------------------------------------------------------- -TEST_F(BridgeRemoteTrackControlTest, RemoteMuteNonexistentTrack) { +TEST_F(SessionManagerRemoteTrackControlTest, RemoteMuteNonexistentTrack) { skipIfNotConfigured(); - std::cout << "\n=== Bridge Remote Mute Nonexistent Track Test ===" + std::cout << "\n=== SessionManager Remote Mute Nonexistent Track Test ===" << std::endl; - LiveKitBridge publisher; - LiveKitBridge controller; + SessionManager publisher; + SessionManager controller; ASSERT_TRUE(connectPair(controller, publisher)); @@ -268,4 +271,4 @@ TEST_F(BridgeRemoteTrackControlTest, RemoteMuteNonexistentTrack) { } } // namespace test -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/tests/test_callback_key.cpp b/src/tests/session_manager/test_callback_key.cpp similarity index 92% rename from bridge/tests/test_callback_key.cpp rename to src/tests/session_manager/test_callback_key.cpp index 29974f6..b3d7570 100644 --- a/bridge/tests/test_callback_key.cpp +++ b/src/tests/session_manager/test_callback_key.cpp @@ -15,24 +15,24 @@ */ /// @file test_callback_key.cpp -/// @brief Unit tests for LiveKitBridge::CallbackKey hash and equality. +/// @brief Unit tests for SessionManager::CallbackKey hash and equality. #include -#include +#include #include #include -namespace livekit_bridge { +namespace livekit { namespace test { class CallbackKeyTest : public ::testing::Test { protected: - // Type aliases for convenience -- these are private types in LiveKitBridge, + // Type aliases for convenience -- these are private types in SessionManager, // accessible via the friend declaration. - using CallbackKey = LiveKitBridge::CallbackKey; - using CallbackKeyHash = LiveKitBridge::CallbackKeyHash; + using CallbackKey = SessionManager::CallbackKey; + using CallbackKeyHash = SessionManager::CallbackKeyHash; }; TEST_F(CallbackKeyTest, EqualKeysCompareEqual) { @@ -121,4 +121,4 @@ TEST_F(CallbackKeyTest, EmptyIdentityWorks) { } } // namespace test -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/tests/test_bridge_audio_track.cpp b/src/tests/session_manager/test_managed_local_audio_track.cpp similarity index 63% rename from bridge/tests/test_bridge_audio_track.cpp rename to src/tests/session_manager/test_managed_local_audio_track.cpp index 8e7274e..58bb9f1 100644 --- a/bridge/tests/test_bridge_audio_track.cpp +++ b/src/tests/session_manager/test_managed_local_audio_track.cpp @@ -14,38 +14,38 @@ * limitations under the License. */ -/// @file test_bridge_audio_track.cpp -/// @brief Unit tests for BridgeAudioTrack. +/// @file test_managed_local_audio_track.cpp +/// @brief Unit tests for ManagedLocalAudioTrack. #include -#include +#include #include #include #include -namespace livekit_bridge { +namespace livekit { namespace test { -class BridgeAudioTrackTest : public ::testing::Test { +class ManagedLocalAudioTrackTest : public ::testing::Test { protected: - /// Create a BridgeAudioTrack with null SDK objects for pure-logic testing. + /// Create a ManagedLocalAudioTrack with null SDK objects for pure-logic testing. /// The track is usable for accessor and state management tests but will /// crash if pushFrame / mute / unmute try to dereference SDK pointers /// on a non-released track. - static BridgeAudioTrack createNullTrack(const std::string &name = "mic", - int sample_rate = 48000, - int num_channels = 2) { - return BridgeAudioTrack(name, sample_rate, num_channels, - nullptr, // source - nullptr, // track - nullptr, // publication - nullptr // participant + static ManagedLocalAudioTrack createNullTrack(const std::string &name = "mic", + int sample_rate = 48000, + int num_channels = 2) { + return ManagedLocalAudioTrack(name, sample_rate, num_channels, + nullptr, // source + nullptr, // track + nullptr, // publication + nullptr // participant ); } }; -TEST_F(BridgeAudioTrackTest, AccessorsReturnConstructionValues) { +TEST_F(ManagedLocalAudioTrackTest, AccessorsReturnConstructionValues) { auto track = createNullTrack("test-mic", 16000, 1); EXPECT_EQ(track.name(), "test-mic") << "Name should match construction value"; @@ -53,14 +53,14 @@ TEST_F(BridgeAudioTrackTest, AccessorsReturnConstructionValues) { EXPECT_EQ(track.numChannels(), 1) << "Channel count should match"; } -TEST_F(BridgeAudioTrackTest, InitiallyNotReleased) { +TEST_F(ManagedLocalAudioTrackTest, InitiallyNotReleased) { auto track = createNullTrack(); EXPECT_FALSE(track.isReleased()) << "Track should not be released immediately after construction"; } -TEST_F(BridgeAudioTrackTest, ReleaseMarksTrackAsReleased) { +TEST_F(ManagedLocalAudioTrackTest, ReleaseMarksTrackAsReleased) { auto track = createNullTrack(); track.release(); @@ -69,7 +69,7 @@ TEST_F(BridgeAudioTrackTest, ReleaseMarksTrackAsReleased) { << "Track should be released after calling release()"; } -TEST_F(BridgeAudioTrackTest, DoubleReleaseIsIdempotent) { +TEST_F(ManagedLocalAudioTrackTest, DoubleReleaseIsIdempotent) { auto track = createNullTrack(); track.release(); @@ -78,7 +78,7 @@ TEST_F(BridgeAudioTrackTest, DoubleReleaseIsIdempotent) { EXPECT_TRUE(track.isReleased()); } -TEST_F(BridgeAudioTrackTest, PushFrameAfterReleaseReturnsFalse) { +TEST_F(ManagedLocalAudioTrackTest, PushFrameAfterReleaseReturnsFalse) { auto track = createNullTrack(); track.release(); @@ -88,7 +88,7 @@ TEST_F(BridgeAudioTrackTest, PushFrameAfterReleaseReturnsFalse) { << "pushFrame (vector) on a released track should return false"; } -TEST_F(BridgeAudioTrackTest, PushFrameRawPointerAfterReleaseReturnsFalse) { +TEST_F(ManagedLocalAudioTrackTest, PushFrameRawPointerAfterReleaseReturnsFalse) { auto track = createNullTrack(); track.release(); @@ -98,7 +98,7 @@ TEST_F(BridgeAudioTrackTest, PushFrameRawPointerAfterReleaseReturnsFalse) { << "pushFrame (raw pointer) on a released track should return false"; } -TEST_F(BridgeAudioTrackTest, MuteOnReleasedTrackDoesNotCrash) { +TEST_F(ManagedLocalAudioTrackTest, MuteOnReleasedTrackDoesNotCrash) { auto track = createNullTrack(); track.release(); @@ -106,7 +106,7 @@ TEST_F(BridgeAudioTrackTest, MuteOnReleasedTrackDoesNotCrash) { << "mute() on a released track should be a no-op"; } -TEST_F(BridgeAudioTrackTest, UnmuteOnReleasedTrackDoesNotCrash) { +TEST_F(ManagedLocalAudioTrackTest, UnmuteOnReleasedTrackDoesNotCrash) { auto track = createNullTrack(); track.release(); @@ -115,4 +115,4 @@ TEST_F(BridgeAudioTrackTest, UnmuteOnReleasedTrackDoesNotCrash) { } } // namespace test -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/tests/test_bridge_video_track.cpp b/src/tests/session_manager/test_managed_local_video_track.cpp similarity index 62% rename from bridge/tests/test_bridge_video_track.cpp rename to src/tests/session_manager/test_managed_local_video_track.cpp index 08517b0..39a9a1e 100644 --- a/bridge/tests/test_bridge_video_track.cpp +++ b/src/tests/session_manager/test_managed_local_video_track.cpp @@ -14,34 +14,34 @@ * limitations under the License. */ -/// @file test_bridge_video_track.cpp -/// @brief Unit tests for BridgeVideoTrack. +/// @file test_managed_local_video_track.cpp +/// @brief Unit tests for ManagedLocalVideoTrack. #include -#include +#include #include #include #include -namespace livekit_bridge { +namespace livekit { namespace test { -class BridgeVideoTrackTest : public ::testing::Test { +class ManagedLocalVideoTrackTest : public ::testing::Test { protected: - /// Create a BridgeVideoTrack with null SDK objects for pure-logic testing. - static BridgeVideoTrack createNullTrack(const std::string &name = "cam", - int width = 1280, int height = 720) { - return BridgeVideoTrack(name, width, height, - nullptr, // source - nullptr, // track - nullptr, // publication - nullptr // participant + /// Create a ManagedLocalVideoTrack with null SDK objects for pure-logic testing. + static ManagedLocalVideoTrack createNullTrack(const std::string &name = "cam", + int width = 1280, int height = 720) { + return ManagedLocalVideoTrack(name, width, height, + nullptr, // source + nullptr, // track + nullptr, // publication + nullptr // participant ); } }; -TEST_F(BridgeVideoTrackTest, AccessorsReturnConstructionValues) { +TEST_F(ManagedLocalVideoTrackTest, AccessorsReturnConstructionValues) { auto track = createNullTrack("test-cam", 640, 480); EXPECT_EQ(track.name(), "test-cam") << "Name should match construction value"; @@ -49,14 +49,14 @@ TEST_F(BridgeVideoTrackTest, AccessorsReturnConstructionValues) { EXPECT_EQ(track.height(), 480) << "Height should match"; } -TEST_F(BridgeVideoTrackTest, InitiallyNotReleased) { +TEST_F(ManagedLocalVideoTrackTest, InitiallyNotReleased) { auto track = createNullTrack(); EXPECT_FALSE(track.isReleased()) << "Track should not be released immediately after construction"; } -TEST_F(BridgeVideoTrackTest, ReleaseMarksTrackAsReleased) { +TEST_F(ManagedLocalVideoTrackTest, ReleaseMarksTrackAsReleased) { auto track = createNullTrack(); track.release(); @@ -65,7 +65,7 @@ TEST_F(BridgeVideoTrackTest, ReleaseMarksTrackAsReleased) { << "Track should be released after calling release()"; } -TEST_F(BridgeVideoTrackTest, DoubleReleaseIsIdempotent) { +TEST_F(ManagedLocalVideoTrackTest, DoubleReleaseIsIdempotent) { auto track = createNullTrack(); track.release(); @@ -74,7 +74,7 @@ TEST_F(BridgeVideoTrackTest, DoubleReleaseIsIdempotent) { EXPECT_TRUE(track.isReleased()); } -TEST_F(BridgeVideoTrackTest, PushFrameAfterReleaseReturnsFalse) { +TEST_F(ManagedLocalVideoTrackTest, PushFrameAfterReleaseReturnsFalse) { auto track = createNullTrack(); track.release(); @@ -84,7 +84,7 @@ TEST_F(BridgeVideoTrackTest, PushFrameAfterReleaseReturnsFalse) { << "pushFrame (vector) on a released track should return false"; } -TEST_F(BridgeVideoTrackTest, PushFrameRawPointerAfterReleaseReturnsFalse) { +TEST_F(ManagedLocalVideoTrackTest, PushFrameRawPointerAfterReleaseReturnsFalse) { auto track = createNullTrack(); track.release(); @@ -94,7 +94,7 @@ TEST_F(BridgeVideoTrackTest, PushFrameRawPointerAfterReleaseReturnsFalse) { << "pushFrame (raw pointer) on a released track should return false"; } -TEST_F(BridgeVideoTrackTest, MuteOnReleasedTrackDoesNotCrash) { +TEST_F(ManagedLocalVideoTrackTest, MuteOnReleasedTrackDoesNotCrash) { auto track = createNullTrack(); track.release(); @@ -102,7 +102,7 @@ TEST_F(BridgeVideoTrackTest, MuteOnReleasedTrackDoesNotCrash) { << "mute() on a released track should be a no-op"; } -TEST_F(BridgeVideoTrackTest, UnmuteOnReleasedTrackDoesNotCrash) { +TEST_F(ManagedLocalVideoTrackTest, UnmuteOnReleasedTrackDoesNotCrash) { auto track = createNullTrack(); track.release(); @@ -111,4 +111,4 @@ TEST_F(BridgeVideoTrackTest, UnmuteOnReleasedTrackDoesNotCrash) { } } // namespace test -} // namespace livekit_bridge +} // namespace livekit diff --git a/bridge/tests/test_rpc_controller.cpp b/src/tests/session_manager/test_rpc_controller.cpp similarity index 97% rename from bridge/tests/test_rpc_controller.cpp rename to src/tests/session_manager/test_rpc_controller.cpp index be2d035..a2f0a6f 100644 --- a/bridge/tests/test_rpc_controller.cpp +++ b/src/tests/session_manager/test_rpc_controller.cpp @@ -19,7 +19,7 @@ #include -#include "livekit_bridge/rpc_constants.h" +#include "livekit/session_manager/rpc_constants.h" #include "rpc_controller.h" #include "livekit/local_participant.h" @@ -28,7 +28,7 @@ #include #include -namespace livekit_bridge { +namespace livekit { namespace test { // Records (action, track_name) pairs passed to the TrackActionFn callback. @@ -45,9 +45,9 @@ class RpcControllerTest : public ::testing::Test { namespace tc = rpc::track_control; return std::make_unique( [this](const tc::Action &action, const std::string &track_name) { - const char *action_str = - (action == tc::Action::kActionMute) ? tc::kActionMute - : tc::kActionUnmute; + const char *action_str = (action == tc::Action::kActionMute) + ? tc::kActionMute + : tc::kActionUnmute; recorded_actions_.push_back({action_str, track_name}); }); } @@ -270,4 +270,4 @@ TEST_F(RpcControllerTest, FormatPayloadRoundTrip) { } } // namespace test -} // namespace livekit_bridge +} // namespace livekit diff --git a/src/tests/session_manager/test_session_manager.cpp b/src/tests/session_manager/test_session_manager.cpp new file mode 100644 index 0000000..4cbafb6 --- /dev/null +++ b/src/tests/session_manager/test_session_manager.cpp @@ -0,0 +1,187 @@ +/* + * Copyright 2025 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 test_session_manager.cpp +/// @brief Unit tests for SessionManager. + +#include +#include + +#include + +#include + +namespace livekit { +namespace test { + +class SessionManagerTest : public ::testing::Test { +protected: + // No SetUp/TearDown needed -- we test the SessionManager without initializing + // the LiveKit SDK, since we only exercise pre-connection behaviour. +}; + +// ============================================================================ +// Initial state +// ============================================================================ + +TEST_F(SessionManagerTest, InitiallyNotConnected) { + SessionManager session_manager; + + EXPECT_FALSE(session_manager.isConnected()) + << "SessionManager should not be connected immediately after " + "construction"; +} + +TEST_F(SessionManagerTest, DisconnectBeforeConnectIsNoOp) { + SessionManager session_manager; + + EXPECT_NO_THROW(session_manager.disconnect()) + << "disconnect() on an unconnected SessionManager should be a safe no-op"; + + EXPECT_FALSE(session_manager.isConnected()); +} + +TEST_F(SessionManagerTest, MultipleDisconnectsAreIdempotent) { + SessionManager session_manager; + + EXPECT_NO_THROW({ + session_manager.disconnect(); + session_manager.disconnect(); + }) << "Multiple disconnect() calls should be safe"; +} + +TEST_F(SessionManagerTest, DestructorOnUnconnectedSessionManagerIsSafe) { + // Just verify no crash when the SessionManager is destroyed without + // connecting. + EXPECT_NO_THROW({ + SessionManager session_manager; + // SessionManager goes out of scope here + }); +} + +// ============================================================================ +// Track creation before connection +// ============================================================================ + +TEST_F(SessionManagerTest, CreateAudioTrackBeforeConnectThrows) { + SessionManager session_manager; + + EXPECT_THROW(session_manager.createAudioTrack( + "mic", 48000, 2, livekit::TrackSource::SOURCE_MICROPHONE), + std::runtime_error) + << "createAudioTrack should throw when not connected"; +} + +TEST_F(SessionManagerTest, CreateVideoTrackBeforeConnectThrows) { + SessionManager session_manager; + + EXPECT_THROW(session_manager.createVideoTrack( + "cam", 1280, 720, livekit::TrackSource::SOURCE_CAMERA), + std::runtime_error) + << "createVideoTrack should throw when not connected"; +} + +// ============================================================================ +// Callback registration (pre-connection, pure map operations) +// ============================================================================ + +TEST_F(SessionManagerTest, RegisterAndUnregisterAudioCallbackDoesNotCrash) { + SessionManager session_manager; + + EXPECT_NO_THROW({ + session_manager.setOnAudioFrameCallback( + "remote-participant", livekit::TrackSource::SOURCE_MICROPHONE, + [](const livekit::AudioFrame &) {}); + + session_manager.clearOnAudioFrameCallback( + "remote-participant", livekit::TrackSource::SOURCE_MICROPHONE); + }) << "Registering and unregistering an audio callback should be safe " + "even without a connection"; +} + +TEST_F(SessionManagerTest, RegisterAndUnregisterVideoCallbackDoesNotCrash) { + SessionManager session_manager; + + EXPECT_NO_THROW({ + session_manager.setOnVideoFrameCallback( + "remote-participant", livekit::TrackSource::SOURCE_CAMERA, + [](const livekit::VideoFrame &, std::int64_t) {}); + + session_manager.clearOnVideoFrameCallback( + "remote-participant", livekit::TrackSource::SOURCE_CAMERA); + }) << "Registering and unregistering a video callback should be safe " + "even without a connection"; +} + +TEST_F(SessionManagerTest, UnregisterNonExistentCallbackIsNoOp) { + SessionManager session_manager; + + EXPECT_NO_THROW({ + session_manager.clearOnAudioFrameCallback( + "nonexistent", livekit::TrackSource::SOURCE_MICROPHONE); + session_manager.clearOnVideoFrameCallback( + "nonexistent", livekit::TrackSource::SOURCE_CAMERA); + }) << "Unregistering a callback that was never registered should be a no-op"; +} + +TEST_F(SessionManagerTest, MultipleRegistrationsSameKeyOverwrites) { + SessionManager session_manager; + + int call_count = 0; + + // Register a first callback + session_manager.setOnAudioFrameCallback( + "alice", livekit::TrackSource::SOURCE_MICROPHONE, + [](const livekit::AudioFrame &) {}); + + // Register a second callback for the same key -- should overwrite + session_manager.setOnAudioFrameCallback( + "alice", livekit::TrackSource::SOURCE_MICROPHONE, + [&call_count](const livekit::AudioFrame &) { call_count++; }); + + // Unregister once should be enough (only one entry per key) + EXPECT_NO_THROW(session_manager.clearOnAudioFrameCallback( + "alice", livekit::TrackSource::SOURCE_MICROPHONE)); +} + +TEST_F(SessionManagerTest, RegisterCallbacksForMultipleParticipants) { + SessionManager session_manager; + + EXPECT_NO_THROW({ + session_manager.setOnAudioFrameCallback( + "alice", livekit::TrackSource::SOURCE_MICROPHONE, + [](const livekit::AudioFrame &) {}); + + session_manager.setOnVideoFrameCallback( + "bob", livekit::TrackSource::SOURCE_CAMERA, + [](const livekit::VideoFrame &, std::int64_t) {}); + + session_manager.setOnAudioFrameCallback( + "charlie", livekit::TrackSource::SOURCE_SCREENSHARE_AUDIO, + [](const livekit::AudioFrame &) {}); + }) << "Should be able to register callbacks for multiple participants"; + + // Cleanup + session_manager.clearOnAudioFrameCallback( + "alice", livekit::TrackSource::SOURCE_MICROPHONE); + session_manager.clearOnVideoFrameCallback( + "bob", livekit::TrackSource::SOURCE_CAMERA); + session_manager.clearOnAudioFrameCallback( + "charlie", livekit::TrackSource::SOURCE_SCREENSHARE_AUDIO); +} + +} // namespace test +} // namespace livekit diff --git a/src/video_frame.cpp b/src/video_frame.cpp index 5fdc83e..271c5ce 100644 --- a/src/video_frame.cpp +++ b/src/video_frame.cpp @@ -6,7 +6,7 @@ #include #include "livekit/ffi_handle.h" -#include "lk_log.h" +#include "livekit/lk_log.h" #include "video_utils.h" namespace livekit { diff --git a/src/video_stream.cpp b/src/video_stream.cpp index 182617e..2003856 100644 --- a/src/video_stream.cpp +++ b/src/video_stream.cpp @@ -5,7 +5,7 @@ #include "ffi.pb.h" #include "ffi_client.h" #include "livekit/track.h" -#include "lk_log.h" +#include "livekit/lk_log.h" #include "video_frame.pb.h" #include "video_utils.h"