Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,12 @@ jobs:
- name: Start livekit-server
if: matrix.e2e-testing
id: livekit_server
uses: livekit/dev-server-action@61e2b4dcb170dd3591e0c9b0db3c3fe5db93b500
uses: livekit/dev-server-action@6562d7d9343e46c26ead1223151f64f00e4fc37f # v1.1.0
with:
github-token: ${{ github.token }}
version: v1.13.3
config: |
enable_participant_data_blob: true

# Needed by token helper script
- name: Install livekit-cli
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ add_library(livekit SHARED
src/data_track_frame.cpp
src/data_stream.cpp
src/data_track_error.cpp
src/data_track_schema.cpp
src/data_track_stream.cpp
src/e2ee.cpp
src/ffi_handle.cpp
Expand Down
2 changes: 1 addition & 1 deletion client-sdk-rust
17 changes: 13 additions & 4 deletions include/livekit/data_track_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,32 @@

#pragma once

#include <optional>
#include <string>

#include "livekit/data_track_schema.h"

namespace livekit {

/// Metadata about a published data track.
/// @brief Metadata about a published data track.
///
/// Unlike audio/video tracks, data tracks are not part of the Track class
/// hierarchy. They carry their own lightweight info struct.
struct DataTrackInfo {
/// Publisher-assigned track name (unique per publisher).
/// @brief Publisher-assigned track name, unique per publisher.
std::string name;

/// SFU-assigned track identifier.
/// @brief SFU-assigned track identifier.
std::string sid;

/// Whether frames on this track use end-to-end encryption.
/// @brief Whether frames on this track use end-to-end encryption.
bool uses_e2ee = false;

/// @brief Schema associated with frames sent on the track, if any.
std::optional<DataTrackSchemaId> schema;

/// @brief Encoding of frames sent on the track, if specified.
std::optional<DataTrackFrameEncoding> frame_encoding;
};

} // namespace livekit
43 changes: 43 additions & 0 deletions include/livekit/data_track_options.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.
*/

#pragma once

#include <optional>
#include <string>

#include "livekit/data_track_schema.h"

namespace livekit {

/// @brief Options for publishing a data track.
///
/// The schema and frame encoding are optional metadata advertised to
/// subscribers; they are surfaced on the subscriber side via DataTrackInfo.
struct DataTrackPublishOptions {
/// @brief Track name used to identify the track to other participants.
///
/// Must not be empty and must be unique per publisher.
std::string name;

/// @brief Schema describing frames sent on the track, if any.
std::optional<DataTrackSchemaId> schema;

/// @brief Encoding of frames sent on the track, if any.
std::optional<DataTrackFrameEncoding> frame_encoding;
};

} // namespace livekit
206 changes: 206 additions & 0 deletions include/livekit/data_track_schema.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/*
* 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.
*/

#pragma once

#include <string>
#include <utility>

namespace livekit {

/// @brief Encoding used to interpret a data track schema definition.
///
/// Identifies the interface definition language the schema is written in (for
/// example, a `.proto` file for @ref DataTrackSchemaEncoding::Protobuf), which
/// in turn dictates the wire format of the frames the schema describes.
///
/// Almost all schemas use a well-known encoding, which converts implicitly:
/// @code
/// DataTrackSchemaEncoding encoding = DataTrackSchemaEncoding::Protobuf;
/// @endcode
/// For the uncommon case of an encoding outside the well-known set, use
/// @ref DataTrackSchemaEncoding::custom.
class DataTrackSchemaEncoding {
public:
/// @brief Well-known schema encodings.
enum WellKnown {
/// @brief Protocol Buffer IDL.
Protobuf,
/// @brief FlatBuffer IDL.
Flatbuffer,
/// @brief ROS 1 Message.
Ros1Msg,
/// @brief ROS 2 Message.
Ros2Msg,
/// @brief ROS 2 IDL.
Ros2Idl,
/// @brief OMG IDL.
OmgIdl,
/// @brief JSON Schema.
JsonSchema,
/// @brief Another well-known encoding not known to this client version.
Other,
};

/// @brief Construct a well-known encoding.
///
/// The constructor is intentionally implicit for the common well-known case.
DataTrackSchemaEncoding(WellKnown wellKnown) : well_known_(wellKnown) {}

/// @brief Construct a custom, application-defined encoding.
///
/// Prefer a well-known encoding wherever one applies. The identifier must be
/// non-empty and no longer than 25 characters.
///
/// @param identifier Custom encoding identifier.
/// @return Custom schema encoding.
static DataTrackSchemaEncoding custom(std::string identifier) {
DataTrackSchemaEncoding encoding;
encoding.custom_ = std::move(identifier);
return encoding;
}

/// @brief Check whether this is a custom encoding.
///
/// @return true if this is a custom encoding rather than a well-known one.
bool isCustom() const { return !custom_.empty(); }

/// @brief Get the well-known encoding.
///
/// @return The well-known encoding. Only meaningful when @ref isCustom is false.
WellKnown wellKnown() const { return well_known_; }

/// @brief Get the custom identifier.
///
/// @return The custom identifier. Empty when @ref isCustom is false.
const std::string& customIdentifier() const { return custom_; }

private:
DataTrackSchemaEncoding() = default;

WellKnown well_known_ = Other;
std::string custom_;
};

inline bool operator==(const DataTrackSchemaEncoding& a, const DataTrackSchemaEncoding& b) {
if (a.isCustom() || b.isCustom()) {
return a.customIdentifier() == b.customIdentifier();
}
return a.wellKnown() == b.wellKnown();
}
inline bool operator!=(const DataTrackSchemaEncoding& a, const DataTrackSchemaEncoding& b) { return !(a == b); }

/// @brief Encoding used for frames sent on a data track.
///
/// The serialization format of the frame bytes (for example,
/// @ref DataTrackFrameEncoding::Protobuf); the structure of those bytes is
/// described by a schema (see @ref DataTrackSchemaEncoding).
///
/// Almost all tracks use a well-known encoding, which converts implicitly:
/// @code
/// options.frame_encoding = DataTrackFrameEncoding::Json;
/// @endcode
/// For the uncommon case of an encoding outside the well-known set, use
/// @ref DataTrackFrameEncoding::custom.
class DataTrackFrameEncoding {
public:
/// @brief Well-known frame encodings.
enum WellKnown {
/// @brief ROS 1, described by a Ros1Msg schema.
Ros1,
/// @brief CDR, described by a Ros2Msg, Ros2Idl, or OmgIdl schema.
Cdr,
/// @brief Protocol Buffer, described by a Protobuf schema.
Protobuf,
/// @brief FlatBuffer, described by a Flatbuffer schema.
Flatbuffer,
/// @brief CBOR, self-describing.
Cbor,
/// @brief MessagePack, self-describing.
Msgpack,
/// @brief JSON, self-describing or described by a JsonSchema schema.
Json,
/// @brief Another well-known encoding not known to this client version.
Other,
};

/// @brief Construct a well-known encoding.
///
/// The constructor is intentionally implicit for the common well-known case.
DataTrackFrameEncoding(WellKnown wellKnown) : well_known_(wellKnown) {}

/// @brief Construct a custom, application-defined encoding.
///
/// Prefer a well-known encoding wherever one applies. The identifier must be
/// non-empty and no longer than 25 characters.
///
/// @param identifier Custom encoding identifier.
/// @return Custom frame encoding.
static DataTrackFrameEncoding custom(std::string identifier) {
DataTrackFrameEncoding encoding;
encoding.custom_ = std::move(identifier);
return encoding;
}

/// @brief Check whether this is a custom encoding.
///
/// @return true if this is a custom encoding rather than a well-known one.
bool isCustom() const { return !custom_.empty(); }

/// @brief Get the well-known encoding.
///
/// @return The well-known encoding. Only meaningful when @ref isCustom is false.
WellKnown wellKnown() const { return well_known_; }

/// @brief Get the custom identifier.
///
/// @return The custom identifier. Empty when @ref isCustom is false.
const std::string& customIdentifier() const { return custom_; }

private:
DataTrackFrameEncoding() = default;

WellKnown well_known_ = Other;
std::string custom_;
};

inline bool operator==(const DataTrackFrameEncoding& a, const DataTrackFrameEncoding& b) {
if (a.isCustom() || b.isCustom()) {
return a.customIdentifier() == b.customIdentifier();
}
return a.wellKnown() == b.wellKnown();
}
inline bool operator!=(const DataTrackFrameEncoding& a, const DataTrackFrameEncoding& b) { return !(a == b); }

/// @brief Uniquely identifies a data track schema.
///
/// A compound identifier with two components: a name and an encoding. Two IDs
/// are equal only if both components match; the same name with a different
/// encoding refers to a distinct schema.
struct DataTrackSchemaId {
/// @brief Name component of the schema identifier.
std::string name;

/// @brief Encoding of the schema definition.
DataTrackSchemaEncoding encoding = DataTrackSchemaEncoding::Other;
};

inline bool operator==(const DataTrackSchemaId& a, const DataTrackSchemaId& b) {
return a.name == b.name && a.encoding == b.encoding;
}
inline bool operator!=(const DataTrackSchemaId& a, const DataTrackSchemaId& b) { return !(a == b); }

} // namespace livekit
47 changes: 47 additions & 0 deletions include/livekit/local_participant.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include <unordered_map>
#include <vector>

#include "livekit/data_track_options.h"
#include "livekit/data_track_schema.h"
#include "livekit/ffi_handle.h"
#include "livekit/local_audio_track.h"
#include "livekit/local_data_track.h"
Expand Down Expand Up @@ -172,6 +174,18 @@ class LIVEKIT_API LocalParticipant : public Participant {
/// publication failed.
Result<std::shared_ptr<LocalDataTrack>, PublishDataTrackError> publishDataTrack(const std::string& name);

/// Publish a data track to the room with explicit options.
///
/// Like publishDataTrack(const std::string&), but also lets the publisher
/// advertise an optional schema and frame encoding as metadata. These are
/// surfaced to subscribers via the remote DataTrackInfo.
///
/// @param options Track name plus optional schema / frame encoding.
/// @return The published track on success, or a typed error describing why
/// publication failed.
Result<std::shared_ptr<LocalDataTrack>, PublishDataTrackError> publishDataTrack(
const DataTrackPublishOptions& options);

/// Unpublish a data track from the room.
///
/// Delegates to LocalDataTrack::unpublishDataTrack(). After this call,
Expand All @@ -180,6 +194,39 @@ class LIVEKIT_API LocalParticipant : public Participant {
/// @param track The data track to unpublish. Null is ignored.
void unpublishDataTrack(const std::shared_ptr<LocalDataTrack>& track);

/// Store the definition of a data track schema.
///
/// Called by a publisher to make a schema available to subscribers, who can
/// later look up its definition via getSchema(). Define a schema before
/// publishing any data track that references it, so subscribers can resolve
/// the schema by its ID.
///
/// A schema can only be defined once. Attempting to redefine an existing
/// schema fails.
///
/// @param id Identifies the schema (name and encoding).
/// @param definition The schema definition, stored as-is. It is neither
/// parsed nor validated against its encoding, so the
/// caller is responsible for ensuring it is well-formed.
///
/// @throws std::runtime_error If the schema could not be defined or the FFI
/// call fails.
void defineSchema(const DataTrackSchemaId& id, const std::string& definition);

/// Retrieve the definition for a data track schema.
///
/// Called by a subscriber that wants to inspect the schema a participant
/// defined (see defineSchema()) for a data track it is publishing.
///
/// @param id Identifies the schema to retrieve.
/// @param participant_identity Identity of the participant that defined the
/// schema.
/// @return The schema definition.
///
/// @throws std::runtime_error If the participant has not defined a schema
/// with this ID or the FFI call fails.
std::string getSchema(const DataTrackSchemaId& id, const std::string& participant_identity);

/// Initiate an RPC call to a remote participant.
///
/// @param destination_identity Identity of the destination participant.
Expand Down
Loading
Loading