From e137d7f378feabd52ff7facd55532c3d2f93a4ec Mon Sep 17 00:00:00 2001 From: shijing xian Date: Thu, 6 Nov 2025 16:13:07 +0800 Subject: [PATCH 01/10] make it build and run --- .gitignore | 1 + CMakeLists.txt | 237 ++++++++++++++++++++++++++++------ README.md | 66 +++++++++- build.sh | 106 +++++++++++++++ examples/CMakeLists.txt | 2 +- examples/simple_room/main.cpp | 112 ++++++++++++++-- 6 files changed, 475 insertions(+), 49 deletions(-) create mode 100755 build.sh diff --git a/.gitignore b/.gitignore index cdc99b9..3bc6e49 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ CMakeCache.txt Makefile cmake_install.cmake out +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 24ef94b..5fe58ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,56 +1,217 @@ -cmake_minimum_required(VERSION 3.0) -project(livekit) +cmake_minimum_required(VERSION 4.1) +project(livekit LANGUAGES C CXX) +# ---- C++ standard ---- set(CMAKE_CXX_STANDARD 17) -set(FFI_PROTO_PATH client-sdk-rust/livekit-ffi/protocol) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# ---- Protobuf (FFI protos) ---- +set(FFI_PROTO_DIR ${CMAKE_SOURCE_DIR}/client-sdk-rust/livekit-ffi/protocol) set(FFI_PROTO_FILES - ${FFI_PROTO_PATH}/handle.proto - ${FFI_PROTO_PATH}/ffi.proto - ${FFI_PROTO_PATH}/participant.proto - ${FFI_PROTO_PATH}/room.proto - ${FFI_PROTO_PATH}/track.proto - ${FFI_PROTO_PATH}/video_frame.proto - ${FFI_PROTO_PATH}/audio_frame.proto -) -set(PROTO_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated) + ${FFI_PROTO_DIR}/handle.proto + ${FFI_PROTO_DIR}/ffi.proto + ${FFI_PROTO_DIR}/participant.proto + ${FFI_PROTO_DIR}/room.proto + ${FFI_PROTO_DIR}/track.proto + ${FFI_PROTO_DIR}/video_frame.proto + ${FFI_PROTO_DIR}/audio_frame.proto +) +set(PROTO_BINARY_DIR ${CMAKE_BINARY_DIR}/generated) file(MAKE_DIRECTORY ${PROTO_BINARY_DIR}) -find_package(Protobuf REQUIRED) +find_package(Protobuf REQUIRED) # protobuf::libprotobuf, protoc +find_package(absl CONFIG REQUIRED) -# livekit-proto +# Object library that owns generated .pb.cc/.pb.h add_library(livekit_proto OBJECT ${FFI_PROTO_FILES}) +target_include_directories(livekit_proto PUBLIC + "$" + ${Protobuf_INCLUDE_DIRS} +) target_link_libraries(livekit_proto PUBLIC protobuf::libprotobuf) -target_include_directories(livekit_proto PUBLIC "$") -target_include_directories(livekit_proto PUBLIC ${Protobuf_INCLUDE_DIRS}) +# Generate .pb sources into ${PROTO_BINARY_DIR} and attach to livekit_proto protobuf_generate( - TARGET livekit_proto - PROTOS ${FFI_PROTO_FILES} - APPEND_PATH ${FFI_PROTO_PATH} - IMPORT_DIRS ${FFI_PROTO_PATH} - PROTOC_OUT_DIR ${PROTO_BINARY_DIR} + LANGUAGE cpp + TARGET livekit_proto + PROTOC_OUT_DIR ${PROTO_BINARY_DIR} + IMPORT_DIRS ${FFI_PROTO_DIR} +) + +# Find cargo +find_program(CARGO_EXECUTABLE NAMES cargo REQUIRED) + +set(RUST_ROOT ${CMAKE_SOURCE_DIR}/client-sdk-rust) + +# Write a helper script that never passes empty args +set(RUN_CARGO_SCRIPT ${CMAKE_BINARY_DIR}/run_cargo.cmake) +file(WRITE ${RUN_CARGO_SCRIPT} +"if(NOT DEFINED CFG) + set(CFG Debug) +endif() +if(NOT DEFINED RUST_ROOT) + message(FATAL_ERROR \"RUST_ROOT not set\") +endif() +if(NOT DEFINED CARGO) + message(FATAL_ERROR \"CARGO not set\") +endif() + +# Build arg list +set(ARGS build) +if(NOT CFG STREQUAL \"Debug\") + list(APPEND ARGS --release) +endif() + +message(STATUS \"[run_cargo.cmake] CFG=\${CFG} CARGO=\${CARGO}\") +execute_process( + COMMAND \"\${CARGO}\" \${ARGS} + WORKING_DIRECTORY \"\${RUST_ROOT}\" + RESULT_VARIABLE rv ) +if(rv) + message(FATAL_ERROR \"cargo build failed with code: \${rv}\") +endif() +") -# livekit -add_library(livekit - include/livekit/room.h - include/livekit/ffi_client.h - include/livekit/livekit.h - src/ffi_client.cpp - src/room.cpp - ${PROTO_SRCS} - ${PROTO_HEADERS} - ${PROTO_FILES} +# Imported Rust lib with per-config locations +add_library(livekit_ffi STATIC IMPORTED GLOBAL) +set_target_properties(livekit_ffi PROPERTIES + IMPORTED_LOCATION_DEBUG "${RUST_ROOT}/target/debug/liblivekit_ffi.a" + IMPORTED_LOCATION_RELWITHDEBINFO "${RUST_ROOT}/target/release/liblivekit_ffi.a" + IMPORTED_LOCATION_MINSIZEREL "${RUST_ROOT}/target/release/liblivekit_ffi.a" + IMPORTED_LOCATION_RELEASE "${RUST_ROOT}/target/release/liblivekit_ffi.a" + INTERFACE_INCLUDE_DIRECTORIES "${RUST_ROOT}/livekit-ffi/include" ) +# Custom target that runs the script; no empty args get passed to cargo +add_custom_target(build_rust_ffi ALL + COMMAND "${CMAKE_COMMAND}" + -DCFG=$ + -DRUST_ROOT=${RUST_ROOT} + -DCARGO=${CARGO_EXECUTABLE} + -P "${RUN_CARGO_SCRIPT}" + USES_TERMINAL + COMMENT "Invoking cargo for Rust FFI ($)" + VERBATIM +) -# Include the auto-generated files from livekit-ffi (C headers) -target_include_directories(livekit PUBLIC client-sdk-rust/livekit-ffi/include/) -target_include_directories(livekit PUBLIC include/) +# Ensure cargo runs before linking livekit (and examples if needed) +# +# If you have example targets like SimpleRoom: +# add_dependencies(SimpleRoom build_rust_ffi) -# Link against livekit-ffi -link_directories(${CMAKE_CURRENT_BINARY_DIR}) -target_link_libraries(livekit PUBLIC livekit_ffi livekit_proto) +# ---- C++ wrapper library ---- +add_library(livekit + include/livekit/room.h + include/livekit/ffi_client.h + include/livekit/livekit.h + src/ffi_client.cpp + src/room.cpp +) +if(APPLE) + find_library(FW_COREAUDIO CoreAudio REQUIRED) + find_library(FW_AUDIOTOOLBOX AudioToolbox REQUIRED) + find_library(FW_COREFOUNDATION CoreFoundation REQUIRED) + find_library(FW_SECURITY Security REQUIRED) + find_library(FW_COREGRAPHICS CoreGraphics REQUIRED) + find_library(FW_COREMEDIA CoreMedia REQUIRED) + find_library(FW_VIDEOTOOLBOX VideoToolbox REQUIRED) + find_library(FW_AVFOUNDATION AVFoundation REQUIRED) + find_library(FW_COREVIDEO CoreVideo REQUIRED) + find_library(FW_FOUNDATION Foundation REQUIRED) -# Examples + target_link_libraries(livekit PUBLIC + ${FW_COREAUDIO} + ${FW_AUDIOTOOLBOX} + ${FW_COREFOUNDATION} + ${FW_SECURITY} + ${FW_COREGRAPHICS} + ${FW_COREMEDIA} + ${FW_VIDEOTOOLBOX} + ${FW_AVFOUNDATION} + ${FW_COREVIDEO} + ${FW_FOUNDATION} + ) +endif() + +# Add generated proto objects to the wrapper +target_sources(livekit PRIVATE $) + +target_include_directories(livekit PUBLIC + ${CMAKE_SOURCE_DIR}/include + ${RUST_ROOT}/livekit-ffi/include + ${PROTO_BINARY_DIR} +) + +target_link_libraries(livekit + PUBLIC + livekit_ffi + protobuf::libprotobuf + absl::log + absl::check + absl::strings + absl::base +) + + +# Ensure cargo runs before we try to link livekit +add_dependencies(livekit build_rust_ffi) + +# Warnings +if (MSVC) + target_compile_options(livekit PRIVATE /permissive- /Zc:__cplusplus /W4) +else() + target_compile_options(livekit PRIVATE -Wall -Wextra -Wpedantic) +endif() + +# ---- Examples ---- add_subdirectory(examples) + + +# ---------- Clean helpers ---------- +# Removes generated protobuf sources +add_custom_target(clean_generated + COMMAND ${CMAKE_COMMAND} -E echo "Removing generated protobufs: ${PROTO_BINARY_DIR}" + COMMAND ${CMAKE_COMMAND} -E rm -rf "${PROTO_BINARY_DIR}" + COMMENT "Clean generated protobuf files" + VERBATIM +) + +# Cargo clean (safer, lets Cargo decide what to delete) +add_custom_target(cargo_clean + COMMAND ${CMAKE_COMMAND} -E echo "Running 'cargo clean' in: ${RUST_ROOT}" + COMMAND "${CARGO_EXECUTABLE}" clean + WORKING_DIRECTORY "${RUST_ROOT}" + COMMENT "Clean Rust target directory via cargo" + VERBATIM +) + +# If you prefer hard deletes of specific profiles instead of 'cargo clean', use: +# add_custom_target(cargo_clean_debug +# COMMAND ${CMAKE_COMMAND} -E rm -rf "${RUST_ROOT}/target/debug" +# COMMENT "Remove Rust target/debug") +# add_custom_target(cargo_clean_release +# COMMAND ${CMAKE_COMMAND} -E rm -rf "${RUST_ROOT}/target/release" +# COMMENT "Remove Rust target/release") +# add_custom_target(cargo_clean_all DEPENDS cargo_clean_debug cargo_clean_release) + +# Combined "clean-all" (C++ clean + generated + cargo) +# Note: 'clean' is CMake's built-in target that removes CMake-built artifacts. +add_custom_target(clean_all + # 1) CMake clean (object files, libs, exes) + COMMAND ${CMAKE_COMMAND} -E echo "==> CMake clean in: ${CMAKE_BINARY_DIR}" + COMMAND ${CMAKE_COMMAND} --build "${CMAKE_BINARY_DIR}" --target clean || true + # 2) Cargo clean (Rust target/) + COMMAND ${CMAKE_COMMAND} -E echo "==> cargo clean in: ${RUST_ROOT}" + COMMAND ${CMAKE_COMMAND} -E chdir "${RUST_ROOT}" "${CARGO_EXECUTABLE}" clean || true + # 3) Remove generated protobufs (lives under build/) + COMMAND ${CMAKE_COMMAND} -E echo "==> removing generated protobufs: ${PROTO_BINARY_DIR}" + COMMAND ${CMAKE_COMMAND} -E rm -rf "${PROTO_BINARY_DIR}" || true + # 4) Remove the entire build directory (like `rm -rf build`) + # Switch to SOURCE dir first so removing BINARY dir is always safe. + COMMAND ${CMAKE_COMMAND} -E echo "==> removing build directory: ${CMAKE_BINARY_DIR}" + COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_SOURCE_DIR}" ${CMAKE_COMMAND} -E rm -rf "${CMAKE_BINARY_DIR}" || true + COMMENT "Full clean: CMake outputs + Rust target + generated protos + delete build/" + VERBATIM +) \ No newline at end of file diff --git a/README.md b/README.md index 3d2569f..bad277f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,67 @@ # LiveKit C++ Client SDK -This repo is a work-in-progress. It's not ready to be used yet. We are building this one in public in collaboration with the community. +A C++ wrapper around the [LiveKit Rust Client SDK](https://github.com/livekit/client-sdk-rust), built using **CMake** and integrated with the Rust FFI layer. +This SDK enables native C++ applications to connect to LiveKit servers for real-time audio/video communication. -Interested in contributing? Stop by our [Community Slack](https://livekit.io/join-slack) and join the #dev channel +--- + +## 📦 Requirements +- **CMake** ≥ 4.0 +- **Rust / Cargo** (latest stable toolchain) +- **Protobuf** compiler (`protoc`) +- **macOS** users: System frameworks (CoreAudio, AudioToolbox, etc.) are automatically linked via CMake. + + +## 🧩 Clone the Repository + +Make sure to initialize the Rust submodule (`client-sdk-rust`): + +```bash +# Option 1: Clone with submodules in one step +git clone --recurse-submodules https://github.com/livekit/client-sdk-cpp.git + +# Option 2: Clone first, then initialize submodules +git clone https://github.com/livekit/client-sdk-cpp.git +cd client-sdk-cpp +git submodule update --init --recursive +``` + +## ⚙️ BUILD + +All build actions are managed by the provided build.sh script. +```bash +./build.sh clean # Clean CMake build artifacts +./build.sh clean-all # Deep clean (C++ + Rust + generated files) +./build.sh debug # Build Debug version +./build.sh release # Build Release version +./build.sh verbose # Verbose build output +``` + +## 🧪 Run Example + +```bash +./build/examples/SimpleRoom --url ws://localhost:7880 --token +``` + +You can also provide the URL and token via environment variables: +```bash +export LIVEKIT_URL=ws://localhost:7880 +export LIVEKIT_TOKEN= +./build/examples/SimpleRoom +``` + +Press Ctrl-C to exit the example. + + +## 🧰 Recommended Setup +### macOS +```bash +brew install cmake protobuf rust +``` + +### Ubuntu / Debian +```bash +sudo apt update +sudo apt install -y cmake protobuf-compiler build-essential +curl https://sh.rustup.rs -sSf | sh +``` \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..f211ea8 --- /dev/null +++ b/build.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +set -euo pipefail + +PROJECT_ROOT="$(cd "$(dirname "$0")" && pwd)" +BUILD_DIR="${PROJECT_ROOT}/build" +BUILD_TYPE="Release" +VERBOSE="" +TARGET="" + + +usage() { + cat < Configuring CMake (${BUILD_TYPE})..." + cmake -S . -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" +} + +build() { + echo "==> Building (${BUILD_TYPE})..." + cmake --build "${BUILD_DIR}" -j ${VERBOSE:+--verbose} +} + +clean() { + echo "==> Cleaning CMake targets..." + if [[ -d "${BUILD_DIR}" ]]; then + cmake --build "${BUILD_DIR}" --target clean || true + else + echo " (skipping) ${BUILD_DIR} does not exist." + fi +} + +clean_all() { + echo "==> Running full clean-all (C++ + Rust)..." + if [[ -d "${BUILD_DIR}" ]]; then + # This may delete the entire build dir; that's fine. + cmake --build "${BUILD_DIR}" --target clean_all || true + else + echo " (info) ${BUILD_DIR} does not exist; doing manual deep clean..." + fi + + # Be tolerant if these paths already don't exist. + rm -rf "${PROJECT_ROOT}/client-sdk-rust/target/debug" || true + rm -rf "${PROJECT_ROOT}/client-sdk-rust/target/release" || true + rm -rf "${BUILD_DIR}" || true + echo "==> Clean-all complete." +} + + +if [[ $# -eq 0 ]]; then + usage + exit 0 +fi + +case "$1" in + debug) + BUILD_TYPE="Debug" + configure + build + ;; + release) + BUILD_TYPE="Release" + configure + build + ;; + verbose) + VERBOSE="1" + build + ;; + clean) + clean + ;; + clean-all) + clean_all + ;; + distclean) + distclean + ;; + help|-h|--help) + usage + ;; + *) + echo "Unknown command: $1" + usage + exit 1 + ;; +esac + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index cc9a8a4..955a4d3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.0) +cmake_minimum_required(VERSION 4.1) project (livekit-examples) add_executable(SimpleRoom simple_room/main.cpp) diff --git a/examples/simple_room/main.cpp b/examples/simple_room/main.cpp index e9e7796..045164a 100644 --- a/examples/simple_room/main.cpp +++ b/examples/simple_room/main.cpp @@ -1,20 +1,116 @@ +#include +#include +#include +#include +#include +#include +#include +#include + #include "livekit/livekit.h" using namespace livekit; -int main(int argc, char *argv[]) -{ - Room room{}; - room.Connect("ws://localhost:7880", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjQ5MjM1OTQ4MjMsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJ3ZWIiLCJuYmYiOjE2ODM1OTQ4MjMsInN1YiI6IndlYiIsInZpZGVvIjp7InJvb20iOiJsaXZla2l0LWZmaS10ZXN0Iiwicm9vbUpvaW4iOnRydWV9fQ.voLT9RK3wNYEGdWovPLv1BzyN1v5tpJ59e0DIqIVfiU"); +namespace { +std::atomic g_running{true}; + +void print_usage(const char* prog) { + std::cerr << "Usage:\n" + << " " << prog << " \n" + << "or:\n" + << " " << prog << " --url= --token=\n" + << " " << prog << " --url --token \n\n" + << "Env fallbacks:\n" + << " LIVEKIT_URL, LIVEKIT_TOKEN\n"; +} +void handle_sigint(int) { + g_running = false; +} - // Should we implement a mechanism to PollEvents/WaitEvents? Like SDL2/glfw - // - So we can remove the useless loop here - // Or is it better to use callback based events? +bool parse_args(int argc, char* argv[], std::string& url, std::string& token) { + // 1) --help + for (int i = 1; i < argc; ++i) { + std::string a = argv[i]; + if (a == "-h" || a == "--help") { + return false; + } + } + + // 2) flags: --url= / --token= or split form + auto get_flag_value = [&](const std::string& name, int& i) -> std::string { + std::string arg = argv[i]; + const std::string eq = name + "="; + if (arg.rfind(name, 0) == 0) { // starts with name + if (arg.size() > name.size() && arg[name.size()] == '=') { + return arg.substr(eq.size()); + } else if (i + 1 < argc) { + return std::string(argv[++i]); + } + } + return {}; + }; + + for (int i = 1; i < argc; ++i) { + const std::string a = argv[i]; + if (a.rfind("--url", 0) == 0) { + auto v = get_flag_value("--url", i); + if (!v.empty()) url = v; + } else if (a.rfind("--token", 0) == 0) { + auto v = get_flag_value("--token", i); + if (!v.empty()) token = v; + } + } + + // 3) positional if still empty + if (url.empty() || token.empty()) { + std::vector pos; + for (int i = 1; i < argc; ++i) { + std::string a = argv[i]; + if (a.rfind("--", 0) == 0) continue; // skip flags we already parsed + pos.push_back(std::move(a)); + } + if (pos.size() >= 2) { + if (url.empty()) url = pos[0]; + if (token.empty()) token = pos[1]; + } + } - while(true) { + // 4) env fallbacks + if (url.empty()) { + const char* e = std::getenv("LIVEKIT_URL"); + if (e) url = e; + } + if (token.empty()) { + const char* e = std::getenv("LIVEKIT_TOKEN"); + if (e) token = e; + } + + return !(url.empty() || token.empty()); +} +} // namespace + +int main(int argc, char* argv[]) { + std::string url, token; + if (!parse_args(argc, argv, url, token)) { + print_usage(argv[0]); + return 1; + } + + std::cout << "Connecting to: " << url << std::endl; + + // Handle Ctrl-C to exit the idle loop + std::signal(SIGINT, handle_sigint); + + Room room{}; + room.Connect(url.c_str(), token.c_str()); + // TODO: replace with proper event loop / callbacks. + // For now, keep the app alive until Ctrl-C. + while (g_running.load()) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } + std::cout << "Exiting.\n"; return 0; } From 697c93648ec2f2a79cd759065cf3852cda011915 Mon Sep 17 00:00:00 2001 From: shijing xian Date: Thu, 6 Nov 2025 16:31:00 +0800 Subject: [PATCH 02/10] removed the unneeded comments --- CMakeLists.txt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fe58ea..6b2ae45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,11 +96,6 @@ add_custom_target(build_rust_ffi ALL VERBATIM ) -# Ensure cargo runs before linking livekit (and examples if needed) -# -# If you have example targets like SimpleRoom: -# add_dependencies(SimpleRoom build_rust_ffi) - # ---- C++ wrapper library ---- add_library(livekit include/livekit/room.h @@ -187,15 +182,6 @@ add_custom_target(cargo_clean VERBATIM ) -# If you prefer hard deletes of specific profiles instead of 'cargo clean', use: -# add_custom_target(cargo_clean_debug -# COMMAND ${CMAKE_COMMAND} -E rm -rf "${RUST_ROOT}/target/debug" -# COMMENT "Remove Rust target/debug") -# add_custom_target(cargo_clean_release -# COMMAND ${CMAKE_COMMAND} -E rm -rf "${RUST_ROOT}/target/release" -# COMMENT "Remove Rust target/release") -# add_custom_target(cargo_clean_all DEPENDS cargo_clean_debug cargo_clean_release) - # Combined "clean-all" (C++ clean + generated + cargo) # Note: 'clean' is CMake's built-in target that removes CMake-built artifacts. add_custom_target(clean_all From 061f4888731ea34cf53b2fa1230e5329e1b787aa Mon Sep 17 00:00:00 2001 From: shijing xian Date: Thu, 6 Nov 2025 16:33:58 +0800 Subject: [PATCH 03/10] minor cleanup --- README.md | 1 - build.sh | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index bad277f..3fd4ff6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # LiveKit C++ Client SDK -A C++ wrapper around the [LiveKit Rust Client SDK](https://github.com/livekit/client-sdk-rust), built using **CMake** and integrated with the Rust FFI layer. This SDK enables native C++ applications to connect to LiveKit servers for real-time audio/video communication. --- diff --git a/build.sh b/build.sh index f211ea8..0a41604 100755 --- a/build.sh +++ b/build.sh @@ -16,7 +16,7 @@ Commands: debug Configure + build Debug version release Configure + build Release version clean Run CMake's built-in clean target - clean-all Run your custom clean_all (clears C++ + Rust targets) + clean-all Run clean_all (clears C++ + Rust targets) verbose Build with verbose output (implies last configured type) help Show this help @@ -51,13 +51,11 @@ clean() { clean_all() { echo "==> Running full clean-all (C++ + Rust)..." if [[ -d "${BUILD_DIR}" ]]; then - # This may delete the entire build dir; that's fine. cmake --build "${BUILD_DIR}" --target clean_all || true else echo " (info) ${BUILD_DIR} does not exist; doing manual deep clean..." fi - # Be tolerant if these paths already don't exist. rm -rf "${PROJECT_ROOT}/client-sdk-rust/target/debug" || true rm -rf "${PROJECT_ROOT}/client-sdk-rust/target/release" || true rm -rf "${BUILD_DIR}" || true From 60a78f8207caae2ce4986c275397d14b8db547aa Mon Sep 17 00:00:00 2001 From: shijing xian Date: Thu, 6 Nov 2025 16:42:24 +0800 Subject: [PATCH 04/10] try adding builds.yml --- .github/workflows/builds.yml | 136 +++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 .github/workflows/builds.yml diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml new file mode 100644 index 0000000..bbeffae --- /dev/null +++ b/.github/workflows/builds.yml @@ -0,0 +1,136 @@ +name: Builds + +on: + push: + branches: ["main"] + paths: + - src/** + - include/** + - examples/** + - client-sdk-rust/** + - CMakeLists.txt + - build.sh + - .github/workflows/** + pull_request: + branches: ["main"] + paths: + - src/** + - include/** + - examples/** + - client-sdk-rust/** + - CMakeLists.txt + - build.sh + - .github/workflows/** + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + - os: macos-latest + - os: windows-latest + name: ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout (with submodules) + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + # ---------- Protobuf (protoc) ---------- + - name: Install Protoc + uses: arduino/setup-protoc@v2 + with: + version: "25.2" + repo-token: ${{ secrets.GITHUB_TOKEN }} + + # ---------- Rust toolchain ---------- + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@stable + + # ---------- Cache Cargo ---------- + - name: Cache Cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-reg-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo-reg- + + - name: Cache Cargo target + uses: actions/cache@v4 + with: + path: client-sdk-rust/target + key: ${{ runner.os }}-cargo-target-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-target- + + # ---------- OS-specific deps ---------- + - name: Install deps (Ubuntu) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y cmake ninja-build build-essential + # Optional audio/video libs if your FFI/examples need them: + # sudo apt-get install -y libasound2-dev libx11-dev libgl1-mesa-dev libxext-dev + protoc --version + + - name: Install deps (macOS) + if: runner.os == 'macOS' + run: | + brew update + brew install cmake protobuf ninja + protoc --version + + - name: Install deps (Windows) + if: runner.os == 'Windows' + shell: bash + run: | + choco install -y cmake ninja protoc + protoc --version || true + + # ---------- Build (Debug) ---------- + - name: Build Debug + shell: bash + run: | + chmod +x build.sh + ./build.sh debug + + # ---------- Build (Release) ---------- + - name: Build Release + shell: bash + run: | + ./build.sh release + + # ---------- Smoke test example (link/usage only) ---------- + - name: Smoke test example (Debug) + if: runner.os != 'Windows' + shell: bash + run: | + if [[ -x build/examples/SimpleRoom ]]; then + build/examples/SimpleRoom --help || true + fi + + - name: Smoke test example (Release) + if: runner.os == 'Windows' + shell: bash + run: | + if [ -f "build/examples/Release/SimpleRoom.exe" ]; then + ./build/examples/Release/SimpleRoom.exe --help || true + fi + + # Optional: keep workspace tidy in CI + - name: Clean after build (best-effort) + if: always() + shell: bash + run: | + [[ -x build.sh ]] && ./build.sh clean-all || true From b4bdc27311380590abf4b95a1bc6c2ec32dddc29 Mon Sep 17 00:00:00 2001 From: shijing xian Date: Thu, 6 Nov 2025 17:42:21 +0800 Subject: [PATCH 05/10] fix the build --- .github/workflows/builds.yml | 74 +++++++++++++++--------------------- CMakeLists.txt | 2 +- examples/CMakeLists.txt | 2 +- 3 files changed, 33 insertions(+), 45 deletions(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index bbeffae..d1caa42 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -34,7 +34,9 @@ jobs: include: - os: ubuntu-latest - os: macos-latest - - os: windows-latest + # Windows needs protobuf *libraries* too (not just protoc). + # Enable after wiring vcpkg (see notes below). + # - os: windows-latest name: ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -45,12 +47,31 @@ jobs: submodules: recursive fetch-depth: 0 - # ---------- Protobuf (protoc) ---------- - - name: Install Protoc - uses: arduino/setup-protoc@v2 - with: - version: "25.2" - repo-token: ${{ secrets.GITHUB_TOKEN }} + # ---------- OS-specific deps: install protoc + libprotobuf that MATCH ---------- + - name: Install deps (Ubuntu) + if: runner.os == 'Linux' + run: | + set -eux + sudo apt-get update + sudo apt-get install -y cmake ninja-build build-essential \ + protobuf-compiler libprotobuf-dev + protoc --version + pkg-config --modversion protobuf + # Fail if versions don't match (best-effort) + test "$(protoc --version | awk '{print $2}')" = "$(pkg-config --modversion protobuf)" || { + echo "protoc and libprotobuf versions differ"; exit 1; } + + - name: Install deps (macOS) + if: runner.os == 'macOS' + run: | + set -eux + brew update + # EITHER: latest protobuf + brew install cmake protobuf ninja + # OR pin a specific version (e.g., protobuf@6) if you need it: + # brew install protobuf@6 && brew link --overwrite --force protobuf@6 + protoc --version + pkg-config --modversion protobuf # ---------- Rust toolchain ---------- - name: Install Rust (stable) @@ -74,44 +95,19 @@ jobs: restore-keys: | ${{ runner.os }}-cargo-target- - # ---------- OS-specific deps ---------- - - name: Install deps (Ubuntu) - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y cmake ninja-build build-essential - # Optional audio/video libs if your FFI/examples need them: - # sudo apt-get install -y libasound2-dev libx11-dev libgl1-mesa-dev libxext-dev - protoc --version - - - name: Install deps (macOS) - if: runner.os == 'macOS' - run: | - brew update - brew install cmake protobuf ninja - protoc --version - - - name: Install deps (Windows) - if: runner.os == 'Windows' - shell: bash - run: | - choco install -y cmake ninja protoc - protoc --version || true - - # ---------- Build (Debug) ---------- + # ---------- Build (Debug / Release) ---------- - name: Build Debug shell: bash run: | chmod +x build.sh ./build.sh debug - # ---------- Build (Release) ---------- - name: Build Release shell: bash run: | ./build.sh release - # ---------- Smoke test example (link/usage only) ---------- + # ---------- Smoke test example (no server needed) ---------- - name: Smoke test example (Debug) if: runner.os != 'Windows' shell: bash @@ -120,15 +116,7 @@ jobs: build/examples/SimpleRoom --help || true fi - - name: Smoke test example (Release) - if: runner.os == 'Windows' - shell: bash - run: | - if [ -f "build/examples/Release/SimpleRoom.exe" ]; then - ./build/examples/Release/SimpleRoom.exe --help || true - fi - - # Optional: keep workspace tidy in CI + # ---------- Cleanup ---------- - name: Clean after build (best-effort) if: always() shell: bash diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b2ae45..4217f45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 4.1) +cmake_minimum_required(VERSION 3.31.0) project(livekit LANGUAGES C CXX) # ---- C++ standard ---- diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 955a4d3..92e35c7 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 4.1) +cmake_minimum_required(VERSION 3.31.0) project (livekit-examples) add_executable(SimpleRoom simple_room/main.cpp) From 236957b191d1ae581ce3f15c0aeee09a76d07e9d Mon Sep 17 00:00:00 2001 From: shijing xian Date: Thu, 6 Nov 2025 18:42:27 +0800 Subject: [PATCH 06/10] fix linux build --- .github/workflows/builds.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index d1caa42..8e5d481 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -54,7 +54,7 @@ jobs: set -eux sudo apt-get update sudo apt-get install -y cmake ninja-build build-essential \ - protobuf-compiler libprotobuf-dev + protobuf-compiler libprotobuf-dev libabsl-dev protoc --version pkg-config --modversion protobuf # Fail if versions don't match (best-effort) From 89b551674f34c851c409124efbe1a311a9ee6356 Mon Sep 17 00:00:00 2001 From: shijing xian Date: Thu, 6 Nov 2025 18:56:39 +0800 Subject: [PATCH 07/10] another fix to the linux build --- CMakeLists.txt | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4217f45..3d73ad2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,11 +143,42 @@ target_link_libraries(livekit PUBLIC livekit_ffi protobuf::libprotobuf - absl::log - absl::check - absl::strings - absl::base ) +# Ensure protoc matches headers/libs +message(STATUS "Protobuf: version=${Protobuf_VERSION}; protoc=${Protobuf_PROTOC_EXECUTABLE}") + +# Only Protobuf >= 6 needs Abseil +if (Protobuf_VERSION VERSION_GREATER_EQUAL 6.0) + # Try modern package name/namespace + find_package(absl CONFIG QUIET) + # Some distros export as "Abseil::" + if (NOT absl_FOUND) + find_package(Abseil QUIET) + endif() + + if (absl_FOUND) + target_link_libraries(livekit PUBLIC + absl::log + absl::check + absl::strings + absl::base + ) + elseif (Abseil_FOUND) + target_link_libraries(livekit PUBLIC + Abseil::log + Abseil::check + Abseil::strings + Abseil::base + ) + else() + message(FATAL_ERROR + "Protobuf ${Protobuf_VERSION} requires Abseil but no CMake package was found.\n" + "Install Abseil (macOS: 'brew install abseil', Ubuntu: 'sudo apt-get install libabsl-dev'), " + "or use Protobuf < 6.") + endif() +else() + message(STATUS "Protobuf < 6 detected; skipping Abseil linking.") +endif() # Ensure cargo runs before we try to link livekit From e32c6dfcc132cc2c1749eea406e48b3f98c29124 Mon Sep 17 00:00:00 2001 From: shijing xian Date: Thu, 6 Nov 2025 19:42:53 +0800 Subject: [PATCH 08/10] another try to fix missing deps on Linux --- .github/workflows/builds.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 8e5d481..dda8e26 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -54,7 +54,8 @@ jobs: set -eux sudo apt-get update sudo apt-get install -y cmake ninja-build build-essential \ - protobuf-compiler libprotobuf-dev libabsl-dev + protobuf-compiler libprotobuf-dev libabsl-dev \ + libx11-dev libxext-dev libgl1-mesa-dev protoc --version pkg-config --modversion protobuf # Fail if versions don't match (best-effort) From a999c7b072968e9b9c8bad4c944c82ff8d78357b Mon Sep 17 00:00:00 2001 From: shijing xian Date: Fri, 7 Nov 2025 09:07:27 +0800 Subject: [PATCH 09/10] added openSSL to fix the Linux build --- .github/workflows/builds.yml | 2 +- CMakeLists.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index dda8e26..86aaa73 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -55,7 +55,7 @@ jobs: sudo apt-get update sudo apt-get install -y cmake ninja-build build-essential \ protobuf-compiler libprotobuf-dev libabsl-dev \ - libx11-dev libxext-dev libgl1-mesa-dev + libx11-dev libxext-dev libgl1-mesa-dev libssl-dev protoc --version pkg-config --modversion protobuf # Fail if versions don't match (best-effort) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d73ad2..7bccd6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,11 @@ else() message(STATUS "Protobuf < 6 detected; skipping Abseil linking.") endif() +# On Linux, it needs to link OpenSSL +if(UNIX AND NOT APPLE) + find_package(OpenSSL REQUIRED) + target_link_libraries(livekit PUBLIC OpenSSL::SSL OpenSSL::Crypto) +endif() # Ensure cargo runs before we try to link livekit add_dependencies(livekit build_rust_ffi) From 211793143ee84833093684c3bfe5327655b9fe4e Mon Sep 17 00:00:00 2001 From: shijing xian Date: Fri, 7 Nov 2025 11:44:12 +0800 Subject: [PATCH 10/10] added Objc linker flag, and added more apple libs --- CMakeLists.txt | 59 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bccd6d..dd989e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,30 @@ add_library(livekit src/ffi_client.cpp src/room.cpp ) + +# Add generated proto objects to the wrapper +target_sources(livekit PRIVATE $) + +target_include_directories(livekit PUBLIC + ${CMAKE_SOURCE_DIR}/include + ${RUST_ROOT}/livekit-ffi/include + ${PROTO_BINARY_DIR} +) + +target_link_libraries(livekit + PUBLIC + livekit_ffi + protobuf::libprotobuf +) +# Ensure protoc matches headers/libs +message(STATUS "Protobuf: version=${Protobuf_VERSION}; protoc=${Protobuf_PROTOC_EXECUTABLE}") + +# Ensure cargo runs before we try to link livekit +add_dependencies(livekit build_rust_ffi) + + +########### Platform specific settings ########### + if(APPLE) find_library(FW_COREAUDIO CoreAudio REQUIRED) find_library(FW_AUDIOTOOLBOX AudioToolbox REQUIRED) @@ -115,6 +139,12 @@ if(APPLE) find_library(FW_AVFOUNDATION AVFoundation REQUIRED) find_library(FW_COREVIDEO CoreVideo REQUIRED) find_library(FW_FOUNDATION Foundation REQUIRED) + find_library(FW_APPKIT AppKit REQUIRED) + find_library(FW_QUARTZCORE QuartzCore REQUIRED) + find_library(FW_OPENGL OpenGL REQUIRED) + find_library(FW_IOSURFACE IOSurface REQUIRED) + find_library(FW_METAL Metal REQUIRED) + find_library(FW_METALKIT MetalKit REQUIRED) target_link_libraries(livekit PUBLIC ${FW_COREAUDIO} @@ -127,25 +157,18 @@ if(APPLE) ${FW_AVFOUNDATION} ${FW_COREVIDEO} ${FW_FOUNDATION} + ${FW_APPKIT} + ${FW_QUARTZCORE} + ${FW_OPENGL} + ${FW_IOSURFACE} + ${FW_METAL} + ${FW_METALKIT} ) -endif() - -# Add generated proto objects to the wrapper -target_sources(livekit PRIVATE $) - -target_include_directories(livekit PUBLIC - ${CMAKE_SOURCE_DIR}/include - ${RUST_ROOT}/livekit-ffi/include - ${PROTO_BINARY_DIR} -) -target_link_libraries(livekit - PUBLIC - livekit_ffi - protobuf::libprotobuf -) -# Ensure protoc matches headers/libs -message(STATUS "Protobuf: version=${Protobuf_VERSION}; protoc=${Protobuf_PROTOC_EXECUTABLE}") + # Ensure Objective-C categories/classes in static archives are loaded (WebRTC needs this) + # Use LINKER: to guarantee it is passed to the linker (CMake >= 3.13) + target_link_options(livekit INTERFACE "LINKER:-ObjC") +endif() # Only Protobuf >= 6 needs Abseil if (Protobuf_VERSION VERSION_GREATER_EQUAL 6.0) @@ -186,8 +209,6 @@ if(UNIX AND NOT APPLE) target_link_libraries(livekit PUBLIC OpenSSL::SSL OpenSSL::Crypto) endif() -# Ensure cargo runs before we try to link livekit -add_dependencies(livekit build_rust_ffi) # Warnings if (MSVC)