diff --git a/CMakeLists.txt b/CMakeLists.txt index 655b7d6..bc36e83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Make "include(LiveKitSDK)" search in ./cmake list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set(LIVEKIT_SDK_VERSION "latest" CACHE STRING "LiveKit C++ SDK version (e.g. 0.2.0 or latest)") +set(LIVEKIT_SDK_VERSION "latest" CACHE STRING "LiveKit C++ SDK version (e.g. 1.3.0 or latest)") set(LIVEKIT_LOCAL_SDK_DIR "" CACHE PATH "Path to a local LiveKit SDK install prefix (skips download)") if(LIVEKIT_LOCAL_SDK_DIR) @@ -82,6 +82,7 @@ endfunction() include(ExampleDeps) add_subdirectory(basic_room) +add_subdirectory(token_source) add_subdirectory(simple_room) add_subdirectory(simple_rpc) add_subdirectory(simple_data_stream) diff --git a/README.md b/README.md index c5e4e18..245cc1a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # cpp-example-collection + This repository contains a collection of small, self-contained examples for the [LiveKit C++ SDK](https://github.com/livekit/client-sdk-cpp). @@ -6,13 +7,30 @@ The goal of these examples is to demonstrate common usage patterns of the LiveKit C++ SDK (connecting to a room, publishing tracks, RPC, data streams, etc.) without requiring users to build the SDK from source. +## Examples + +| Example | Description | +| --- | --- | +| [`basic_room`](basic_room/) | Connects to a room and publishes synthetic audio and video tracks. | +| [`frame_metadata`](frame_metadata/) | Publishes and consumes video frames with frame metadata such as IDs, timestamps, and user data. | +| [`hello_livekit/receiver`](hello_livekit/receiver/) | Subscribes to the sender example's video and data tracks. | +| [`hello_livekit/sender`](hello_livekit/sender/) | Publishes synthetic video and data for a paired receiver example. | +| [`logging_levels/basic_usage`](logging_levels/basic_usage/) | Demonstrates SDK log-level filtering and log callbacks. | +| [`logging_levels/custom_sinks`](logging_levels/custom_sinks/) | Shows custom SDK log sinks such as file, JSON, and ROS-style output. | +| [`ping_pong/ping`](ping_pong/ping/) | Sends ping messages over a data track and records response latency. | +| [`ping_pong/pong`](ping_pong/pong/) | Listens for ping messages and publishes matching pong responses. | +| [`platform_audio`](platform_audio/) | Demonstrates microphone capture and speaker playout with platform audio devices. | +| [`simple_data_stream`](simple_data_stream/) | Sends and receives text and byte streams over LiveKit data streams. | +| [`simple_room`](simple_room/) | Connects to a room, publishes local media, and subscribes to remote media. | +| [`simple_rpc`](simple_rpc/) | Demonstrates LiveKit RPC calls between participants. | +| [`token_source`](token_source/) | Shows ways to supply connection credentials through LiveKit token sources. | ## How the SDK is provided These examples **automatically download a prebuilt LiveKit C++ SDK release** from GitHub at CMake configure time. -This is handled by the cmake helper: [LiveKitSDK.cmake ](https://github.com/livekit-examples/cpp-example-collection/cmake/LiveKitSDK.cmake) +This is handled by the CMake helper: [`LiveKitSDK.cmake`](https://github.com/livekit-examples/cpp-example-collection/blob/main/cmake/LiveKitSDK.cmake). ## Selecting a LiveKit SDK version @@ -23,52 +41,63 @@ You can pin a specific SDK version using the `LIVEKIT_SDK_VERSION` CMake option. ### Examples Use the latest release: + ```bash cmake -S . -B build -# Or use a specific version (recommended for reproducibility): -cmake -S . -B build -DLIVEKIT_SDK_VERSION=0.2.0 +``` + +Use a specific version: + +```bash +cmake -S . -B build -DLIVEKIT_SDK_VERSION=1.3.0 ``` Reconfigure to change versions: + ```bash rm -rf build -cmake -S . -B build -DLIVEKIT_SDK_VERSION=0.3.1 +cmake -S . -B build -DLIVEKIT_SDK_VERSION=1.3.0 ``` Build against a local SDK: + ```bash rm -rf build # install the SDK into $HOME/livekit-sdk-install (or any other directory) -cmake --install --prefix $HOME/livekit-sdk-install +cmake --install --prefix "$HOME/livekit-sdk-install" # build the examples against the local SDK -cmake -S . -B build -DLIVEKIT_LOCAL_SDK_DIR=$HOME/livekit-sdk-install +cmake -S . -B build -DLIVEKIT_LOCAL_SDK_DIR="$HOME/livekit-sdk-install" ``` - ### Building the examples + #### macOS / Linux + ```bash -cmake -S . -B build # add -DLIVEKIT_SDK_VERSION=0.2.0 if using a specific version +cmake -S . -B build cmake --build build ``` #### Windows (Visual Studio generator) + ```powershell -cmake -S . -B build # add -DLIVEKIT_SDK_VERSION=0.2.0 if using a specific version +cmake -S . -B build cmake --build build --config Release ``` -The Livekit Release SDK is downloaded into **build/_deps/livekit-sdk/** +The LiveKit Release SDK is downloaded into **build/_deps/livekit-sdk/** ### Running the examples After building, example binaries are located under: + ```bash build// ``` For example: + ```bash ./build/basic_room/basic_room --url --token ``` @@ -76,6 +105,7 @@ For example: ### Supported platforms Prebuilt SDKs are downloaded automatically for: + * Windows: x64 * macOS: x64, arm64 (Apple Silicon) * Linux: x64 diff --git a/token_source/CMakeLists.txt b/token_source/CMakeLists.txt new file mode 100644 index 0000000..098426a --- /dev/null +++ b/token_source/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright 2026 LiveKit, Inc. +# +# 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. + +# One executable per token source type. Each shares token_source_common.h for +# the logging delegate and the connect/observe session loop. + +# Resolve the SDK lib directory so -llivekit_ffi is found at link time. +get_filename_component(_lk_cmake_dir "${LiveKit_DIR}" DIRECTORY) # .../lib/cmake +get_filename_component(_lk_lib_dir "${_lk_cmake_dir}" DIRECTORY) # .../lib + +set(_token_source_examples + token_source_literal token_source_literal.cpp + token_source_endpoint token_source_endpoint.cpp + token_source_sandbox token_source_sandbox.cpp + token_source_custom token_source_custom.cpp + token_source_caching token_source_caching.cpp +) + +list(LENGTH _token_source_examples _count) +math(EXPR _last "${_count} - 1") +foreach(_i RANGE 0 ${_last} 2) + list(GET _token_source_examples ${_i} _target) + math(EXPR _src_idx "${_i} + 1") + list(GET _token_source_examples ${_src_idx} _src) + + add_executable(${_target} ${_src}) + target_include_directories(${_target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(${_target} PRIVATE ${LIVEKIT_CORE_TARGET}) + target_link_directories(${_target} PRIVATE "${_lk_lib_dir}") + + livekit_copy_windows_runtime_dlls(${_target}) +endforeach() + +unset(_token_source_examples) diff --git a/token_source/README.md b/token_source/README.md new file mode 100644 index 0000000..932868a --- /dev/null +++ b/token_source/README.md @@ -0,0 +1,125 @@ +# Token Source Examples + +These examples show the different ways to obtain the credentials +(**WebSocket URL** + **participant JWT**) the SDK needs to join a room. Each +example builds its own executable, constructs a single kind of +[`TokenSource`](https://github.com/livekit/client-sdk-cpp/blob/main/include/livekit/token_source.h), +connects to a room, logs participant join/leave for a few seconds, then +disconnects. + +> Token sources are for the **initial connection only**. Once connected, the +> LiveKit server refreshes the session token internally — your token source is +> not called again unless you connect again. See the SDK's +> [authentication docs](https://github.com/livekit/client-sdk-cpp/blob/main/docs/authentication.md). + +## Types + +| Type | Example | When to use | +| --- | --- | --- | +| `LiteralTokenSource` | `token_source_literal.cpp` | You already have a URL + JWT (minted out of band, e.g. `lk token create`). The SDK consumes them as-is. | +| `EndpointTokenSource` | `token_source_endpoint.cpp` | Recommended for production. The SDK POSTs request options to your backend token endpoint, which returns the URL + a fresh JWT. API keys stay server-side. | +| `SandboxTokenSource` | `token_source_sandbox.cpp` | Local development only. Uses LiveKit Cloud's sandbox token server. **Not for production.** | +| `CustomTokenSource` | `token_source_custom.cpp` | You have an internal auth/token system. Plug in your own async callback that returns credentials. | +| `CachingTokenSource` | `token_source_caching.cpp` | A decorator that adds JWT-aware caching around any configurable source (endpoint/sandbox/custom) to cut down on fetch calls. | + +`LiteralTokenSource` is *fixed* (no per-call options); the others are +*configurable* and accept +[`TokenRequestOptions`](https://github.com/livekit/client-sdk-cpp/blob/main/include/livekit/token_source.h) +(room name, participant identity, agent dispatch, ...). + +## Configuring the Examples + +All inputs come from environment variables, so no secrets or sandbox IDs are committed to source. + +| Variable | Used by | Notes | +| --- | --- | --- | +| `LIVEKIT_URL` | literal, custom | WebSocket URL, e.g. `ws://localhost:7880`. | +| `LIVEKIT_TOKEN` | literal, custom | Participant JWT. | +| `LIVEKIT_TOKEN_ENDPOINT` | endpoint, caching | Token endpoint URL. Default `http://127.0.0.1:3000/createToken`. | +| `LIVEKIT_TOKEN_ENDPOINT_METHOD` | endpoint, caching | Optional HTTP method (default `POST`). | +| `LIVEKIT_TOKEN_ENDPOINT_HEADERS` | endpoint, caching | Optional newline-separated `Name: Value` headers. | +| `LIVEKIT_SANDBOX_ID` | sandbox | Required. Sandbox ID from LiveKit Cloud. | +| `LIVEKIT_AGENT_NAME` | sandbox | Optional agent to dispatch into the room. | +| `LIVEKIT_AGENT_METADATA` | sandbox | Optional metadata for the dispatched agent. | + +These examples require LiveKit C++ SDK **v1.3.0** or newer. Build them with the +rest of the repo — see the root [README](../README.md#building-the-examples). + +## Running the Examples + +### Prerequisites + +#### LiveKit Server + +Start a development server via: + +```bash +livekit-server --dev +``` + +#### Token Sources + +For literal and custom, generate a development token with the +[LiveKit CLI](https://docs.livekit.io/home/cli/cli-setup/) (a dev server started +with `livekit-server --dev` uses `devkey` / `secret`): + +```bash +export LIVEKIT_TOKEN=$(lk token create \ + --api-key devkey --api-secret secret \ + -i my-participant --join --room my-room \ + --valid-for 24h --token-only) +``` + +For the endpoint and caching examples, run a local token server such as +[token-server-node](https://github.com/livekit-examples/token-server-node). This can be run via: + +```bash +cd /token-server-node +LIVEKIT_URL=ws://localhost:7880 LIVEKIT_API_KEY=devkey LIVEKIT_API_SECRET=secret PORT=3000 pnpm start +``` + +For sandbox, create a **Token Server** sandbox in [LiveKit Cloud](https://cloud.livekit.io) +(Sandbox → create from the token-server template), then copy the sandbox ID +(`token-server-xxxxxx`). See the +[sandbox token server docs](https://docs.livekit.io/frontends/build/authentication/sandbox-token-server/) +for setup details. Do not use this in production. + +```bash +export LIVEKIT_SANDBOX_ID=token-server-xxxxxx +# optional: dispatch a registered agent into the room with metadata +# export LIVEKIT_AGENT_NAME=my-agent +# export LIVEKIT_AGENT_METADATA='{"greeting": "hello from cpp"}' +``` + +### Executing + +The built binaries live under `build/token_source/`: + +```bash +# Literal: bring your own URL + token +export LIVEKIT_URL=ws://localhost:7880 +export LIVEKIT_TOKEN= +./build/token_source/token_source_literal +``` + +```bash +# Endpoint / caching: point at your token endpoint +export LIVEKIT_TOKEN_ENDPOINT=http://127.0.0.1:3000/createToken +./build/token_source/token_source_endpoint +./build/token_source/token_source_caching +``` + +```bash +# Sandbox: development-only, ID from the environment +export LIVEKIT_SANDBOX_ID= +export LIVEKIT_AGENT_NAME= # optional +export LIVEKIT_AGENT_METADATA= # optional +./build/token_source/token_source_sandbox +``` + +```bash +# Custom: callback returns credentials (this example reads the env) +export LIVEKIT_URL=ws://localhost:7880 +export LIVEKIT_TOKEN= +./build/token_source/token_source_custom +``` diff --git a/token_source/token_source_caching.cpp b/token_source/token_source_caching.cpp new file mode 100644 index 0000000..30ceb3b --- /dev/null +++ b/token_source/token_source_caching.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2026 LiveKit, Inc. + * + * 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. + */ + +// Caching token source: a decorator that adds JWT-aware caching to any +// configurable token source (endpoint, sandbox, or custom). +// +// Repeated fetches for the same request options reuse the cached token until it +// nears expiry or you call invalidate() to drop the cached credentials, cutting +// down calls to your backend. This example wraps an EndpointTokenSource. +// +// Environment: +// LIVEKIT_TOKEN_ENDPOINT Token endpoint URL +// (default http://127.0.0.1:3000/createToken) +// LIVEKIT_TOKEN_ENDPOINT_METHOD Optional HTTP method (default POST) +// LIVEKIT_TOKEN_ENDPOINT_HEADERS Optional newline-separated "Name: Value" headers + +#include +#include + +#include +#include +#include + +#include "token_source_common.h" + +namespace { + +using namespace token_source_example; + +bool cachingTokenSourceConnect() { + std::string endpoint_url = getenvOrEmpty("LIVEKIT_TOKEN_ENDPOINT"); + if (endpoint_url.empty()) { + endpoint_url = "http://127.0.0.1:3000/createToken"; + } + + std::cout << "Caching token source wrapping endpoint: " << endpoint_url << "\n"; + + // Build the inner source, then wrap it. CachingTokenSource::create takes + // ownership of the inner source via unique_ptr. + auto inner = livekit::EndpointTokenSource::create(endpoint_url, endpointOptionsFromEnv()); + auto token_source = livekit::CachingTokenSource::create(std::move(inner)); + + livekit::TokenRequestOptions request_options; + request_options.participant_identity = "robot-a"; + const auto credentials = token_source->fetch(request_options).get(); + if (!credentials) { + std::cerr << "Failed to fetch credentials: " << credentials.error().message << "\n"; + return false; + } + + livekit::Room room; + ParticipantLogDelegate delegate; + room.setDelegate(&delegate); + if (!room.connect(credentials.value().server_url, credentials.value().participant_token, livekit::RoomOptions())) { + std::cerr << "Failed to connect to room\n"; + return false; + } + std::cout << "Connected to room: " << room.roomInfo().name << " (caching token source)\n"; + + return runConnectedSession(room); +} + +} // namespace + +int main() { + livekit::initialize(livekit::LogLevel::Info); + const bool ok = cachingTokenSourceConnect(); + livekit::shutdown(); + return ok ? 0 : 1; +} diff --git a/token_source/token_source_common.h b/token_source/token_source_common.h new file mode 100644 index 0000000..0a7026b --- /dev/null +++ b/token_source/token_source_common.h @@ -0,0 +1,174 @@ +/* + * Copyright 2026 LiveKit, Inc. + * + * 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. + */ + +// Shared helpers for the token_source examples. +// +// Each example focuses on constructing a single kind of token source; the +// logging delegate, the "connect then observe" session loop, and the small +// environment-variable helpers are factored out here so the per-type files stay +// short and highlight only the token-source-specific code. + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace token_source_example { + +using namespace std::chrono_literals; + +// How long to stay connected so participant join/leave events can be observed. +constexpr auto kObserveDuration = 5s; + +/// Returns the value of an environment variable, or an empty string when unset. +inline std::string getenvOrEmpty(const char* name) { + const char* value = std::getenv(name); + return (value == nullptr) ? std::string() : std::string(value); +} + +/// Reads a required environment variable. Prints an error and returns false +/// when it is unset or empty. +inline bool requireEnv(const char* name, std::string& out) { + out = getenvOrEmpty(name); + if (out.empty()) { + std::cerr << name << " not set\n"; + return false; + } + return true; +} + +/// Trims leading and trailing ASCII whitespace. +inline std::string trimWhitespace(const std::string& value) { + std::size_t begin = 0; + std::size_t end = value.size(); + while (begin < end && std::isspace(static_cast(value[begin])) != 0) { + ++begin; + } + while (end > begin && std::isspace(static_cast(value[end - 1])) != 0) { + --end; + } + return value.substr(begin, end - begin); +} + +/// Renders a participant display name, falling back to "" when empty. +inline std::string displayName(const std::string& name) { return name.empty() ? "" : name; } + +/// Standard one-line description of a participant (local or remote). +inline std::string formatParticipant(const livekit::Participant& participant) { + return "identity=" + participant.identity() + ", name=" + displayName(participant.name()); +} + +/// Minimal delegate that logs remote participant join/leave activity. +class ParticipantLogDelegate : public livekit::RoomDelegate { +public: + void onParticipantConnected(livekit::Room& /*room*/, const livekit::ParticipantConnectedEvent& event) override { + if (event.participant == nullptr) { + return; + } + std::cout << "Participant connected: " << formatParticipant(*event.participant) << "\n"; + } + + void onParticipantDisconnected(livekit::Room& /*room*/, + const livekit::ParticipantDisconnectedEvent& event) override { + if (event.participant == nullptr) { + return; + } + std::cout << "Participant disconnected: identity=" << event.participant->identity() << "\n"; + } +}; + +inline void logRemoteParticipants(const livekit::Room& room) { + const auto participants = room.remoteParticipants(); + std::cout << "Remote participants currently in room: " << participants.size() << "\n"; + for (const auto& participant_weak : participants) { + if (const auto participant = participant_weak.lock()) { + std::cout << " - " << formatParticipant(*participant) << "\n"; + } + } +} + +/// Logs local/remote participants, stays connected briefly so join/leave events +/// surface, then disconnects gracefully. Returns false on any failure. +inline bool runConnectedSession(livekit::Room& room) { + const auto local_participant = room.localParticipant().lock(); + if (!local_participant) { + std::cerr << "Failed to get local participant\n"; + return false; + } + std::cout << "Local participant info: " << formatParticipant(*local_participant) << "\n"; + + logRemoteParticipants(room); + + // Stay connected briefly so participant join/leave events are surfaced. + std::this_thread::sleep_for(kObserveDuration); + logRemoteParticipants(room); + + if (!room.disconnect()) { + std::cerr << "Failed to gracefully disconnect from room\n"; + return false; + } + + std::cout << "Disconnected from room\n"; + return true; +} + +/// Parses HTTP transport options for EndpointTokenSource from the environment. +/// +/// LIVEKIT_TOKEN_ENDPOINT_METHOD - optional HTTP method (default POST). +/// LIVEKIT_TOKEN_ENDPOINT_HEADERS - optional newline-separated "Name: Value" +/// pairs, e.g. "Authorization: Bearer ...". +inline livekit::TokenEndpointOptions endpointOptionsFromEnv() { + livekit::TokenEndpointOptions options; + + if (const std::string method = getenvOrEmpty("LIVEKIT_TOKEN_ENDPOINT_METHOD"); !method.empty()) { + options.method = method; + } + + const std::string headers_text = getenvOrEmpty("LIVEKIT_TOKEN_ENDPOINT_HEADERS"); + if (headers_text.empty()) { + return options; + } + + std::size_t start = 0; + while (start <= headers_text.size()) { + const std::size_t newline = headers_text.find('\n', start); + const std::string line = + headers_text.substr(start, newline == std::string::npos ? std::string::npos : newline - start); + const std::size_t colon = line.find(':'); + if (colon != std::string::npos) { + const std::string name = trimWhitespace(line.substr(0, colon)); + const std::string value = trimWhitespace(line.substr(colon + 1)); + if (!name.empty()) { + options.headers[name] = value; + } + } + if (newline == std::string::npos) { + break; + } + start = newline + 1; + } + + return options; +} + +} // namespace token_source_example diff --git a/token_source/token_source_custom.cpp b/token_source/token_source_custom.cpp new file mode 100644 index 0000000..558b802 --- /dev/null +++ b/token_source/token_source_custom.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2026 LiveKit, Inc. + * + * 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. + */ + +// Custom token source: plug your own async token-fetching logic into the SDK. +// +// Use this when you already have an internal auth/token system and want to +// integrate it without adopting the standardized token-endpoint format. Your +// callback receives the per-call TokenRequestOptions and returns a future of +// credentials. +// +// This example's callback simulates a private backend by reading credentials +// from the environment, but the body could call any internal service. +// +// Environment: +// LIVEKIT_URL WebSocket URL of the LiveKit server +// LIVEKIT_TOKEN Participant JWT + +#include +#include +#include + +#include +#include +#include + +#include "token_source_common.h" + +namespace { + +using namespace token_source_example; + +using TokenResult = livekit::Result; + +/// Wraps an already-computed value in a ready std::future. +std::future makeReadyFuture(TokenResult result) { + std::promise promise; + promise.set_value(std::move(result)); + return promise.get_future(); +} + +bool customTokenSourceConnect() { + // The callback is invoked for each fetch with the request options. Here we + // pretend to call an internal service; in a real app this is where you would + // talk to your own auth backend. + auto token_source = livekit::CustomTokenSource::create( + [](const livekit::TokenRequestOptions& options) -> std::future { + (void)options; // A real backend would honor these (room, identity, ...). + + const std::string url = getenvOrEmpty("LIVEKIT_URL"); + const std::string token = getenvOrEmpty("LIVEKIT_TOKEN"); + if (url.empty() || token.empty()) { + return makeReadyFuture(TokenResult::failure( + livekit::TokenSourceError{"LIVEKIT_URL and LIVEKIT_TOKEN must be set for the custom example"})); + } + + livekit::TokenSourceResponse response; + response.server_url = url; + response.participant_token = token; + return makeReadyFuture(TokenResult::success(std::move(response))); + }); + const auto credentials = token_source->fetch(livekit::TokenRequestOptions()).get(); + if (!credentials) { + std::cerr << "Failed to fetch credentials: " << credentials.error().message << "\n"; + return false; + } + + livekit::Room room; + ParticipantLogDelegate delegate; + room.setDelegate(&delegate); + if (!room.connect(credentials.value().server_url, credentials.value().participant_token, livekit::RoomOptions())) { + std::cerr << "Failed to connect to room\n"; + return false; + } + std::cout << "Connected to room: " << room.roomInfo().name << " (custom token source)\n"; + + return runConnectedSession(room); +} + +} // namespace + +int main() { + livekit::initialize(livekit::LogLevel::Info); + const bool ok = customTokenSourceConnect(); + livekit::shutdown(); + return ok ? 0 : 1; +} diff --git a/token_source/token_source_endpoint.cpp b/token_source/token_source_endpoint.cpp new file mode 100644 index 0000000..dc9ce7b --- /dev/null +++ b/token_source/token_source_endpoint.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2026 LiveKit, Inc. + * + * 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. + */ + +// Endpoint token source: fetch credentials from your own backend token endpoint. +// +// This is the recommended pattern for production: API keys stay server-side and +// the SDK POSTs request options (room, identity, ...) to your endpoint, which +// returns the server URL and a freshly minted JWT. +// +// Environment: +// LIVEKIT_TOKEN_ENDPOINT Token endpoint URL +// (default http://127.0.0.1:3000/createToken) +// LIVEKIT_TOKEN_ENDPOINT_METHOD Optional HTTP method (default POST) +// LIVEKIT_TOKEN_ENDPOINT_HEADERS Optional newline-separated "Name: Value" headers, +// e.g. "Authorization: Bearer abc123" + +#include +#include + +#include +#include +#include + +#include "token_source_common.h" + +namespace { + +using namespace token_source_example; + +bool endpointTokenSourceConnect() { + std::string endpoint_url = getenvOrEmpty("LIVEKIT_TOKEN_ENDPOINT"); + if (endpoint_url.empty()) { + endpoint_url = "http://127.0.0.1:3000/createToken"; + } + + auto endpoint_options = endpointOptionsFromEnv(); + std::cout << "Endpoint token source: " << endpoint_options.method << " " << endpoint_url << " (" + << endpoint_options.headers.size() << " custom header(s))\n"; + + auto token_source = livekit::EndpointTokenSource::create(endpoint_url, std::move(endpoint_options)); + + // These options are sent to your endpoint, which embeds them into the JWT. + livekit::TokenRequestOptions request_options; + request_options.participant_identity = "robot-a"; + const auto credentials = token_source->fetch(request_options).get(); + if (!credentials) { + std::cerr << "Failed to fetch credentials: " << credentials.error().message << "\n"; + return false; + } + + livekit::Room room; + ParticipantLogDelegate delegate; + room.setDelegate(&delegate); + if (!room.connect(credentials.value().server_url, credentials.value().participant_token, livekit::RoomOptions())) { + std::cerr << "Failed to connect to room\n"; + return false; + } + std::cout << "Connected to room: " << room.roomInfo().name << " (endpoint token source)\n"; + + return runConnectedSession(room); +} + +} // namespace + +int main() { + livekit::initialize(livekit::LogLevel::Info); + const bool ok = endpointTokenSourceConnect(); + livekit::shutdown(); + return ok ? 0 : 1; +} diff --git a/token_source/token_source_literal.cpp b/token_source/token_source_literal.cpp new file mode 100644 index 0000000..13bcb46 --- /dev/null +++ b/token_source/token_source_literal.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2026 LiveKit, Inc. + * + * 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. + */ + +// Literal token source: connect with a server URL and JWT you already have. +// +// Use this when your app mints or fetches tokens out of band (e.g. with +// `lk token create`) and just wants the SDK to consume them as-is. The room, +// identity, and grants all come from the token itself. +// +// Environment: +// LIVEKIT_URL WebSocket URL of the LiveKit server (e.g. ws://localhost:7880) +// LIVEKIT_TOKEN Participant JWT + +#include +#include + +#include +#include + +#include "token_source_common.h" + +namespace { + +using namespace token_source_example; + +bool literalTokenSourceConnect() { + std::string url; + std::string token; + if (!requireEnv("LIVEKIT_URL", url) || !requireEnv("LIVEKIT_TOKEN", token)) { + return false; + } + + // Each fetch() returns these exact credentials; nothing is requested over the + // network. Room and participant identity are encoded in the token. + auto token_source = livekit::LiteralTokenSource::create(url, token); + const auto credentials = token_source->fetch().get(); + if (!credentials) { + std::cerr << "Failed to fetch credentials: " << credentials.error().message << "\n"; + return false; + } + + livekit::Room room; + ParticipantLogDelegate delegate; + room.setDelegate(&delegate); + if (!room.connect(credentials.value().server_url, credentials.value().participant_token, livekit::RoomOptions())) { + std::cerr << "Failed to connect to room\n"; + return false; + } + std::cout << "Connected to room: " << room.roomInfo().name << " (literal token source)\n"; + + return runConnectedSession(room); +} + +} // namespace + +int main() { + livekit::initialize(livekit::LogLevel::Info); + const bool ok = literalTokenSourceConnect(); + livekit::shutdown(); + return ok ? 0 : 1; +} diff --git a/token_source/token_source_sandbox.cpp b/token_source/token_source_sandbox.cpp new file mode 100644 index 0000000..4fb0445 --- /dev/null +++ b/token_source/token_source_sandbox.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2026 LiveKit, Inc. + * + * 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. + */ + +// Sandbox token source: use LiveKit Cloud's sandbox token server for quick +// local development. Do not use in production. +// +// The sandbox ID is read from the environment (never hard-coded) so this +// example does not link a personal sandbox into source control. +// +// Environment: +// LIVEKIT_SANDBOX_ID Sandbox identifier from LiveKit Cloud (required) +// LIVEKIT_AGENT_NAME Optional registered agent to dispatch into the room +// LIVEKIT_AGENT_METADATA Optional metadata passed to the dispatched agent + +#include +#include + +#include +#include + +#include "token_source_common.h" + +namespace { + +using namespace token_source_example; + +bool sandboxTokenSourceConnect() { + std::string sandbox_id; + if (!requireEnv("LIVEKIT_SANDBOX_ID", sandbox_id)) { + return false; + } + + // POSTs to cloud-api.livekit.io/api/v2/sandbox/connection-details with + // X-Sandbox-ID set from LIVEKIT_SANDBOX_ID. + auto token_source = livekit::SandboxTokenSource::create(sandbox_id); + + livekit::TokenRequestOptions request_options; + request_options.participant_identity = "robot-a"; + + // Optional agent dispatch: when LIVEKIT_AGENT_NAME is set, the request embeds + // room_config.agents so the token server dispatches a named agent. + if (const std::string agent_name = getenvOrEmpty("LIVEKIT_AGENT_NAME"); !agent_name.empty()) { + request_options.agent_name = agent_name; + if (const std::string agent_metadata = getenvOrEmpty("LIVEKIT_AGENT_METADATA"); !agent_metadata.empty()) { + request_options.agent_metadata = agent_metadata; + } + std::cout << "Requesting sandbox token with agent dispatch: agent_name=" << *request_options.agent_name << "\n"; + } + const auto credentials = token_source->fetch(request_options).get(); + if (!credentials) { + std::cerr << "Failed to fetch credentials: " << credentials.error().message << "\n"; + return false; + } + + livekit::Room room; + ParticipantLogDelegate delegate; + room.setDelegate(&delegate); + if (!room.connect(credentials.value().server_url, credentials.value().participant_token, livekit::RoomOptions())) { + std::cerr << "Failed to connect to room\n"; + return false; + } + std::cout << "Connected to room: " << room.roomInfo().name << " (sandbox token source)\n"; + + return runConnectedSession(room); +} + +} // namespace + +int main() { + livekit::initialize(livekit::LogLevel::Info); + const bool ok = sandboxTokenSourceConnect(); + livekit::shutdown(); + return ok ? 0 : 1; +}