Skip to content

Commit 2cbf9a3

Browse files
Data track schema metadata (#214)
Adds C++ support for data track schema metadata, updating to upstream Rust livekit-ffi/v0.12.72 (06371a33) release update. API changes: * Adds schema types (DataTrackSchemaId, DataTrackSchemaEncoding, DataTrackFrameEncoding) and optional metadata on DataTrackPublishOptions / DataTrackInfo * Adds methods LocalParticipant::defineSchema() and getSchema()` for storing and retrieving schema definitions via room data blobs Other changes: * Bumps client-sdk-rust to the published release that ships schema metadata support * Updates CI e2e setup to dev-server-action@v1.1.0 with enable_participant_data_blob: true so schema storage tests can run * Prevents pre-release builds from publishing docs to production link
1 parent d604e97 commit 2cbf9a3

23 files changed

Lines changed: 1045 additions & 36 deletions

.github/workflows/make-release.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -445,11 +445,16 @@ jobs:
445445
env:
446446
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
447447

448-
# ---------- Publish Docs Job ----------
449-
publish-docs:
450-
name: Publish Documentation
448+
# Build docs for validation and retain them as a workflow artifact. Publishing
449+
# is triggered separately by the GitHub release's authoritative state.
450+
generate-docs:
451+
name: Generate Documentation
451452
needs: release
452-
uses: ./.github/workflows/publish-docs.yml
453+
permissions:
454+
contents: read
455+
actions: read
456+
uses: ./.github/workflows/generate-docs.yml
453457
with:
454458
version: ${{ needs.release.outputs.version }}
455-
secrets: inherit
459+
upload_artifact: true
460+
artifact_name: livekit-cpp-docs-${{ github.run_id }}

.github/workflows/publish-docs.yml

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
name: Publish docs
22

33
on:
4-
workflow_dispatch:
5-
inputs:
6-
version:
7-
description: 'Documentation version (e.g. v0.1.0)'
8-
required: false
9-
type: string
10-
workflow_call:
11-
inputs:
12-
version:
13-
description: 'Documentation version (e.g. v0.1.0)'
14-
required: false
15-
type: string
4+
release:
5+
# "published" covers newly published stable releases and prereleases.
6+
# "released" also catches promotion of an existing prerelease to stable.
7+
types: [published, released]
168

179
permissions:
1810
contents: read
@@ -23,7 +15,7 @@ jobs:
2315
name: Validate (build docs)
2416
uses: ./.github/workflows/generate-docs.yml
2517
with:
26-
version: ${{ inputs.version || github.event.inputs.version || '' }}
18+
version: ${{ github.event.release.tag_name }}
2719
upload_artifact: true
2820
# Suffix with run_id so concurrent publish runs cannot collide on the
2921
# artifact namespace within the same repository.
@@ -32,6 +24,9 @@ jobs:
3224
publish:
3325
name: Publish (S3 + CloudFront)
3426
needs: validate
27+
# The GitHub release is the source of truth. Drafts do not emit these
28+
# events until published; prereleases validate docs but never deploy them.
29+
if: github.event.release.draft == false && github.event.release.prerelease == false
3530
runs-on: ubuntu-latest
3631
steps:
3732
- name: Download docs artifact

.github/workflows/tests.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,12 @@ jobs:
342342
- name: Start livekit-server
343343
if: matrix.e2e-testing && (inputs.integration_repeat > 0 || inputs.run_stress_tests)
344344
id: livekit_server
345-
uses: livekit/dev-server-action@61e2b4dcb170dd3591e0c9b0db3c3fe5db93b500
345+
uses: livekit/dev-server-action@6562d7d9343e46c26ead1223151f64f00e4fc37f # v1.1.0
346346
with:
347347
github-token: ${{ github.token }}
348+
version: v1.13.3
349+
config: |
350+
enable_participant_data_blob: true
348351
349352
# Needed by token helper script
350353
- name: Install livekit-cli

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ add_library(livekit SHARED
378378
src/data_track_frame.cpp
379379
src/data_stream.cpp
380380
src/data_track_error.cpp
381+
src/data_track_schema.cpp
381382
src/data_track_stream.cpp
382383
src/e2ee.cpp
383384
src/ffi_handle.cpp

client-sdk-rust

include/livekit/data_track_error.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ enum class PublishDataTrackErrorCode : std::uint32_t {
4141
LIMIT_REACHED = 7,
4242
PROTOCOL_ERROR = 8,
4343
INTERNAL = 9,
44+
INVALID_SCHEMA = 10,
4445
};
4546

4647
/// @brief Error details returned when publishing a local data track fails.

include/livekit/data_track_info.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616

1717
#pragma once
1818

19+
#include <optional>
1920
#include <string>
2021

22+
#include "livekit/data_track_schema.h"
23+
2124
namespace livekit {
2225

2326
/// Metadata about a published data track.
@@ -33,6 +36,12 @@ struct DataTrackInfo {
3336

3437
/// Whether frames on this track use end-to-end encryption.
3538
bool uses_e2ee = false;
39+
40+
/// Schema associated with frames sent on the track, if any.
41+
std::optional<DataTrackSchemaId> schema;
42+
43+
/// Encoding of frames sent on the track, if specified.
44+
std::optional<DataTrackFrameEncoding> frame_encoding;
3645
};
3746

3847
} // namespace livekit
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2026 LiveKit
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <optional>
20+
#include <string>
21+
22+
#include "livekit/data_track_schema.h"
23+
24+
namespace livekit {
25+
26+
/// @brief Options for publishing a data track.
27+
///
28+
/// The schema and frame encoding are optional metadata advertised to
29+
/// subscribers; they are surfaced on the subscriber side via DataTrackInfo.
30+
struct DataTrackPublishOptions {
31+
/// Track name used to identify the track to other participants.
32+
///
33+
/// Must not be empty and must be unique per publisher.
34+
std::string name;
35+
36+
/// Schema describing frames sent on the track, if any.
37+
std::optional<DataTrackSchemaId> schema;
38+
39+
/// Encoding of frames sent on the track, if any.
40+
std::optional<DataTrackFrameEncoding> frame_encoding;
41+
};
42+
43+
} // namespace livekit
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Copyright 2026 LiveKit
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <string>
20+
#include <utility>
21+
22+
namespace livekit {
23+
24+
/// @brief Encoding used to interpret a data track schema definition.
25+
///
26+
/// Identifies the interface definition language the schema is written in (for
27+
/// example, a `.proto` file for @ref DataTrackSchemaEncoding::Protobuf), which
28+
/// in turn dictates the wire format of the frames the schema describes.
29+
///
30+
/// Almost all schemas use a well-known encoding, which converts implicitly:
31+
/// @code
32+
/// DataTrackSchemaEncoding encoding = DataTrackSchemaEncoding::Protobuf;
33+
/// @endcode
34+
/// For the uncommon case of an encoding outside the well-known set, use
35+
/// @ref DataTrackSchemaEncoding::custom.
36+
class DataTrackSchemaEncoding {
37+
public:
38+
/// Well-known schema encodings.
39+
enum WellKnown {
40+
/// Protocol Buffer IDL.
41+
Protobuf,
42+
/// FlatBuffer IDL.
43+
Flatbuffer,
44+
/// ROS 1 Message.
45+
Ros1Msg,
46+
/// ROS 2 Message.
47+
Ros2Msg,
48+
/// ROS 2 IDL.
49+
Ros2Idl,
50+
/// OMG IDL.
51+
OmgIdl,
52+
/// JSON Schema.
53+
JsonSchema,
54+
/// Another well-known encoding not known to this client version.
55+
Other,
56+
};
57+
58+
/// Construct a well-known encoding.
59+
///
60+
/// The constructor is intentionally implicit for the common well-known case.
61+
///
62+
/// @param well_known Well-known schema encoding.
63+
DataTrackSchemaEncoding(WellKnown well_known) : well_known_(well_known) {}
64+
65+
/// Construct a custom, application-defined encoding.
66+
///
67+
/// Prefer a well-known encoding wherever one applies. The identifier must be
68+
/// non-empty and no longer than 25 characters.
69+
///
70+
/// @param identifier Custom encoding identifier.
71+
/// @return Custom schema encoding.
72+
static DataTrackSchemaEncoding custom(std::string identifier) {
73+
DataTrackSchemaEncoding encoding;
74+
encoding.custom_ = std::move(identifier);
75+
return encoding;
76+
}
77+
78+
/// Check whether this is a custom encoding.
79+
///
80+
/// @return true if this is a custom encoding rather than a well-known one.
81+
bool isCustom() const { return !custom_.empty(); }
82+
83+
/// Get the well-known encoding.
84+
///
85+
/// @return The well-known encoding. Only meaningful when @ref isCustom is false.
86+
WellKnown wellKnown() const { return well_known_; }
87+
88+
/// Get the custom identifier.
89+
///
90+
/// @return The custom identifier. Empty when @ref isCustom is false.
91+
const std::string& customIdentifier() const { return custom_; }
92+
93+
private:
94+
DataTrackSchemaEncoding() = default;
95+
96+
WellKnown well_known_ = Other;
97+
std::string custom_;
98+
};
99+
100+
inline bool operator==(const DataTrackSchemaEncoding& a, const DataTrackSchemaEncoding& b) {
101+
if (a.isCustom() || b.isCustom()) {
102+
return a.customIdentifier() == b.customIdentifier();
103+
}
104+
return a.wellKnown() == b.wellKnown();
105+
}
106+
inline bool operator!=(const DataTrackSchemaEncoding& a, const DataTrackSchemaEncoding& b) { return !(a == b); }
107+
108+
/// @brief Encoding used for frames sent on a data track.
109+
///
110+
/// The serialization format of the frame bytes (for example,
111+
/// @ref DataTrackFrameEncoding::Protobuf); the structure of those bytes is
112+
/// described by a schema (see @ref DataTrackSchemaEncoding).
113+
///
114+
/// Almost all tracks use a well-known encoding, which converts implicitly:
115+
/// @code
116+
/// options.frame_encoding = DataTrackFrameEncoding::Json;
117+
/// @endcode
118+
/// For the uncommon case of an encoding outside the well-known set, use
119+
/// @ref DataTrackFrameEncoding::custom.
120+
class DataTrackFrameEncoding {
121+
public:
122+
/// Well-known frame encodings.
123+
enum WellKnown {
124+
/// ROS 1, described by a Ros1Msg schema.
125+
Ros1,
126+
/// CDR, described by a Ros2Msg, Ros2Idl, or OmgIdl schema.
127+
Cdr,
128+
/// Protocol Buffer, described by a Protobuf schema.
129+
Protobuf,
130+
/// FlatBuffer, described by a Flatbuffer schema.
131+
Flatbuffer,
132+
/// CBOR, self-describing.
133+
Cbor,
134+
/// MessagePack, self-describing.
135+
Msgpack,
136+
/// JSON, self-describing or described by a JsonSchema schema.
137+
Json,
138+
/// Another well-known encoding not known to this client version.
139+
Other,
140+
};
141+
142+
/// Construct a well-known encoding.
143+
///
144+
/// The constructor is intentionally implicit for the common well-known case.
145+
///
146+
/// @param well_known Well-known frame encoding.
147+
DataTrackFrameEncoding(WellKnown well_known) : well_known_(well_known) {}
148+
149+
/// Construct a custom, application-defined encoding.
150+
///
151+
/// Prefer a well-known encoding wherever one applies. The identifier must be
152+
/// non-empty and no longer than 25 characters.
153+
///
154+
/// @param identifier Custom encoding identifier.
155+
/// @return Custom frame encoding.
156+
static DataTrackFrameEncoding custom(std::string identifier) {
157+
DataTrackFrameEncoding encoding;
158+
encoding.custom_ = std::move(identifier);
159+
return encoding;
160+
}
161+
162+
/// Check whether this is a custom encoding.
163+
///
164+
/// @return true if this is a custom encoding rather than a well-known one.
165+
bool isCustom() const { return !custom_.empty(); }
166+
167+
/// Get the well-known encoding.
168+
///
169+
/// @return The well-known encoding. Only meaningful when @ref isCustom is false.
170+
WellKnown wellKnown() const { return well_known_; }
171+
172+
/// Get the custom identifier.
173+
///
174+
/// @return The custom identifier. Empty when @ref isCustom is false.
175+
const std::string& customIdentifier() const { return custom_; }
176+
177+
private:
178+
DataTrackFrameEncoding() = default;
179+
180+
WellKnown well_known_ = Other;
181+
std::string custom_;
182+
};
183+
184+
inline bool operator==(const DataTrackFrameEncoding& a, const DataTrackFrameEncoding& b) {
185+
if (a.isCustom() || b.isCustom()) {
186+
return a.customIdentifier() == b.customIdentifier();
187+
}
188+
return a.wellKnown() == b.wellKnown();
189+
}
190+
inline bool operator!=(const DataTrackFrameEncoding& a, const DataTrackFrameEncoding& b) { return !(a == b); }
191+
192+
/// @brief Uniquely identifies a data track schema.
193+
///
194+
/// A compound identifier with two components: a name and an encoding. Two IDs
195+
/// are equal only if both components match; the same name with a different
196+
/// encoding refers to a distinct schema.
197+
struct DataTrackSchemaId {
198+
/// Name component of the schema identifier.
199+
std::string name;
200+
201+
/// Encoding of the schema definition.
202+
DataTrackSchemaEncoding encoding = DataTrackSchemaEncoding::Other;
203+
};
204+
205+
inline bool operator==(const DataTrackSchemaId& a, const DataTrackSchemaId& b) {
206+
return a.name == b.name && a.encoding == b.encoding;
207+
}
208+
inline bool operator!=(const DataTrackSchemaId& a, const DataTrackSchemaId& b) { return !(a == b); }
209+
210+
} // namespace livekit

0 commit comments

Comments
 (0)