diff --git a/.changeset/data-track-schemas-ffi.md b/.changeset/data-track-schemas-ffi.md new file mode 100644 index 000000000..3127bafc3 --- /dev/null +++ b/.changeset/data-track-schemas-ffi.md @@ -0,0 +1,7 @@ +--- +livekit: patch +livekit-datatrack: patch +livekit-ffi: patch +--- + +Add schema metadata support for data tracks. diff --git a/livekit-datatrack/src/lib.rs b/livekit-datatrack/src/lib.rs index 1faadfeb5..88db850a1 100644 --- a/livekit-datatrack/src/lib.rs +++ b/livekit-datatrack/src/lib.rs @@ -17,6 +17,9 @@ /// Common types for local and remote tracks. mod track; +/// Schema and frame encoding metadata for typed tracks. +mod schema; + /// Local track publication. mod local; @@ -40,7 +43,7 @@ mod error; /// Public APIs re-exported by client SDKs. pub mod api { - pub use crate::{error::*, frame::*, local::*, remote::*, track::*}; + pub use crate::{error::*, frame::*, local::*, remote::*, schema::*, track::*}; } /// Internal APIs used within client SDKs to power data tracks functionality. diff --git a/livekit-datatrack/src/local/events.rs b/livekit-datatrack/src/local/events.rs index fda9c1536..9400e3f07 100644 --- a/livekit-datatrack/src/local/events.rs +++ b/livekit-datatrack/src/local/events.rs @@ -15,6 +15,7 @@ use crate::{ api::{DataTrackInfo, DataTrackOptions, LocalDataTrack, PublishError}, packet::Handle, + schema::{DataTrackFrameEncoding, DataTrackSchemaId}, }; use bytes::Bytes; use from_variants::FromVariants; @@ -124,6 +125,8 @@ pub struct SfuPublishRequest { pub handle: Handle, pub name: String, pub uses_e2ee: bool, + pub schema: Option, + pub frame_encoding: Option, } /// Request sent to the SFU to unpublish a track. diff --git a/livekit-datatrack/src/local/manager.rs b/livekit-datatrack/src/local/manager.rs index 17212ac0b..7deb5efc6 100644 --- a/livekit-datatrack/src/local/manager.rs +++ b/livekit-datatrack/src/local/manager.rs @@ -128,6 +128,8 @@ impl Manager { handle, name: event.options.name, uses_e2ee: self.encryption_provider.is_some(), + schema: event.options.schema, + frame_encoding: event.options.frame_encoding, }; _ = self.event_out_tx.send(event.into()).await; } @@ -280,6 +282,8 @@ impl Manager { handle: info.pub_handle, name: info.name.clone(), uses_e2ee: info.uses_e2ee, + schema: info.schema.clone(), + frame_encoding: info.frame_encoding, }; _ = state_tx.send(PublishState::Republishing); _ = self.event_out_tx.send(event.into()).await; @@ -525,6 +529,8 @@ mod tests { pub_handle, name: event.name, uses_e2ee: event.uses_e2ee, + schema: None, + frame_encoding: None, }; let event = SfuPublishResponse { handle: event.handle, result: Ok(info) }; _ = input.send(event.into()); @@ -604,6 +610,8 @@ mod tests { pub_handle: handle, name: "test".into(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; let event = SfuPublishResponse { handle, result: Ok(info) }; input.send(event.into()).unwrap(); @@ -634,6 +642,8 @@ mod tests { pub_handle: event.handle, name: "secure".into(), uses_e2ee: true, + schema: None, + frame_encoding: None, }; let event = SfuPublishResponse { handle: event.handle, result: Ok(info) }; input.send(event.into()).unwrap(); @@ -674,6 +684,8 @@ mod tests { pub_handle: handle, name: track_name.clone(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; let event = SfuPublishResponse { handle, result: Ok(info) }; input.send(event.into()).unwrap(); @@ -699,6 +711,8 @@ mod tests { pub_handle: handle, name: track_name.clone(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; let event = SfuPublishResponse { handle, result: Ok(info) }; input.send(event.into()).unwrap(); @@ -728,6 +742,8 @@ mod tests { pub_handle: event.handle, name: name.into(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; let event = SfuPublishResponse { handle: event.handle, result: Ok(info) }; input.send(event.into()).unwrap(); @@ -767,6 +783,8 @@ mod tests { pub_handle: event.handle, name: "active".into(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; let event = SfuPublishResponse { handle: event.handle, result: Ok(info) }; input.send(event.into()).unwrap(); diff --git a/livekit-datatrack/src/local/mod.rs b/livekit-datatrack/src/local/mod.rs index 358547146..4c432cd49 100644 --- a/livekit-datatrack/src/local/mod.rs +++ b/livekit-datatrack/src/local/mod.rs @@ -14,6 +14,7 @@ use crate::{ api::{DataTrack, DataTrackFrame, DataTrackInfo, InternalError}, + schema::{DataTrackFrameEncoding, DataTrackSchemaId}, track::DataTrackInner, }; use std::{fmt, marker::PhantomData, sync::Arc}; @@ -153,6 +154,8 @@ impl Drop for LocalTrackInner { #[derive(Clone, Debug)] pub struct DataTrackOptions { pub(crate) name: String, + pub(crate) schema: Option, + pub(crate) frame_encoding: Option, } impl DataTrackOptions { @@ -165,7 +168,15 @@ impl DataTrackOptions { /// - Must be unique per publisher /// pub fn new(name: impl Into) -> Self { - Self { name: name.into() } + Self { name: name.into(), schema: None, frame_encoding: None } + } + + pub fn with_schema(self, schema: DataTrackSchemaId) -> Self { + Self { schema: Some(schema), ..self } + } + + pub fn with_frame_encoding(self, encoding: DataTrackFrameEncoding) -> Self { + Self { frame_encoding: Some(encoding), ..self } } } diff --git a/livekit-datatrack/src/local/proto.rs b/livekit-datatrack/src/local/proto.rs index 907250681..d6f5b9584 100644 --- a/livekit-datatrack/src/local/proto.rs +++ b/livekit-datatrack/src/local/proto.rs @@ -33,7 +33,17 @@ impl From for proto::PublishDataTrackRequest { fn from(event: SfuPublishRequest) -> Self { use proto::encryption::Type; let encryption = if event.uses_e2ee { Type::Gcm } else { Type::None }.into(); - Self { pub_handle: event.handle.into(), name: event.name, encryption } + let schema = event.schema.map(|schema| schema.into()); + let frame_encoding = event + .frame_encoding + .map(|encoding| proto::DataTrackFrameEncoding::from(encoding) as i32); + Self { + pub_handle: event.handle.into(), + name: event.name, + encryption, + schema, + frame_encoding, + } } } @@ -74,8 +84,18 @@ impl TryFrom for DataTrackInfo { proto::encryption::Type::Gcm => true, other => Err(anyhow!("Unsupported E2EE type: {:?}", other))?, }; + let frame_encoding = msg.frame_encoding.map(|_| msg.frame_encoding().into()); let sid: DataTrackSid = msg.sid.try_into().map_err(anyhow::Error::from)?; - Ok(Self { pub_handle: handle, sid: RwLock::new(sid).into(), name: msg.name, uses_e2ee }) + let schema = msg.schema.map(|schema| schema.into()); + + Ok(Self { + pub_handle: handle, + sid: RwLock::new(sid).into(), + name: msg.name, + uses_e2ee, + schema, + frame_encoding, + }) } } @@ -106,12 +126,19 @@ impl From for proto::DataTrackInfo { proto::encryption::Type::Gcm } else { proto::encryption::Type::None - }; + } as i32; + let sid = info.sid().to_string(); + let schema = info.schema.map(|schema| schema.into()); + let frame_encoding = info + .frame_encoding + .map(|encoding| proto::DataTrackFrameEncoding::from(encoding) as i32); Self { pub_handle: info.pub_handle.into(), - sid: info.sid().to_string(), + sid, name: info.name, - encryption: encryption as i32, + encryption, + schema, + frame_encoding, } } } @@ -128,6 +155,8 @@ pub fn publish_responses_for_sync_state( #[cfg(test)] mod tests { + use crate::schema::{DataTrackFrameEncoding, DataTrackSchemaEncoding, DataTrackSchemaId}; + use super::*; use fake::{Fake, Faker}; @@ -137,6 +166,8 @@ mod tests { handle: 1u32.try_into().unwrap(), name: "track".into(), uses_e2ee: true, + schema: None, + frame_encoding: None, }; let request: proto::PublishDataTrackRequest = event.into(); assert_eq!(request.pub_handle, 1); @@ -159,6 +190,12 @@ mod tests { sid: "DTR_1234".into(), name: "track".into(), encryption: proto::encryption::Type::Gcm.into(), + schema: proto::DataTrackSchemaId { + name: "schema".into(), + encoding: proto::DataTrackSchemaEncoding::JsonSchema.into(), + } + .into(), + frame_encoding: Some(proto::DataTrackFrameEncoding::Json.into()), } .into(), }; @@ -169,9 +206,36 @@ mod tests { assert_eq!(info.pub_handle, 1u32.try_into().unwrap()); assert_eq!(*info.sid.read().unwrap(), "DTR_1234".to_string().try_into().unwrap()); assert_eq!(info.name, "track"); + assert_eq!( + info.schema, + Some(DataTrackSchemaId::new("schema", DataTrackSchemaEncoding::JsonSchema)) + ); + assert_eq!(info.frame_encoding, Some(DataTrackFrameEncoding::Json)); assert!(info.uses_e2ee); } + #[test] + fn test_frame_encoding_mapping() { + let base = proto::DataTrackInfo { + pub_handle: 1, + sid: "DTR_1234".into(), + name: "track".into(), + encryption: proto::encryption::Type::None.into(), + schema: None, + frame_encoding: None, + }; + + let info: DataTrackInfo = base.clone().try_into().unwrap(); + assert_eq!(info.frame_encoding, None); + + let unspecified = proto::DataTrackInfo { + frame_encoding: Some(proto::DataTrackFrameEncoding::Unspecified.into()), + ..base + }; + let info: DataTrackInfo = unspecified.try_into().unwrap(); + assert_eq!(info.frame_encoding, Some(DataTrackFrameEncoding::Other)); + } + #[test] fn test_from_request_response() { use proto::request_response::{Reason, Request}; diff --git a/livekit-datatrack/src/remote/manager.rs b/livekit-datatrack/src/remote/manager.rs index ce1bb260f..947a41513 100644 --- a/livekit-datatrack/src/remote/manager.rs +++ b/livekit-datatrack/src/remote/manager.rs @@ -596,6 +596,8 @@ mod tests { pub_handle: Faker.fake(), // Pub handle name: track_name.clone(), uses_e2ee: false, + schema: None, + frame_encoding: None, }], )]), }; @@ -658,6 +660,8 @@ mod tests { pub_handle: Faker.fake(), name: "test".into(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; // Simulate track published @@ -694,6 +698,8 @@ mod tests { pub_handle: Faker.fake(), name: "test".into(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; // Simulate three identical publication updates @@ -726,6 +732,8 @@ mod tests { pub_handle: Faker.fake(), name: "test".into(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; // Simulate track published @@ -785,6 +793,8 @@ mod tests { pub_handle: Faker.fake(), name: "test".into(), uses_e2ee: true, + schema: None, + frame_encoding: None, }; // Simulate track published (with e2ee) @@ -846,6 +856,8 @@ mod tests { pub_handle: Faker.fake(), name: "test".into(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; // Simulate track published @@ -944,6 +956,8 @@ mod tests { pub_handle: Faker.fake(), name: "test".into(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; // Simulate track published @@ -987,6 +1001,8 @@ mod tests { pub_handle: Faker.fake(), name: "test".into(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; // Simulate track published @@ -1038,6 +1054,8 @@ mod tests { pub_handle: Faker.fake(), name: "test".into(), uses_e2ee: false, + schema: None, + frame_encoding: None, }; // Simulate track published diff --git a/livekit-datatrack/src/remote/proto.rs b/livekit-datatrack/src/remote/proto.rs index 6f1b29d16..1a382b0e9 100644 --- a/livekit-datatrack/src/remote/proto.rs +++ b/livekit-datatrack/src/remote/proto.rs @@ -150,6 +150,8 @@ mod tests { sid: "DTR_1234".into(), name: "track1".into(), encryption: proto::encryption::Type::Gcm.into(), + schema: None, + frame_encoding: None, }]; let mut participant_info = proto::ParticipantInfo { data_tracks, ..Default::default() }; diff --git a/livekit-datatrack/src/schema.rs b/livekit-datatrack/src/schema.rs new file mode 100644 index 000000000..ddf0874ef --- /dev/null +++ b/livekit-datatrack/src/schema.rs @@ -0,0 +1,234 @@ +// 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. + +use livekit_protocol as proto; +use std::sync::Arc; + +/// Identifier for a data track schema. +/// +/// A compound identifier with two components: name and encoding. +/// +/// Two IDs are equal only if both components match; the same name with a +/// different encoding refers to a distinct schema. Cloning is cheap, as the name +/// component is reference counted. +/// +/// # Examples +/// +/// ``` +/// # use livekit_datatrack::api::{DataTrackSchemaId, DataTrackSchemaEncoding}; +/// let schema = DataTrackSchemaId::new("my_schema", DataTrackSchemaEncoding::Protobuf); +/// +/// assert_eq!(schema.name(), "my_schema"); +/// assert_eq!(schema.encoding(), DataTrackSchemaEncoding::Protobuf); +/// ``` +/// +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct DataTrackSchemaId { + name: Arc, + encoding: DataTrackSchemaEncoding, +} + +impl DataTrackSchemaId { + /// Creates a new schema ID. + pub fn new(name: impl Into, encoding: DataTrackSchemaEncoding) -> Self { + Self { name: Arc::::from(name.into()), encoding } + } + + /// Returns the name component of the ID. + pub fn name(&self) -> &str { + &self.name + } + + /// Returns the encoding component of the ID. + pub fn encoding(&self) -> DataTrackSchemaEncoding { + self.encoding + } +} + +/// Encoding used for a schema definition. +/// +/// Identifies the interface definition language the schema is written in (e.g. a +/// `.proto` file for [`Protobuf`]). This in turn dictates the wire format of the +/// frames the schema describes, captured by [`DataTrackFrameEncoding`]. +/// +/// [`Protobuf`]: DataTrackSchemaEncoding::Protobuf +/// +#[non_exhaustive] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +#[cfg_attr(test, derive(fake::Dummy))] +pub enum DataTrackSchemaEncoding { + /// Protocol Buffer IDL, describes [`Protobuf`] encoded frames. + /// + /// [`Protobuf`]: DataTrackFrameEncoding::Protobuf + Protobuf, + /// FlatBuffer IDL, describes [`Flatbuffer`] encoded frames. + /// + /// [`Flatbuffer`]: DataTrackFrameEncoding::Flatbuffer + Flatbuffer, + /// ROS 1 Message, describes [`Ros1`] encoded frames. + /// + /// [`Ros1`]: DataTrackFrameEncoding::Ros1 + Ros1Msg, + /// ROS 2 Message, describes [`Cdr`] encoded frames. + /// + /// [`Cdr`]: DataTrackFrameEncoding::Cdr + Ros2Msg, + /// ROS 2 IDL, describes [`Cdr`] encoded frames. + /// + /// [`Cdr`]: DataTrackFrameEncoding::Cdr + Ros2Idl, + /// OMG IDL, describes [`Cdr`] encoded frames. + /// + /// [`Cdr`]: DataTrackFrameEncoding::Cdr + OmgIdl, + /// JSON Schema, describes [`Json`] encoded frames. + /// + /// [`Json`]: DataTrackFrameEncoding::Json + JsonSchema, + /// Another encoding not known to this client version. + Other, +} + +/// Encoding used for frames pushed on a data track. +/// +/// The serialization format of the frame bytes (e.g. [`Protobuf`]); the structure +/// of those bytes is described by a schema, see [`DataTrackSchemaEncoding`]. +/// +/// [`Protobuf`]: DataTrackFrameEncoding::Protobuf +/// +#[non_exhaustive] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +#[cfg_attr(test, derive(fake::Dummy))] +pub enum DataTrackFrameEncoding { + /// ROS 1, must be described by a [`Ros1Msg`] schema. + /// + /// [`Ros1Msg`]: DataTrackSchemaEncoding::Ros1Msg + Ros1, + /// CDR, must be described by a [`Ros2Msg`], [`Ros2Idl`], or [`OmgIdl`] schema. + /// + /// [`Ros2Msg`]: DataTrackSchemaEncoding::Ros2Msg + /// [`Ros2Idl`]: DataTrackSchemaEncoding::Ros2Idl + /// [`OmgIdl`]: DataTrackSchemaEncoding::OmgIdl + Cdr, + /// Protocol Buffer, must be described by a [`Protobuf`] schema. + /// + /// [`Protobuf`]: DataTrackSchemaEncoding::Protobuf + Protobuf, + /// FlatBuffer, must be described by a [`Flatbuffer`] schema. + /// + /// [`Flatbuffer`]: DataTrackSchemaEncoding::Flatbuffer + Flatbuffer, + /// CBOR, self-describing. + Cbor, + /// MessagePack, self-describing. + Msgpack, + /// JSON, self-describing or described by a [`JsonSchema`] schema. + /// + /// [`JsonSchema`]: DataTrackSchemaEncoding::JsonSchema + Json, + /// Another encoding not known to this client version. + Other, +} + +impl From for DataTrackSchemaId { + fn from(msg: proto::DataTrackSchemaId) -> Self { + let encoding = msg.encoding().into(); + DataTrackSchemaId::new(msg.name, encoding) + } +} + +impl From for proto::DataTrackSchemaId { + fn from(value: DataTrackSchemaId) -> Self { + Self { + name: value.name.to_string(), + encoding: proto::DataTrackSchemaEncoding::from(value.encoding) as i32, + } + } +} + +impl From for DataTrackSchemaEncoding { + fn from(msg: proto::DataTrackSchemaEncoding) -> Self { + match msg { + proto::DataTrackSchemaEncoding::Unspecified => Self::Other, + proto::DataTrackSchemaEncoding::Protobuf => Self::Protobuf, + proto::DataTrackSchemaEncoding::Flatbuffer => Self::Flatbuffer, + proto::DataTrackSchemaEncoding::Ros1Msg => Self::Ros1Msg, + proto::DataTrackSchemaEncoding::Ros2Msg => Self::Ros2Msg, + proto::DataTrackSchemaEncoding::Ros2Idl => Self::Ros2Idl, + proto::DataTrackSchemaEncoding::OmgIdl => Self::OmgIdl, + proto::DataTrackSchemaEncoding::JsonSchema => Self::JsonSchema, + } + } +} + +impl From for proto::DataTrackSchemaEncoding { + fn from(value: DataTrackSchemaEncoding) -> Self { + match value { + DataTrackSchemaEncoding::Other => Self::Unspecified, + DataTrackSchemaEncoding::Protobuf => Self::Protobuf, + DataTrackSchemaEncoding::Flatbuffer => Self::Flatbuffer, + DataTrackSchemaEncoding::Ros1Msg => Self::Ros1Msg, + DataTrackSchemaEncoding::Ros2Msg => Self::Ros2Msg, + DataTrackSchemaEncoding::Ros2Idl => Self::Ros2Idl, + DataTrackSchemaEncoding::OmgIdl => Self::OmgIdl, + DataTrackSchemaEncoding::JsonSchema => Self::JsonSchema, + } + } +} + +impl From for DataTrackFrameEncoding { + fn from(msg: proto::DataTrackFrameEncoding) -> Self { + match msg { + proto::DataTrackFrameEncoding::Unspecified => Self::Other, + proto::DataTrackFrameEncoding::Ros1 => Self::Ros1, + proto::DataTrackFrameEncoding::Cdr => Self::Cdr, + proto::DataTrackFrameEncoding::Protobuf => Self::Protobuf, + proto::DataTrackFrameEncoding::Flatbuffer => Self::Flatbuffer, + proto::DataTrackFrameEncoding::Cbor => Self::Cbor, + proto::DataTrackFrameEncoding::Msgpack => Self::Msgpack, + proto::DataTrackFrameEncoding::Json => Self::Json, + } + } +} + +impl From for proto::DataTrackFrameEncoding { + fn from(value: DataTrackFrameEncoding) -> Self { + match value { + DataTrackFrameEncoding::Other => Self::Unspecified, + DataTrackFrameEncoding::Ros1 => Self::Ros1, + DataTrackFrameEncoding::Cdr => Self::Cdr, + DataTrackFrameEncoding::Protobuf => Self::Protobuf, + DataTrackFrameEncoding::Flatbuffer => Self::Flatbuffer, + DataTrackFrameEncoding::Cbor => Self::Cbor, + DataTrackFrameEncoding::Msgpack => Self::Msgpack, + DataTrackFrameEncoding::Json => Self::Json, + } + } +} + +impl From for proto::DataBlobKey { + fn from(id: DataTrackSchemaId) -> Self { + Self { key: Some(proto::data_blob_key::Key::SchemaId(id.into())) } + } +} + +#[cfg(test)] +impl fake::Dummy for DataTrackSchemaId { + fn dummy_with_rng(_: &fake::Faker, rng: &mut R) -> Self { + use fake::{Fake, Faker}; + let name: String = Faker.fake_with_rng(rng); + let encoding: DataTrackSchemaEncoding = Faker.fake_with_rng(rng); + Self::new(name, encoding) + } +} diff --git a/livekit-datatrack/src/track.rs b/livekit-datatrack/src/track.rs index 8c313b2b1..e8d95b495 100644 --- a/livekit-datatrack/src/track.rs +++ b/livekit-datatrack/src/track.rs @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::packet::Handle; +use crate::{ + packet::Handle, + schema::{DataTrackFrameEncoding, DataTrackSchemaId}, +}; use from_variants::FromVariants; use std::{ fmt::Display, @@ -71,6 +74,8 @@ pub struct DataTrackInfo { pub(crate) pub_handle: Handle, pub(crate) name: String, pub(crate) uses_e2ee: bool, + pub(crate) schema: Option, + pub(crate) frame_encoding: Option, } impl DataTrackInfo { @@ -92,6 +97,24 @@ impl DataTrackInfo { pub fn uses_e2ee(&self) -> bool { self.uses_e2ee } + + /// Schema associated with frames sent on the track. + /// + /// Returns `None` if the publisher did not associate a + /// [`DataTrackSchemaId`] with the track. + /// + pub fn schema(&self) -> Option<&DataTrackSchemaId> { + self.schema.as_ref() + } + + /// Encoding of frames sent on the track. + /// + /// Returns `None` if the publisher did not specify a + /// [`DataTrackFrameEncoding`] for the track. + /// + pub fn frame_encoding(&self) -> Option { + self.frame_encoding + } } /// SFU-assigned identifier uniquely identifying a data track. diff --git a/livekit-ffi-node-bindings/proto/data_track_pb.d.ts b/livekit-ffi-node-bindings/proto/data_track_pb.d.ts index 7fe5fd23e..5f20ed358 100644 --- a/livekit-ffi-node-bindings/proto/data_track_pb.d.ts +++ b/livekit-ffi-node-bindings/proto/data_track_pb.d.ts @@ -196,6 +196,53 @@ export declare enum SubscribeDataTrackErrorCode { INTERNAL = 6, } +/** + * Encoding used to interpret a data track schema definition. + * + * @generated from enum livekit.proto.DataTrackSchemaEncoding + */ +export declare enum DataTrackSchemaEncoding { + /** + * @generated from enum value: DATA_TRACK_SCHEMA_ENCODING_PROTOBUF = 0; + */ + PROTOBUF = 0, + + /** + * @generated from enum value: DATA_TRACK_SCHEMA_ENCODING_FLATBUFFER = 1; + */ + FLATBUFFER = 1, + + /** + * @generated from enum value: DATA_TRACK_SCHEMA_ENCODING_ROS1_MSG = 2; + */ + ROS1_MSG = 2, + + /** + * @generated from enum value: DATA_TRACK_SCHEMA_ENCODING_ROS2_MSG = 3; + */ + ROS2_MSG = 3, + + /** + * @generated from enum value: DATA_TRACK_SCHEMA_ENCODING_ROS2_IDL = 4; + */ + ROS2_IDL = 4, + + /** + * @generated from enum value: DATA_TRACK_SCHEMA_ENCODING_OMG_IDL = 5; + */ + OMG_IDL = 5, + + /** + * @generated from enum value: DATA_TRACK_SCHEMA_ENCODING_JSON_SCHEMA = 6; + */ + JSON_SCHEMA = 6, + + /** + * @generated from enum value: DATA_TRACK_SCHEMA_ENCODING_OTHER = 7; + */ + OTHER = 7, +} + /** * Information about a published data track. * @@ -1128,3 +1175,239 @@ export declare class DataTrackStreamEOS extends Message { static equals(a: DataTrackStreamEOS | PlainMessage | undefined, b: DataTrackStreamEOS | PlainMessage | undefined): boolean; } +/** + * Uniquely identifies a data track schema. + * + * @generated from message livekit.proto.DataTrackSchemaId + */ +export declare class DataTrackSchemaId extends Message { + /** + * Name component of the schema identifier. + * + * @generated from field: required string name = 1; + */ + name?: string; + + /** + * Encoding of the schema definition. + * + * @generated from field: required livekit.proto.DataTrackSchemaEncoding encoding = 2; + */ + encoding?: DataTrackSchemaEncoding; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto2; + static readonly typeName = "livekit.proto.DataTrackSchemaId"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): DataTrackSchemaId; + + static fromJson(jsonValue: JsonValue, options?: Partial): DataTrackSchemaId; + + static fromJsonString(jsonString: string, options?: Partial): DataTrackSchemaId; + + static equals(a: DataTrackSchemaId | PlainMessage | undefined, b: DataTrackSchemaId | PlainMessage | undefined): boolean; +} + +/** + * Define (store) a schema definition for the local participant. + * + * @generated from message livekit.proto.DefineSchemaRequest + */ +export declare class DefineSchemaRequest extends Message { + /** + * @generated from field: required uint64 local_participant_handle = 1; + */ + localParticipantHandle?: bigint; + + /** + * @generated from field: required livekit.proto.DataTrackSchemaId schema_id = 2; + */ + schemaId?: DataTrackSchemaId; + + /** + * @generated from field: required string definition = 3; + */ + definition?: string; + + /** + * @generated from field: optional uint64 request_async_id = 4; + */ + requestAsyncId?: bigint; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto2; + static readonly typeName = "livekit.proto.DefineSchemaRequest"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): DefineSchemaRequest; + + static fromJson(jsonValue: JsonValue, options?: Partial): DefineSchemaRequest; + + static fromJsonString(jsonString: string, options?: Partial): DefineSchemaRequest; + + static equals(a: DefineSchemaRequest | PlainMessage | undefined, b: DefineSchemaRequest | PlainMessage | undefined): boolean; +} + +/** + * @generated from message livekit.proto.DefineSchemaResponse + */ +export declare class DefineSchemaResponse extends Message { + /** + * @generated from field: required uint64 async_id = 1; + */ + asyncId?: bigint; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto2; + static readonly typeName = "livekit.proto.DefineSchemaResponse"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): DefineSchemaResponse; + + static fromJson(jsonValue: JsonValue, options?: Partial): DefineSchemaResponse; + + static fromJsonString(jsonString: string, options?: Partial): DefineSchemaResponse; + + static equals(a: DefineSchemaResponse | PlainMessage | undefined, b: DefineSchemaResponse | PlainMessage | undefined): boolean; +} + +/** + * @generated from message livekit.proto.DefineSchemaCallback + */ +export declare class DefineSchemaCallback extends Message { + /** + * @generated from field: required uint64 async_id = 1; + */ + asyncId?: bigint; + + /** + * Present if the schema could not be defined. + * + * @generated from field: optional string error = 2; + */ + error?: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto2; + static readonly typeName = "livekit.proto.DefineSchemaCallback"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): DefineSchemaCallback; + + static fromJson(jsonValue: JsonValue, options?: Partial): DefineSchemaCallback; + + static fromJsonString(jsonString: string, options?: Partial): DefineSchemaCallback; + + static equals(a: DefineSchemaCallback | PlainMessage | undefined, b: DefineSchemaCallback | PlainMessage | undefined): boolean; +} + +/** + * Retrieve a schema definition previously stored by a participant. + * + * @generated from message livekit.proto.GetSchemaRequest + */ +export declare class GetSchemaRequest extends Message { + /** + * @generated from field: required uint64 local_participant_handle = 1; + */ + localParticipantHandle?: bigint; + + /** + * @generated from field: required livekit.proto.DataTrackSchemaId schema_id = 2; + */ + schemaId?: DataTrackSchemaId; + + /** + * Identity of the participant who owns the schema. + * + * @generated from field: required string participant_identity = 3; + */ + participantIdentity?: string; + + /** + * @generated from field: optional uint64 request_async_id = 4; + */ + requestAsyncId?: bigint; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto2; + static readonly typeName = "livekit.proto.GetSchemaRequest"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): GetSchemaRequest; + + static fromJson(jsonValue: JsonValue, options?: Partial): GetSchemaRequest; + + static fromJsonString(jsonString: string, options?: Partial): GetSchemaRequest; + + static equals(a: GetSchemaRequest | PlainMessage | undefined, b: GetSchemaRequest | PlainMessage | undefined): boolean; +} + +/** + * @generated from message livekit.proto.GetSchemaResponse + */ +export declare class GetSchemaResponse extends Message { + /** + * @generated from field: required uint64 async_id = 1; + */ + asyncId?: bigint; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto2; + static readonly typeName = "livekit.proto.GetSchemaResponse"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): GetSchemaResponse; + + static fromJson(jsonValue: JsonValue, options?: Partial): GetSchemaResponse; + + static fromJsonString(jsonString: string, options?: Partial): GetSchemaResponse; + + static equals(a: GetSchemaResponse | PlainMessage | undefined, b: GetSchemaResponse | PlainMessage | undefined): boolean; +} + +/** + * @generated from message livekit.proto.GetSchemaCallback + */ +export declare class GetSchemaCallback extends Message { + /** + * @generated from field: required uint64 async_id = 1; + */ + asyncId?: bigint; + + /** + * The schema definition, present on success. + * + * @generated from field: optional string definition = 2; + */ + definition?: string; + + /** + * Present if the schema could not be retrieved. + * + * @generated from field: optional string error = 3; + */ + error?: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto2; + static readonly typeName = "livekit.proto.GetSchemaCallback"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): GetSchemaCallback; + + static fromJson(jsonValue: JsonValue, options?: Partial): GetSchemaCallback; + + static fromJsonString(jsonString: string, options?: Partial): GetSchemaCallback; + + static equals(a: GetSchemaCallback | PlainMessage | undefined, b: GetSchemaCallback | PlainMessage | undefined): boolean; +} + diff --git a/livekit-ffi-node-bindings/proto/data_track_pb.js b/livekit-ffi-node-bindings/proto/data_track_pb.js index 73b496d21..d5f327613 100644 --- a/livekit-ffi-node-bindings/proto/data_track_pb.js +++ b/livekit-ffi-node-bindings/proto/data_track_pb.js @@ -90,6 +90,25 @@ const SubscribeDataTrackErrorCode = /*@__PURE__*/ proto2.makeEnum( ], ); +/** + * Encoding used to interpret a data track schema definition. + * + * @generated from enum livekit.proto.DataTrackSchemaEncoding + */ +const DataTrackSchemaEncoding = /*@__PURE__*/ proto2.makeEnum( + "livekit.proto.DataTrackSchemaEncoding", + [ + {no: 0, name: "DATA_TRACK_SCHEMA_ENCODING_PROTOBUF", localName: "PROTOBUF"}, + {no: 1, name: "DATA_TRACK_SCHEMA_ENCODING_FLATBUFFER", localName: "FLATBUFFER"}, + {no: 2, name: "DATA_TRACK_SCHEMA_ENCODING_ROS1_MSG", localName: "ROS1_MSG"}, + {no: 3, name: "DATA_TRACK_SCHEMA_ENCODING_ROS2_MSG", localName: "ROS2_MSG"}, + {no: 4, name: "DATA_TRACK_SCHEMA_ENCODING_ROS2_IDL", localName: "ROS2_IDL"}, + {no: 5, name: "DATA_TRACK_SCHEMA_ENCODING_OMG_IDL", localName: "OMG_IDL"}, + {no: 6, name: "DATA_TRACK_SCHEMA_ENCODING_JSON_SCHEMA", localName: "JSON_SCHEMA"}, + {no: 7, name: "DATA_TRACK_SCHEMA_ENCODING_OTHER", localName: "OTHER"}, + ], +); + /** * Information about a published data track. * @@ -465,11 +484,98 @@ const DataTrackStreamEOS = /*@__PURE__*/ proto2.makeMessageType( ], ); +/** + * Uniquely identifies a data track schema. + * + * @generated from message livekit.proto.DataTrackSchemaId + */ +const DataTrackSchemaId = /*@__PURE__*/ proto2.makeMessageType( + "livekit.proto.DataTrackSchemaId", + () => [ + { no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */, req: true }, + { no: 2, name: "encoding", kind: "enum", T: proto2.getEnumType(DataTrackSchemaEncoding), req: true }, + ], +); + +/** + * Define (store) a schema definition for the local participant. + * + * @generated from message livekit.proto.DefineSchemaRequest + */ +const DefineSchemaRequest = /*@__PURE__*/ proto2.makeMessageType( + "livekit.proto.DefineSchemaRequest", + () => [ + { no: 1, name: "local_participant_handle", kind: "scalar", T: 4 /* ScalarType.UINT64 */, req: true }, + { no: 2, name: "schema_id", kind: "message", T: DataTrackSchemaId, req: true }, + { no: 3, name: "definition", kind: "scalar", T: 9 /* ScalarType.STRING */, req: true }, + { no: 4, name: "request_async_id", kind: "scalar", T: 4 /* ScalarType.UINT64 */, opt: true }, + ], +); + +/** + * @generated from message livekit.proto.DefineSchemaResponse + */ +const DefineSchemaResponse = /*@__PURE__*/ proto2.makeMessageType( + "livekit.proto.DefineSchemaResponse", + () => [ + { no: 1, name: "async_id", kind: "scalar", T: 4 /* ScalarType.UINT64 */, req: true }, + ], +); + +/** + * @generated from message livekit.proto.DefineSchemaCallback + */ +const DefineSchemaCallback = /*@__PURE__*/ proto2.makeMessageType( + "livekit.proto.DefineSchemaCallback", + () => [ + { no: 1, name: "async_id", kind: "scalar", T: 4 /* ScalarType.UINT64 */, req: true }, + { no: 2, name: "error", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + ], +); + +/** + * Retrieve a schema definition previously stored by a participant. + * + * @generated from message livekit.proto.GetSchemaRequest + */ +const GetSchemaRequest = /*@__PURE__*/ proto2.makeMessageType( + "livekit.proto.GetSchemaRequest", + () => [ + { no: 1, name: "local_participant_handle", kind: "scalar", T: 4 /* ScalarType.UINT64 */, req: true }, + { no: 2, name: "schema_id", kind: "message", T: DataTrackSchemaId, req: true }, + { no: 3, name: "participant_identity", kind: "scalar", T: 9 /* ScalarType.STRING */, req: true }, + { no: 4, name: "request_async_id", kind: "scalar", T: 4 /* ScalarType.UINT64 */, opt: true }, + ], +); + +/** + * @generated from message livekit.proto.GetSchemaResponse + */ +const GetSchemaResponse = /*@__PURE__*/ proto2.makeMessageType( + "livekit.proto.GetSchemaResponse", + () => [ + { no: 1, name: "async_id", kind: "scalar", T: 4 /* ScalarType.UINT64 */, req: true }, + ], +); + +/** + * @generated from message livekit.proto.GetSchemaCallback + */ +const GetSchemaCallback = /*@__PURE__*/ proto2.makeMessageType( + "livekit.proto.GetSchemaCallback", + () => [ + { no: 1, name: "async_id", kind: "scalar", T: 4 /* ScalarType.UINT64 */, req: true }, + { no: 2, name: "definition", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + { no: 3, name: "error", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + ], +); + exports.DataTrackErrorCode = DataTrackErrorCode; exports.PublishDataTrackErrorCode = PublishDataTrackErrorCode; exports.LocalDataTrackTryPushErrorCode = LocalDataTrackTryPushErrorCode; exports.SubscribeDataTrackErrorCode = SubscribeDataTrackErrorCode; +exports.DataTrackSchemaEncoding = DataTrackSchemaEncoding; exports.DataTrackInfo = DataTrackInfo; exports.DataTrackFrame = DataTrackFrame; exports.DataTrackError = DataTrackError; @@ -502,3 +608,10 @@ exports.DataTrackStreamReadResponse = DataTrackStreamReadResponse; exports.DataTrackStreamEvent = DataTrackStreamEvent; exports.DataTrackStreamFrameReceived = DataTrackStreamFrameReceived; exports.DataTrackStreamEOS = DataTrackStreamEOS; +exports.DataTrackSchemaId = DataTrackSchemaId; +exports.DefineSchemaRequest = DefineSchemaRequest; +exports.DefineSchemaResponse = DefineSchemaResponse; +exports.DefineSchemaCallback = DefineSchemaCallback; +exports.GetSchemaRequest = GetSchemaRequest; +exports.GetSchemaResponse = GetSchemaResponse; +exports.GetSchemaCallback = GetSchemaCallback; diff --git a/livekit-ffi-node-bindings/proto/ffi_pb.d.ts b/livekit-ffi-node-bindings/proto/ffi_pb.d.ts index 930dd7245..ac81cb93e 100644 --- a/livekit-ffi-node-bindings/proto/ffi_pb.d.ts +++ b/livekit-ffi-node-bindings/proto/ffi_pb.d.ts @@ -27,7 +27,7 @@ import type { E2eeRequest, E2eeResponse } from "./e2ee_pb.js"; import type { PerformRpcCallback, PerformRpcRequest, PerformRpcResponse, RegisterRpcMethodRequest, RegisterRpcMethodResponse, RpcMethodInvocationEvent, RpcMethodInvocationResponseRequest, RpcMethodInvocationResponseResponse, UnregisterRpcMethodRequest, UnregisterRpcMethodResponse } from "./rpc_pb.js"; import type { EnableRemoteTrackPublicationRequest, EnableRemoteTrackPublicationResponse, SetRemoteTrackPublicationQualityRequest, SetRemoteTrackPublicationQualityResponse, UpdateRemoteTrackPublicationDimensionRequest, UpdateRemoteTrackPublicationDimensionResponse } from "./track_publication_pb.js"; import type { ByteStreamOpenCallback, ByteStreamOpenRequest, ByteStreamOpenResponse, ByteStreamReaderEvent, ByteStreamReaderReadAllCallback, ByteStreamReaderReadAllRequest, ByteStreamReaderReadAllResponse, ByteStreamReaderReadIncrementalRequest, ByteStreamReaderReadIncrementalResponse, ByteStreamReaderWriteToFileCallback, ByteStreamReaderWriteToFileRequest, ByteStreamReaderWriteToFileResponse, ByteStreamWriterCloseCallback, ByteStreamWriterCloseRequest, ByteStreamWriterCloseResponse, ByteStreamWriterWriteCallback, ByteStreamWriterWriteRequest, ByteStreamWriterWriteResponse, StreamSendBytesCallback, StreamSendBytesRequest, StreamSendBytesResponse, StreamSendFileCallback, StreamSendFileRequest, StreamSendFileResponse, StreamSendTextCallback, StreamSendTextRequest, StreamSendTextResponse, TextStreamOpenCallback, TextStreamOpenRequest, TextStreamOpenResponse, TextStreamReaderEvent, TextStreamReaderReadAllCallback, TextStreamReaderReadAllRequest, TextStreamReaderReadAllResponse, TextStreamReaderReadIncrementalRequest, TextStreamReaderReadIncrementalResponse, TextStreamWriterCloseCallback, TextStreamWriterCloseRequest, TextStreamWriterCloseResponse, TextStreamWriterWriteCallback, TextStreamWriterWriteRequest, TextStreamWriterWriteResponse } from "./data_stream_pb.js"; -import type { DataTrackStreamEvent, DataTrackStreamReadRequest, DataTrackStreamReadResponse, LocalDataTrackIsPublishedRequest, LocalDataTrackIsPublishedResponse, LocalDataTrackTryPushRequest, LocalDataTrackTryPushResponse, LocalDataTrackUnpublishRequest, LocalDataTrackUnpublishResponse, PublishDataTrackCallback, PublishDataTrackRequest, PublishDataTrackResponse, RemoteDataTrackIsPublishedRequest, RemoteDataTrackIsPublishedResponse, RemoteDataTrackSetPipelineOptionsRequest, RemoteDataTrackSetPipelineOptionsResponse, SubscribeDataTrackRequest, SubscribeDataTrackResponse } from "./data_track_pb.js"; +import type { DataTrackStreamEvent, DataTrackStreamReadRequest, DataTrackStreamReadResponse, DefineSchemaCallback, DefineSchemaRequest, DefineSchemaResponse, GetSchemaCallback, GetSchemaRequest, GetSchemaResponse, LocalDataTrackIsPublishedRequest, LocalDataTrackIsPublishedResponse, LocalDataTrackTryPushRequest, LocalDataTrackTryPushResponse, LocalDataTrackUnpublishRequest, LocalDataTrackUnpublishResponse, PublishDataTrackCallback, PublishDataTrackRequest, PublishDataTrackResponse, RemoteDataTrackIsPublishedRequest, RemoteDataTrackIsPublishedResponse, RemoteDataTrackSetPipelineOptionsRequest, RemoteDataTrackSetPipelineOptionsResponse, SubscribeDataTrackRequest, SubscribeDataTrackResponse } from "./data_track_pb.js"; /** * @generated from enum livekit.proto.LogLevel @@ -543,6 +543,20 @@ export declare class FfiRequest extends Message { */ value: RemoteDataTrackSetPipelineOptionsRequest; case: "remoteDataTrackSetPipelineOptions"; + } | { + /** + * Data Track (schemas) + * + * @generated from field: livekit.proto.DefineSchemaRequest define_schema = 85; + */ + value: DefineSchemaRequest; + case: "defineSchema"; + } | { + /** + * @generated from field: livekit.proto.GetSchemaRequest get_schema = 86; + */ + value: GetSchemaRequest; + case: "getSchema"; } | { /** * Reconnection / chaos testing @@ -1091,6 +1105,20 @@ export declare class FfiResponse extends Message { */ value: RemoteDataTrackSetPipelineOptionsResponse; case: "remoteDataTrackSetPipelineOptions"; + } | { + /** + * Data Track (schemas) + * + * @generated from field: livekit.proto.DefineSchemaResponse define_schema = 85; + */ + value: DefineSchemaResponse; + case: "defineSchema"; + } | { + /** + * @generated from field: livekit.proto.GetSchemaResponse get_schema = 86; + */ + value: GetSchemaResponse; + case: "getSchema"; } | { /** * Reconnection / chaos testing @@ -1439,6 +1467,20 @@ export declare class FfiEvent extends Message { */ value: SimulateScenarioCallback; case: "simulateScenario"; + } | { + /** + * Data Track (schemas) + * + * @generated from field: livekit.proto.DefineSchemaCallback define_schema = 45; + */ + value: DefineSchemaCallback; + case: "defineSchema"; + } | { + /** + * @generated from field: livekit.proto.GetSchemaCallback get_schema = 46; + */ + value: GetSchemaCallback; + case: "getSchema"; } | { case: undefined; value?: undefined }; constructor(data?: PartialMessage); diff --git a/livekit-ffi-node-bindings/proto/ffi_pb.js b/livekit-ffi-node-bindings/proto/ffi_pb.js index 10986d29d..4f0bdd422 100644 --- a/livekit-ffi-node-bindings/proto/ffi_pb.js +++ b/livekit-ffi-node-bindings/proto/ffi_pb.js @@ -29,7 +29,7 @@ const { E2eeRequest, E2eeResponse } = require("./e2ee_pb.js"); const { PerformRpcCallback, PerformRpcRequest, PerformRpcResponse, RegisterRpcMethodRequest, RegisterRpcMethodResponse, RpcMethodInvocationEvent, RpcMethodInvocationResponseRequest, RpcMethodInvocationResponseResponse, UnregisterRpcMethodRequest, UnregisterRpcMethodResponse } = require("./rpc_pb.js"); const { EnableRemoteTrackPublicationRequest, EnableRemoteTrackPublicationResponse, SetRemoteTrackPublicationQualityRequest, SetRemoteTrackPublicationQualityResponse, UpdateRemoteTrackPublicationDimensionRequest, UpdateRemoteTrackPublicationDimensionResponse } = require("./track_publication_pb.js"); const { ByteStreamOpenCallback, ByteStreamOpenRequest, ByteStreamOpenResponse, ByteStreamReaderEvent, ByteStreamReaderReadAllCallback, ByteStreamReaderReadAllRequest, ByteStreamReaderReadAllResponse, ByteStreamReaderReadIncrementalRequest, ByteStreamReaderReadIncrementalResponse, ByteStreamReaderWriteToFileCallback, ByteStreamReaderWriteToFileRequest, ByteStreamReaderWriteToFileResponse, ByteStreamWriterCloseCallback, ByteStreamWriterCloseRequest, ByteStreamWriterCloseResponse, ByteStreamWriterWriteCallback, ByteStreamWriterWriteRequest, ByteStreamWriterWriteResponse, StreamSendBytesCallback, StreamSendBytesRequest, StreamSendBytesResponse, StreamSendFileCallback, StreamSendFileRequest, StreamSendFileResponse, StreamSendTextCallback, StreamSendTextRequest, StreamSendTextResponse, TextStreamOpenCallback, TextStreamOpenRequest, TextStreamOpenResponse, TextStreamReaderEvent, TextStreamReaderReadAllCallback, TextStreamReaderReadAllRequest, TextStreamReaderReadAllResponse, TextStreamReaderReadIncrementalRequest, TextStreamReaderReadIncrementalResponse, TextStreamWriterCloseCallback, TextStreamWriterCloseRequest, TextStreamWriterCloseResponse, TextStreamWriterWriteCallback, TextStreamWriterWriteRequest, TextStreamWriterWriteResponse } = require("./data_stream_pb.js"); -const { DataTrackStreamEvent, DataTrackStreamReadRequest, DataTrackStreamReadResponse, LocalDataTrackIsPublishedRequest, LocalDataTrackIsPublishedResponse, LocalDataTrackTryPushRequest, LocalDataTrackTryPushResponse, LocalDataTrackUnpublishRequest, LocalDataTrackUnpublishResponse, PublishDataTrackCallback, PublishDataTrackRequest, PublishDataTrackResponse, RemoteDataTrackIsPublishedRequest, RemoteDataTrackIsPublishedResponse, RemoteDataTrackSetPipelineOptionsRequest, RemoteDataTrackSetPipelineOptionsResponse, SubscribeDataTrackRequest, SubscribeDataTrackResponse } = require("./data_track_pb.js"); +const { DataTrackStreamEvent, DataTrackStreamReadRequest, DataTrackStreamReadResponse, DefineSchemaCallback, DefineSchemaRequest, DefineSchemaResponse, GetSchemaCallback, GetSchemaRequest, GetSchemaResponse, LocalDataTrackIsPublishedRequest, LocalDataTrackIsPublishedResponse, LocalDataTrackTryPushRequest, LocalDataTrackTryPushResponse, LocalDataTrackUnpublishRequest, LocalDataTrackUnpublishResponse, PublishDataTrackCallback, PublishDataTrackRequest, PublishDataTrackResponse, RemoteDataTrackIsPublishedRequest, RemoteDataTrackIsPublishedResponse, RemoteDataTrackSetPipelineOptionsRequest, RemoteDataTrackSetPipelineOptionsResponse, SubscribeDataTrackRequest, SubscribeDataTrackResponse } = require("./data_track_pb.js"); /** * @generated from enum livekit.proto.LogLevel @@ -129,6 +129,8 @@ const FfiRequest = /*@__PURE__*/ proto2.makeMessageType( { no: 74, name: "remote_data_track_is_published", kind: "message", T: RemoteDataTrackIsPublishedRequest, oneof: "message" }, { no: 75, name: "data_track_stream_read", kind: "message", T: DataTrackStreamReadRequest, oneof: "message" }, { no: 84, name: "remote_data_track_set_pipeline_options", kind: "message", T: RemoteDataTrackSetPipelineOptionsRequest, oneof: "message" }, + { no: 85, name: "define_schema", kind: "message", T: DefineSchemaRequest, oneof: "message" }, + { no: 86, name: "get_schema", kind: "message", T: GetSchemaRequest, oneof: "message" }, { no: 76, name: "simulate_scenario", kind: "message", T: SimulateScenarioRequest, oneof: "message" }, { no: 77, name: "new_platform_audio", kind: "message", T: NewPlatformAudioRequest, oneof: "message" }, { no: 78, name: "get_audio_devices", kind: "message", T: GetAudioDevicesRequest, oneof: "message" }, @@ -222,6 +224,8 @@ const FfiResponse = /*@__PURE__*/ proto2.makeMessageType( { no: 73, name: "remote_data_track_is_published", kind: "message", T: RemoteDataTrackIsPublishedResponse, oneof: "message" }, { no: 74, name: "data_track_stream_read", kind: "message", T: DataTrackStreamReadResponse, oneof: "message" }, { no: 84, name: "remote_data_track_set_pipeline_options", kind: "message", T: RemoteDataTrackSetPipelineOptionsResponse, oneof: "message" }, + { no: 85, name: "define_schema", kind: "message", T: DefineSchemaResponse, oneof: "message" }, + { no: 86, name: "get_schema", kind: "message", T: GetSchemaResponse, oneof: "message" }, { no: 75, name: "simulate_scenario", kind: "message", T: SimulateScenarioResponse, oneof: "message" }, { no: 76, name: "new_platform_audio", kind: "message", T: NewPlatformAudioResponse, oneof: "message" }, { no: 77, name: "get_audio_devices", kind: "message", T: GetAudioDevicesResponse, oneof: "message" }, @@ -286,6 +290,8 @@ const FfiEvent = /*@__PURE__*/ proto2.makeMessageType( { no: 42, name: "publish_data_track", kind: "message", T: PublishDataTrackCallback, oneof: "message" }, { no: 43, name: "data_track_stream_event", kind: "message", T: DataTrackStreamEvent, oneof: "message" }, { no: 44, name: "simulate_scenario", kind: "message", T: SimulateScenarioCallback, oneof: "message" }, + { no: 45, name: "define_schema", kind: "message", T: DefineSchemaCallback, oneof: "message" }, + { no: 46, name: "get_schema", kind: "message", T: GetSchemaCallback, oneof: "message" }, ], ); diff --git a/livekit-ffi/protocol/data_track.proto b/livekit-ffi/protocol/data_track.proto index 02eafdd94..c5c55f220 100644 --- a/livekit-ffi/protocol/data_track.proto +++ b/livekit-ffi/protocol/data_track.proto @@ -30,6 +30,10 @@ message DataTrackInfo { required string sid = 2; // Whether or not frames sent on the track use end-to-end encryption. required bool uses_e2ee = 3; + // Schema associated with frames sent on the track, if any. + optional DataTrackSchemaId schema = 4; + // Encoding of frames sent on the track, if specified. + optional DataTrackFrameEncoding frame_encoding = 5; } // A frame published on a data track. @@ -110,6 +114,10 @@ message DataTrackOptions { // Must not be empty and must be unique per publisher. // required string name = 1; + // Schema describing frames sent on the track. + optional DataTrackSchemaId schema = 2; + // Encoding of frames sent on the track. + optional DataTrackFrameEncoding frame_encoding = 3; } // Publish a data track @@ -246,3 +254,72 @@ message DataTrackStreamEOS { // Absent if the stream ended normally (i.e., due to the track being unpublished). optional SubscribeDataTrackError error = 1; } + +// MARK: - Schemas + +// Encoding used to interpret a data track schema definition. +enum DataTrackSchemaEncoding { + DATA_TRACK_SCHEMA_ENCODING_OTHER = 0; + DATA_TRACK_SCHEMA_ENCODING_PROTOBUF = 1; + DATA_TRACK_SCHEMA_ENCODING_FLATBUFFER = 2; + DATA_TRACK_SCHEMA_ENCODING_ROS1_MSG = 3; + DATA_TRACK_SCHEMA_ENCODING_ROS2_MSG = 4; + DATA_TRACK_SCHEMA_ENCODING_ROS2_IDL = 5; + DATA_TRACK_SCHEMA_ENCODING_OMG_IDL = 6; + DATA_TRACK_SCHEMA_ENCODING_JSON_SCHEMA = 7; +} + +// Encoding used for frames sent on a data track. +enum DataTrackFrameEncoding { + DATA_TRACK_FRAME_ENCODING_OTHER = 0; + DATA_TRACK_FRAME_ENCODING_ROS1 = 1; + DATA_TRACK_FRAME_ENCODING_CDR = 2; + DATA_TRACK_FRAME_ENCODING_PROTOBUF = 3; + DATA_TRACK_FRAME_ENCODING_FLATBUFFER = 4; + DATA_TRACK_FRAME_ENCODING_CBOR = 5; + DATA_TRACK_FRAME_ENCODING_MSGPACK = 6; + DATA_TRACK_FRAME_ENCODING_JSON = 7; +} + +// Uniquely identifies a data track schema. +message DataTrackSchemaId { + // Name component of the schema identifier. + required string name = 1; + // Encoding of the schema definition. + required DataTrackSchemaEncoding encoding = 2; +} + +// Define (store) a schema definition for the local participant. +message DefineSchemaRequest { + required uint64 local_participant_handle = 1; + required DataTrackSchemaId schema_id = 2; + required string definition = 3; + optional uint64 request_async_id = 4; +} +message DefineSchemaResponse { + required uint64 async_id = 1; +} +message DefineSchemaCallback { + required uint64 async_id = 1; + // Present if the schema could not be defined. + optional string error = 2; +} + +// Retrieve a schema definition previously stored by a participant. +message GetSchemaRequest { + required uint64 local_participant_handle = 1; + required DataTrackSchemaId schema_id = 2; + // Identity of the participant who owns the schema. + required string participant_identity = 3; + optional uint64 request_async_id = 4; +} +message GetSchemaResponse { + required uint64 async_id = 1; +} +message GetSchemaCallback { + required uint64 async_id = 1; + // The schema definition, present on success. + optional string definition = 2; + // Present if the schema could not be retrieved. + optional string error = 3; +} diff --git a/livekit-ffi/protocol/ffi.proto b/livekit-ffi/protocol/ffi.proto index b630095e4..53ea782bd 100644 --- a/livekit-ffi/protocol/ffi.proto +++ b/livekit-ffi/protocol/ffi.proto @@ -15,18 +15,19 @@ syntax = "proto2"; package livekit.proto; -option csharp_namespace = "LiveKit.Proto"; +import "audio_frame.proto"; +import "data_stream.proto"; +import "data_track.proto"; // import "handle.proto"; import "e2ee.proto"; +import "room.proto"; +import "rpc.proto"; import "track.proto"; import "track_publication.proto"; -import "room.proto"; import "video_frame.proto"; -import "audio_frame.proto"; -import "rpc.proto"; -import "data_stream.proto"; -import "data_track.proto"; + +option csharp_namespace = "LiveKit.Proto"; // **How is the livekit-ffi working: // We refer as the ffi server the Rust server that is running the LiveKit client implementation, and we @@ -165,6 +166,10 @@ message FfiRequest { DataTrackStreamReadRequest data_track_stream_read = 75; RemoteDataTrackSetPipelineOptionsRequest remote_data_track_set_pipeline_options = 84; + // Data Track (schemas) + DefineSchemaRequest define_schema = 85; + GetSchemaRequest get_schema = 86; + // Reconnection / chaos testing SimulateScenarioRequest simulate_scenario = 76; @@ -179,7 +184,7 @@ message FfiRequest { // Room event ready signal ReadyForRoomEventRequest ready_for_room_event = 83; - // NEXT_ID: 85 + // NEXT_ID: 87 } } @@ -290,6 +295,10 @@ message FfiResponse { DataTrackStreamReadResponse data_track_stream_read = 74; RemoteDataTrackSetPipelineOptionsResponse remote_data_track_set_pipeline_options = 84; + // Data Track (schemas) + DefineSchemaResponse define_schema = 85; + GetSchemaResponse get_schema = 86; + // Reconnection / chaos testing SimulateScenarioResponse simulate_scenario = 75; @@ -304,7 +313,7 @@ message FfiResponse { // Room event ready signal ReadyForRoomEventResponse ready_for_room_event = 82; - // NEXT_ID: 85 + // NEXT_ID: 87 } } @@ -369,7 +378,11 @@ message FfiEvent { SimulateScenarioCallback simulate_scenario = 44; - // NEXT_ID: 45 + // Data Track (schemas) + DefineSchemaCallback define_schema = 45; + GetSchemaCallback get_schema = 46; + + // NEXT_ID: 47 } } diff --git a/livekit-ffi/src/conversion/data_track.rs b/livekit-ffi/src/conversion/data_track.rs index e4777a680..db4f41726 100644 --- a/livekit-ffi/src/conversion/data_track.rs +++ b/livekit-ffi/src/conversion/data_track.rs @@ -15,7 +15,8 @@ use crate::proto; use livekit::{ data_track::{ - DataTrackFrame, DataTrackInfo, DataTrackOptions, DataTrackSubscribeError, PublishError, + DataTrackFrame, DataTrackFrameEncoding, DataTrackInfo, DataTrackOptions, + DataTrackSchemaEncoding, DataTrackSchemaId, DataTrackSubscribeError, PublishError, PushFrameError, PushFrameErrorReason, RemoteDataTrackPipelineOptions, }, prelude::DataTrackSubscribeOptions, @@ -23,7 +24,15 @@ use livekit::{ impl From for DataTrackOptions { fn from(options: proto::DataTrackOptions) -> Self { - Self::new(options.name) + let frame_encoding = options.frame_encoding.map(|_| options.frame_encoding().into()); + let mut result = Self::new(options.name); + if let Some(schema) = options.schema { + result = result.with_schema(schema.into()); + } + if let Some(frame_encoding) = frame_encoding { + result = result.with_frame_encoding(frame_encoding); + } + result } } @@ -33,6 +42,10 @@ impl From for proto::DataTrackInfo { name: info.name().to_string(), sid: info.sid().to_string(), uses_e2ee: info.uses_e2ee(), + schema: info.schema().cloned().map(Into::into), + frame_encoding: info + .frame_encoding() + .map(|encoding| proto::DataTrackFrameEncoding::from(encoding) as i32), } } } @@ -73,6 +86,88 @@ impl From for DataTrackSubscribeOptions { } } +impl From for DataTrackSchemaEncoding { + fn from(encoding: proto::DataTrackSchemaEncoding) -> Self { + match encoding { + proto::DataTrackSchemaEncoding::Protobuf => Self::Protobuf, + proto::DataTrackSchemaEncoding::Flatbuffer => Self::Flatbuffer, + proto::DataTrackSchemaEncoding::Ros1Msg => Self::Ros1Msg, + proto::DataTrackSchemaEncoding::Ros2Msg => Self::Ros2Msg, + proto::DataTrackSchemaEncoding::Ros2Idl => Self::Ros2Idl, + proto::DataTrackSchemaEncoding::OmgIdl => Self::OmgIdl, + proto::DataTrackSchemaEncoding::JsonSchema => Self::JsonSchema, + proto::DataTrackSchemaEncoding::Other => Self::Other, + } + } +} + +impl From for proto::DataTrackSchemaEncoding { + fn from(encoding: DataTrackSchemaEncoding) -> Self { + match encoding { + DataTrackSchemaEncoding::Protobuf => Self::Protobuf, + DataTrackSchemaEncoding::Flatbuffer => Self::Flatbuffer, + DataTrackSchemaEncoding::Ros1Msg => Self::Ros1Msg, + DataTrackSchemaEncoding::Ros2Msg => Self::Ros2Msg, + DataTrackSchemaEncoding::Ros2Idl => Self::Ros2Idl, + DataTrackSchemaEncoding::OmgIdl => Self::OmgIdl, + DataTrackSchemaEncoding::JsonSchema => Self::JsonSchema, + DataTrackSchemaEncoding::Other => Self::Other, + // `DataTrackSchemaEncoding` is `#[non_exhaustive]`; map any future + // variant to the catch-all encoding. + _ => Self::Other, + } + } +} + +impl From for DataTrackFrameEncoding { + fn from(encoding: proto::DataTrackFrameEncoding) -> Self { + match encoding { + proto::DataTrackFrameEncoding::Ros1 => Self::Ros1, + proto::DataTrackFrameEncoding::Cdr => Self::Cdr, + proto::DataTrackFrameEncoding::Protobuf => Self::Protobuf, + proto::DataTrackFrameEncoding::Flatbuffer => Self::Flatbuffer, + proto::DataTrackFrameEncoding::Cbor => Self::Cbor, + proto::DataTrackFrameEncoding::Msgpack => Self::Msgpack, + proto::DataTrackFrameEncoding::Json => Self::Json, + proto::DataTrackFrameEncoding::Other => Self::Other, + } + } +} + +impl From for proto::DataTrackFrameEncoding { + fn from(encoding: DataTrackFrameEncoding) -> Self { + match encoding { + DataTrackFrameEncoding::Ros1 => Self::Ros1, + DataTrackFrameEncoding::Cdr => Self::Cdr, + DataTrackFrameEncoding::Protobuf => Self::Protobuf, + DataTrackFrameEncoding::Flatbuffer => Self::Flatbuffer, + DataTrackFrameEncoding::Cbor => Self::Cbor, + DataTrackFrameEncoding::Msgpack => Self::Msgpack, + DataTrackFrameEncoding::Json => Self::Json, + DataTrackFrameEncoding::Other => Self::Other, + // `DataTrackFrameEncoding` is `#[non_exhaustive]`; map any future + // variant to the catch-all encoding. + _ => Self::Other, + } + } +} + +impl From for DataTrackSchemaId { + fn from(msg: proto::DataTrackSchemaId) -> Self { + let encoding = msg.encoding().into(); + DataTrackSchemaId::new(msg.name, encoding) + } +} + +impl From for proto::DataTrackSchemaId { + fn from(id: DataTrackSchemaId) -> Self { + Self { + name: id.name().to_string(), + encoding: proto::DataTrackSchemaEncoding::from(id.encoding()) as i32, + } + } +} + impl From<&PublishError> for proto::PublishDataTrackErrorCode { fn from(err: &PublishError) -> Self { match err { diff --git a/livekit-ffi/src/server/participant.rs b/livekit-ffi/src/server/participant.rs index aff0bb6cc..0ed29b766 100644 --- a/livekit-ffi/src/server/participant.rs +++ b/livekit-ffi/src/server/participant.rs @@ -242,6 +242,48 @@ impl FfiParticipant { Ok(proto::TextStreamOpenResponse { async_id }) } + pub fn define_schema( + &self, + server: &'static FfiServer, + request: proto::DefineSchemaRequest, + ) -> FfiResult { + let async_id = server.resolve_async_id(request.request_async_id); + let local = self.guard_local_participant()?; + let schema_id = request.schema_id.into(); + + let handle = server.async_runtime.spawn(async move { + let res = local.define_schema(schema_id, request.definition).await; + let callback = + proto::DefineSchemaCallback { async_id, error: res.err().map(|e| e.to_string()) }; + let _ = server.send_event(callback.into()); + }); + server.watch_panic(handle); + Ok(proto::DefineSchemaResponse { async_id }) + } + + pub fn get_schema( + &self, + server: &'static FfiServer, + request: proto::GetSchemaRequest, + ) -> FfiResult { + let async_id = server.resolve_async_id(request.request_async_id); + let local = self.guard_local_participant()?; + let schema_id = request.schema_id.into(); + let participant = ParticipantIdentity::from(request.participant_identity); + + let handle = server.async_runtime.spawn(async move { + let result = local.get_schema(schema_id, participant).await; + let callback = proto::GetSchemaCallback { + async_id, + definition: result.as_ref().ok().cloned(), + error: result.as_ref().err().map(|e| e.to_string()), + }; + let _ = server.send_event(callback.into()); + }); + server.watch_panic(handle); + Ok(proto::GetSchemaResponse { async_id }) + } + pub fn publish_data_track( &self, server: &'static FfiServer, diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index 57c1e38d7..9b6aa5abe 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -1247,6 +1247,24 @@ fn on_publish_data_track( ffi_participant.publish_data_track(server, request) } +fn on_define_schema( + server: &'static FfiServer, + request: proto::DefineSchemaRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + ffi_participant.define_schema(server, request) +} + +fn on_get_schema( + server: &'static FfiServer, + request: proto::GetSchemaRequest, +) -> FfiResult { + let ffi_participant = + server.retrieve_handle::(request.local_participant_handle)?.clone(); + ffi_participant.get_schema(server, request) +} + fn on_local_data_track_is_published( server: &'static FfiServer, request: proto::LocalDataTrackIsPublishedRequest, @@ -1424,6 +1442,8 @@ pub fn handle_request( on_remote_data_track_set_pipeline_options(server, req)?.into() } Request::DataTrackStreamRead(req) => on_data_track_stream_read(server, req)?.into(), + Request::DefineSchema(req) => on_define_schema(server, req)?.into(), + Request::GetSchema(req) => on_get_schema(server, req)?.into(), // Platform Audio Request::NewPlatformAudio(req) => { platform_audio::on_new_platform_audio(server, req)?.into() diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index 4b09446be..2de4579fd 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit 4b09446beca5b3b5b02a3c424655f386521ca008 +Subproject commit 2de4579fdfc57e56869b29eb7635bb6a8ba74e42 diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 975c95629..30a8c0ac8 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -632,6 +632,25 @@ pub struct DataTrackInfo { /// Method used for end-to-end encryption (E2EE) on packet payloads. #[prost(enumeration="encryption::Type", tag="4")] pub encryption: i32, + /// Encoding for frame payloads on this track. If unspecified, the track is untyped. + #[prost(enumeration="DataTrackFrameEncoding", optional, tag="5")] + pub frame_encoding: ::core::option::Option, + /// ID of the schema used by frames on this track if the track is typed. + #[prost(message, optional, tag="6")] + pub schema: ::core::option::Option, +} +/// Identifier for a data track schema. +/// +/// Schemas with the same name but different encodings are distinct. +/// +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataTrackSchemaId { + /// This must be non-empty and no longer than 256 characters. + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(enumeration="DataTrackSchemaEncoding", tag="2")] + pub encoding: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -649,6 +668,37 @@ pub struct DataTrackSubscriptionOptions { #[prost(uint32, optional, tag="1")] pub target_fps: ::core::option::Option, } +/// Key used to uniquely identify a data blob for storage and retrieval. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataBlobKey { + #[prost(oneof="data_blob_key::Key", tags="1, 2")] + pub key: ::core::option::Option, +} +/// Nested message and enum types in `DataBlobKey`. +pub mod data_blob_key { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Key { + /// Generic string key, blob contains arbitrary data. + #[prost(string, tag="1")] + Generic(::prost::alloc::string::String), + /// Data track schema identifier, blob contains schema definition. + #[prost(message, tag="2")] + SchemaId(super::DataTrackSchemaId), + } +} +/// A blob of data stored in a room identified by a unique key. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DataBlob { + /// Unique key the data blob is identified by. + #[prost(message, optional, tag="1")] + pub key: ::core::option::Option, + /// Contents of the data blob. This must not exceed 50 KB. + #[prost(bytes="vec", tag="2")] + pub contents: ::prost::alloc::vec::Vec, +} /// provide information about available spatial layers #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -1780,6 +1830,118 @@ impl TrackSource { } } } +/// Encoding for frame payloads. +/// +/// Mirrors the well-known message encodings from the MCAP spec: +/// +/// +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum DataTrackFrameEncoding { + Unspecified = 0, + /// ROS 1: must be described by `ROS1_MSG` schema encoding. + Ros1 = 1, + /// CDR: must be described by `ROS2_MSG`, `ROS2_IDL`, or `OMG_IDL` schema encoding. + Cdr = 2, + /// Protocol Buffer: must be described by `PROTOBUF` schema encoding. + Protobuf = 3, + /// FlatBuffer: must be described by `FLATBUFFER` schema encoding. + Flatbuffer = 4, + /// CBOR: self-describing. + Cbor = 5, + /// MessagePack: self-describing. + Msgpack = 6, + /// JSON: self-describing or described by `JSON_SCHEMA` schema encoding. + Json = 7, +} +impl DataTrackFrameEncoding { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + DataTrackFrameEncoding::Unspecified => "DATA_TRACK_FRAME_ENCODING_UNSPECIFIED", + DataTrackFrameEncoding::Ros1 => "DATA_TRACK_FRAME_ENCODING_ROS1", + DataTrackFrameEncoding::Cdr => "DATA_TRACK_FRAME_ENCODING_CDR", + DataTrackFrameEncoding::Protobuf => "DATA_TRACK_FRAME_ENCODING_PROTOBUF", + DataTrackFrameEncoding::Flatbuffer => "DATA_TRACK_FRAME_ENCODING_FLATBUFFER", + DataTrackFrameEncoding::Cbor => "DATA_TRACK_FRAME_ENCODING_CBOR", + DataTrackFrameEncoding::Msgpack => "DATA_TRACK_FRAME_ENCODING_MSGPACK", + DataTrackFrameEncoding::Json => "DATA_TRACK_FRAME_ENCODING_JSON", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DATA_TRACK_FRAME_ENCODING_UNSPECIFIED" => Some(Self::Unspecified), + "DATA_TRACK_FRAME_ENCODING_ROS1" => Some(Self::Ros1), + "DATA_TRACK_FRAME_ENCODING_CDR" => Some(Self::Cdr), + "DATA_TRACK_FRAME_ENCODING_PROTOBUF" => Some(Self::Protobuf), + "DATA_TRACK_FRAME_ENCODING_FLATBUFFER" => Some(Self::Flatbuffer), + "DATA_TRACK_FRAME_ENCODING_CBOR" => Some(Self::Cbor), + "DATA_TRACK_FRAME_ENCODING_MSGPACK" => Some(Self::Msgpack), + "DATA_TRACK_FRAME_ENCODING_JSON" => Some(Self::Json), + _ => None, + } + } +} +/// Encoding for schema definitions. +/// +/// Mirrors the well-known schema encodings from the MCAP spec: +/// +/// +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum DataTrackSchemaEncoding { + Unspecified = 0, + /// Protocol Buffer IDL: describes `PROTOBUF` frame encoding. + Protobuf = 1, + /// FlatBuffer IDL: describes `FLATBUFFER` frame encoding. + Flatbuffer = 2, + /// ROS 1 Message: describes `ROS1` frame encoding. + Ros1Msg = 3, + /// ROS 2 Message: describes `CDR` frame encoding. + Ros2Msg = 4, + /// ROS 2 IDL: describes `CDR` frame encoding. + Ros2Idl = 5, + /// OMG IDL: describes `CDR` frame encoding. + OmgIdl = 6, + /// JSON Schema: describes `JSON` frame encoding. + JsonSchema = 7, +} +impl DataTrackSchemaEncoding { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + DataTrackSchemaEncoding::Unspecified => "DATA_TRACK_SCHEMA_ENCODING_UNSPECIFIED", + DataTrackSchemaEncoding::Protobuf => "DATA_TRACK_SCHEMA_ENCODING_PROTOBUF", + DataTrackSchemaEncoding::Flatbuffer => "DATA_TRACK_SCHEMA_ENCODING_FLATBUFFER", + DataTrackSchemaEncoding::Ros1Msg => "DATA_TRACK_SCHEMA_ENCODING_ROS1_MSG", + DataTrackSchemaEncoding::Ros2Msg => "DATA_TRACK_SCHEMA_ENCODING_ROS2_MSG", + DataTrackSchemaEncoding::Ros2Idl => "DATA_TRACK_SCHEMA_ENCODING_ROS2_IDL", + DataTrackSchemaEncoding::OmgIdl => "DATA_TRACK_SCHEMA_ENCODING_OMG_IDL", + DataTrackSchemaEncoding::JsonSchema => "DATA_TRACK_SCHEMA_ENCODING_JSON_SCHEMA", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DATA_TRACK_SCHEMA_ENCODING_UNSPECIFIED" => Some(Self::Unspecified), + "DATA_TRACK_SCHEMA_ENCODING_PROTOBUF" => Some(Self::Protobuf), + "DATA_TRACK_SCHEMA_ENCODING_FLATBUFFER" => Some(Self::Flatbuffer), + "DATA_TRACK_SCHEMA_ENCODING_ROS1_MSG" => Some(Self::Ros1Msg), + "DATA_TRACK_SCHEMA_ENCODING_ROS2_MSG" => Some(Self::Ros2Msg), + "DATA_TRACK_SCHEMA_ENCODING_ROS2_IDL" => Some(Self::Ros2Idl), + "DATA_TRACK_SCHEMA_ENCODING_OMG_IDL" => Some(Self::OmgIdl), + "DATA_TRACK_SCHEMA_ENCODING_JSON_SCHEMA" => Some(Self::JsonSchema), + _ => None, + } + } +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum DataTrackExtensionId { @@ -2196,10 +2358,9 @@ pub struct WebSource { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MediaSource { + /// TODO: DataConfig data = 4; #[prost(message, optional, tag="3")] pub audio: ::core::option::Option, - #[prost(message, optional, tag="4")] - pub data: ::core::option::Option, #[prost(oneof="media_source::Video", tags="1, 2")] pub video: ::core::option::Option, } @@ -2214,6 +2375,8 @@ pub mod media_source { ParticipantVideo(super::ParticipantVideo), } } +// --- Video Configuration --- + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ParticipantVideo { @@ -2229,9 +2392,10 @@ pub struct ParticipantVideo { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AudioConfig { - /// If empty, all audio captured in both channels. - /// If non-empty, only matching audio is captured and routed. Unmatched is excluded. - #[prost(message, repeated, tag="1")] + /// If true, all unmatched audio is recorded to both channels + #[prost(bool, tag="1")] + pub capture_all: bool, + #[prost(message, repeated, tag="2")] pub routes: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2260,15 +2424,15 @@ pub mod audio_route { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataConfig { - /// If empty, all data tracks captured. - /// If non-empty, only matching data tracks are captured. - #[prost(message, repeated, tag="1")] + #[prost(bool, tag="1")] + pub capture_all: bool, + #[prost(message, repeated, tag="2")] pub selectors: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DataSelector { - #[prost(oneof="data_selector::Match", tags="1, 2, 3")] + #[prost(oneof="data_selector::Match", tags="1, 2")] pub r#match: ::core::option::Option, } /// Nested message and enum types in `DataSelector`. @@ -2280,8 +2444,6 @@ pub mod data_selector { TrackId(::prost::alloc::string::String), #[prost(string, tag="2")] ParticipantIdentity(::prost::alloc::string::String), - #[prost(string, tag="3")] - Topic(::prost::alloc::string::String), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -2350,7 +2512,7 @@ pub mod output { Stream(super::StreamOutput), #[prost(message, tag="3")] Segments(super::SegmentedFileOutput), - /// 5 reserved for mcap; + /// TODO: DataOutput data = 5; #[prost(message, tag="4")] Images(super::ImageOutput), } @@ -2402,11 +2564,13 @@ pub struct SegmentedFileOutput { /// disable upload of manifest file (default false) #[prost(bool, tag="8")] pub disable_manifest: bool, + /// TODO: deprecate #[prost(oneof="segmented_file_output::Output", tags="5, 6, 7, 9")] pub output: ::core::option::Option, } /// Nested message and enum types in `SegmentedFileOutput`. pub mod segmented_file_output { + /// TODO: deprecate #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { @@ -2445,11 +2609,13 @@ pub struct ImageOutput { /// disable upload of manifest file (default false) #[prost(bool, tag="7")] pub disable_manifest: bool, + /// TODO: deprecate #[prost(oneof="image_output::Output", tags="8, 9, 10, 11")] pub output: ::core::option::Option, } /// Nested message and enum types in `ImageOutput`. pub mod image_output { + /// TODO: deprecate #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Output { @@ -2652,7 +2818,7 @@ pub struct EgressInfo { pub backup_storage_used: bool, #[prost(int32, tag="27")] pub retry_count: i32, - #[prost(oneof="egress_info::Request", tags="30, 4, 14, 19, 5, 6")] + #[prost(oneof="egress_info::Request", tags="29, 30, 4, 14, 19, 5, 6")] pub request: ::core::option::Option, // next ID: 31 @@ -2665,9 +2831,11 @@ pub mod egress_info { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Request { - /// StartEgressRequest egress = 29; + #[prost(message, tag="29")] + Egress(super::StartEgressRequest), #[prost(message, tag="30")] Replay(super::ExportReplayRequest), + /// TODO: deprecate #[prost(message, tag="4")] RoomComposite(super::RoomCompositeEgressRequest), #[prost(message, tag="14")] @@ -2890,7 +3058,7 @@ pub mod export_replay_request { Advanced(super::EncodingOptions), } } -// --- V1 --- +// TODO: deprecate --- V1 --- #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3497,7 +3665,7 @@ impl AudioMixing { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignalRequest { - #[prost(oneof="signal_request::Message", tags="1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21")] + #[prost(oneof="signal_request::Message", tags="1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23")] pub message: ::core::option::Option, } /// Nested message and enum types in `SignalRequest`. @@ -3565,12 +3733,18 @@ pub mod signal_request { /// Update subscription state for one or more data tracks #[prost(message, tag="21")] UpdateDataSubscription(super::UpdateDataSubscription), + /// Store a data blob. + #[prost(message, tag="22")] + StoreDataBlobRequest(super::StoreDataBlobRequest), + /// Retrieve a stored data blob. + #[prost(message, tag="23")] + GetDataBlobRequest(super::GetDataBlobRequest), } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignalResponse { - #[prost(oneof="signal_response::Message", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29")] + #[prost(oneof="signal_response::Message", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31")] pub message: ::core::option::Option, } /// Nested message and enum types in `SignalResponse`. @@ -3665,6 +3839,12 @@ pub mod signal_response { /// Sent to data track subscribers to provide mapping from track SIDs to handles. #[prost(message, tag="29")] DataTrackSubscriberHandles(super::DataTrackSubscriberHandles), + /// Sent in response to `StoreDataBlobRequest`. + #[prost(message, tag="30")] + StoreDataBlobResponse(super::StoreDataBlobResponse), + /// Sent in response to `GetDataBlobRequest`. + #[prost(message, tag="31")] + GetDataBlobResponse(super::GetDataBlobResponse), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -3745,6 +3925,13 @@ pub struct PublishDataTrackRequest { /// Method used for end-to-end encryption (E2EE) on frame payloads. #[prost(enumeration="encryption::Type", tag="3")] pub encryption: i32, + /// Encoding for frame payloads on this track. If unspecified, the track is untyped. + #[prost(enumeration="DataTrackFrameEncoding", optional, tag="4")] + pub frame_encoding: ::core::option::Option, + /// ID of the schema used by frames on this track if the track is typed. + /// If set, the associated schema must be stored with `StoreDataBlobRequest`. + #[prost(message, optional, tag="5")] + pub schema: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -3925,6 +4112,43 @@ pub mod update_data_subscription { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct StoreDataBlobRequest { + #[prost(uint32, tag="1")] + pub request_id: u32, + #[prost(message, optional, tag="2")] + pub blob: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StoreDataBlobResponse { + #[prost(uint32, tag="1")] + pub request_id: u32, + /// Unique key the data blob was stored under. + #[prost(message, optional, tag="2")] + pub key: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDataBlobRequest { + #[prost(uint32, tag="1")] + pub request_id: u32, + /// Identity of the participant who owns the blob. + #[prost(string, tag="2")] + pub participant_identity: ::prost::alloc::string::String, + /// Unique key of the data blob to retrieve. + #[prost(message, optional, tag="3")] + pub key: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDataBlobResponse { + #[prost(uint32, tag="1")] + pub request_id: u32, + #[prost(message, optional, tag="2")] + pub blob: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct UpdateTrackSettings { #[prost(string, repeated, tag="1")] pub track_sids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, @@ -4333,6 +4557,7 @@ pub mod request_response { InvalidName = 8, DuplicateHandle = 9, DuplicateName = 10, + InvalidRequest = 11, } impl Reason { /// String value of the enum field names used in the ProtoBuf definition. @@ -4352,6 +4577,7 @@ pub mod request_response { Reason::InvalidName => "INVALID_NAME", Reason::DuplicateHandle => "DUPLICATE_HANDLE", Reason::DuplicateName => "DUPLICATE_NAME", + Reason::InvalidRequest => "INVALID_REQUEST", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -4368,6 +4594,7 @@ pub mod request_response { "INVALID_NAME" => Some(Self::InvalidName), "DUPLICATE_HANDLE" => Some(Self::DuplicateHandle), "DUPLICATE_NAME" => Some(Self::DuplicateName), + "INVALID_REQUEST" => Some(Self::InvalidRequest), _ => None, } } diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index c80ca9821..dd9b815d7 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -4906,10 +4906,16 @@ impl serde::Serialize for AudioConfig { { use serde::ser::SerializeStruct; let mut len = 0; + if self.capture_all { + len += 1; + } if !self.routes.is_empty() { len += 1; } let mut struct_ser = serializer.serialize_struct("livekit.AudioConfig", len)?; + if self.capture_all { + struct_ser.serialize_field("captureAll", &self.capture_all)?; + } if !self.routes.is_empty() { struct_ser.serialize_field("routes", &self.routes)?; } @@ -4923,11 +4929,14 @@ impl<'de> serde::Deserialize<'de> for AudioConfig { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "capture_all", + "captureAll", "routes", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { + CaptureAll, Routes, __SkipField__, } @@ -4951,6 +4960,7 @@ impl<'de> serde::Deserialize<'de> for AudioConfig { E: serde::de::Error, { match value { + "captureAll" | "capture_all" => Ok(GeneratedField::CaptureAll), "routes" => Ok(GeneratedField::Routes), _ => Ok(GeneratedField::__SkipField__), } @@ -4971,9 +4981,16 @@ impl<'de> serde::Deserialize<'de> for AudioConfig { where V: serde::de::MapAccess<'de>, { + let mut capture_all__ = None; let mut routes__ = None; while let Some(k) = map_.next_key()? { match k { + GeneratedField::CaptureAll => { + if capture_all__.is_some() { + return Err(serde::de::Error::duplicate_field("captureAll")); + } + capture_all__ = Some(map_.next_value()?); + } GeneratedField::Routes => { if routes__.is_some() { return Err(serde::de::Error::duplicate_field("routes")); @@ -4986,6 +5003,7 @@ impl<'de> serde::Deserialize<'de> for AudioConfig { } } Ok(AudioConfig { + capture_all: capture_all__.unwrap_or_default(), routes: routes__.unwrap_or_default(), }) } @@ -10468,6 +10486,235 @@ impl<'de> serde::Deserialize<'de> for CreateSipTrunkRequest { deserializer.deserialize_struct("livekit.CreateSIPTrunkRequest", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for DataBlob { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.key.is_some() { + len += 1; + } + if !self.contents.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DataBlob", len)?; + if let Some(v) = self.key.as_ref() { + struct_ser.serialize_field("key", v)?; + } + if !self.contents.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("contents", pbjson::private::base64::encode(&self.contents).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DataBlob { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "key", + "contents", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Key, + Contents, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "key" => Ok(GeneratedField::Key), + "contents" => Ok(GeneratedField::Contents), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DataBlob; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.DataBlob") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut key__ = None; + let mut contents__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Key => { + if key__.is_some() { + return Err(serde::de::Error::duplicate_field("key")); + } + key__ = map_.next_value()?; + } + GeneratedField::Contents => { + if contents__.is_some() { + return Err(serde::de::Error::duplicate_field("contents")); + } + contents__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(DataBlob { + key: key__, + contents: contents__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("livekit.DataBlob", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for DataBlobKey { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.key.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DataBlobKey", len)?; + if let Some(v) = self.key.as_ref() { + match v { + data_blob_key::Key::Generic(v) => { + struct_ser.serialize_field("generic", v)?; + } + data_blob_key::Key::SchemaId(v) => { + struct_ser.serialize_field("schemaId", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DataBlobKey { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "generic", + "schema_id", + "schemaId", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Generic, + SchemaId, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "generic" => Ok(GeneratedField::Generic), + "schemaId" | "schema_id" => Ok(GeneratedField::SchemaId), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DataBlobKey; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.DataBlobKey") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut key__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Generic => { + if key__.is_some() { + return Err(serde::de::Error::duplicate_field("generic")); + } + key__ = map_.next_value::<::std::option::Option<_>>()?.map(data_blob_key::Key::Generic); + } + GeneratedField::SchemaId => { + if key__.is_some() { + return Err(serde::de::Error::duplicate_field("schemaId")); + } + key__ = map_.next_value::<::std::option::Option<_>>()?.map(data_blob_key::Key::SchemaId) +; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(DataBlobKey { + key: key__, + }) + } + } + deserializer.deserialize_struct("livekit.DataBlobKey", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for DataChannelInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -10725,10 +10972,16 @@ impl serde::Serialize for DataConfig { { use serde::ser::SerializeStruct; let mut len = 0; + if self.capture_all { + len += 1; + } if !self.selectors.is_empty() { len += 1; } let mut struct_ser = serializer.serialize_struct("livekit.DataConfig", len)?; + if self.capture_all { + struct_ser.serialize_field("captureAll", &self.capture_all)?; + } if !self.selectors.is_empty() { struct_ser.serialize_field("selectors", &self.selectors)?; } @@ -10742,11 +10995,14 @@ impl<'de> serde::Deserialize<'de> for DataConfig { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "capture_all", + "captureAll", "selectors", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { + CaptureAll, Selectors, __SkipField__, } @@ -10770,6 +11026,7 @@ impl<'de> serde::Deserialize<'de> for DataConfig { E: serde::de::Error, { match value { + "captureAll" | "capture_all" => Ok(GeneratedField::CaptureAll), "selectors" => Ok(GeneratedField::Selectors), _ => Ok(GeneratedField::__SkipField__), } @@ -10790,9 +11047,16 @@ impl<'de> serde::Deserialize<'de> for DataConfig { where V: serde::de::MapAccess<'de>, { + let mut capture_all__ = None; let mut selectors__ = None; while let Some(k) = map_.next_key()? { match k { + GeneratedField::CaptureAll => { + if capture_all__.is_some() { + return Err(serde::de::Error::duplicate_field("captureAll")); + } + capture_all__ = Some(map_.next_value()?); + } GeneratedField::Selectors => { if selectors__.is_some() { return Err(serde::de::Error::duplicate_field("selectors")); @@ -10805,6 +11069,7 @@ impl<'de> serde::Deserialize<'de> for DataConfig { } } Ok(DataConfig { + capture_all: capture_all__.unwrap_or_default(), selectors: selectors__.unwrap_or_default(), }) } @@ -11260,9 +11525,6 @@ impl serde::Serialize for DataSelector { data_selector::Match::ParticipantIdentity(v) => { struct_ser.serialize_field("participantIdentity", v)?; } - data_selector::Match::Topic(v) => { - struct_ser.serialize_field("topic", v)?; - } } } struct_ser.end() @@ -11279,14 +11541,12 @@ impl<'de> serde::Deserialize<'de> for DataSelector { "trackId", "participant_identity", "participantIdentity", - "topic", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { TrackId, ParticipantIdentity, - Topic, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -11311,7 +11571,6 @@ impl<'de> serde::Deserialize<'de> for DataSelector { match value { "trackId" | "track_id" => Ok(GeneratedField::TrackId), "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), - "topic" => Ok(GeneratedField::Topic), _ => Ok(GeneratedField::__SkipField__), } } @@ -11346,12 +11605,6 @@ impl<'de> serde::Deserialize<'de> for DataSelector { } r#match__ = map_.next_value::<::std::option::Option<_>>()?.map(data_selector::Match::ParticipantIdentity); } - GeneratedField::Topic => { - if r#match__.is_some() { - return Err(serde::de::Error::duplicate_field("topic")); - } - r#match__ = map_.next_value::<::std::option::Option<_>>()?.map(data_selector::Match::Topic); - } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -12526,6 +12779,95 @@ impl<'de> serde::Deserialize<'de> for DataTrackExtensionParticipantSid { deserializer.deserialize_struct("livekit.DataTrackExtensionParticipantSid", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for DataTrackFrameEncoding { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Unspecified => "DATA_TRACK_FRAME_ENCODING_UNSPECIFIED", + Self::Ros1 => "DATA_TRACK_FRAME_ENCODING_ROS1", + Self::Cdr => "DATA_TRACK_FRAME_ENCODING_CDR", + Self::Protobuf => "DATA_TRACK_FRAME_ENCODING_PROTOBUF", + Self::Flatbuffer => "DATA_TRACK_FRAME_ENCODING_FLATBUFFER", + Self::Cbor => "DATA_TRACK_FRAME_ENCODING_CBOR", + Self::Msgpack => "DATA_TRACK_FRAME_ENCODING_MSGPACK", + Self::Json => "DATA_TRACK_FRAME_ENCODING_JSON", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for DataTrackFrameEncoding { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "DATA_TRACK_FRAME_ENCODING_UNSPECIFIED", + "DATA_TRACK_FRAME_ENCODING_ROS1", + "DATA_TRACK_FRAME_ENCODING_CDR", + "DATA_TRACK_FRAME_ENCODING_PROTOBUF", + "DATA_TRACK_FRAME_ENCODING_FLATBUFFER", + "DATA_TRACK_FRAME_ENCODING_CBOR", + "DATA_TRACK_FRAME_ENCODING_MSGPACK", + "DATA_TRACK_FRAME_ENCODING_JSON", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DataTrackFrameEncoding; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "DATA_TRACK_FRAME_ENCODING_UNSPECIFIED" => Ok(DataTrackFrameEncoding::Unspecified), + "DATA_TRACK_FRAME_ENCODING_ROS1" => Ok(DataTrackFrameEncoding::Ros1), + "DATA_TRACK_FRAME_ENCODING_CDR" => Ok(DataTrackFrameEncoding::Cdr), + "DATA_TRACK_FRAME_ENCODING_PROTOBUF" => Ok(DataTrackFrameEncoding::Protobuf), + "DATA_TRACK_FRAME_ENCODING_FLATBUFFER" => Ok(DataTrackFrameEncoding::Flatbuffer), + "DATA_TRACK_FRAME_ENCODING_CBOR" => Ok(DataTrackFrameEncoding::Cbor), + "DATA_TRACK_FRAME_ENCODING_MSGPACK" => Ok(DataTrackFrameEncoding::Msgpack), + "DATA_TRACK_FRAME_ENCODING_JSON" => Ok(DataTrackFrameEncoding::Json), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for DataTrackInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -12546,6 +12888,12 @@ impl serde::Serialize for DataTrackInfo { if self.encryption != 0 { len += 1; } + if self.frame_encoding.is_some() { + len += 1; + } + if self.schema.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.DataTrackInfo", len)?; if self.pub_handle != 0 { struct_ser.serialize_field("pubHandle", &self.pub_handle)?; @@ -12561,6 +12909,14 @@ impl serde::Serialize for DataTrackInfo { .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.encryption)))?; struct_ser.serialize_field("encryption", &v)?; } + if let Some(v) = self.frame_encoding.as_ref() { + let v = DataTrackFrameEncoding::try_from(*v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; + struct_ser.serialize_field("frameEncoding", &v)?; + } + if let Some(v) = self.schema.as_ref() { + struct_ser.serialize_field("schema", v)?; + } struct_ser.end() } } @@ -12576,6 +12932,9 @@ impl<'de> serde::Deserialize<'de> for DataTrackInfo { "sid", "name", "encryption", + "frame_encoding", + "frameEncoding", + "schema", ]; #[allow(clippy::enum_variant_names)] @@ -12584,6 +12943,249 @@ impl<'de> serde::Deserialize<'de> for DataTrackInfo { Sid, Name, Encryption, + FrameEncoding, + Schema, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "pubHandle" | "pub_handle" => Ok(GeneratedField::PubHandle), + "sid" => Ok(GeneratedField::Sid), + "name" => Ok(GeneratedField::Name), + "encryption" => Ok(GeneratedField::Encryption), + "frameEncoding" | "frame_encoding" => Ok(GeneratedField::FrameEncoding), + "schema" => Ok(GeneratedField::Schema), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DataTrackInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.DataTrackInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut pub_handle__ = None; + let mut sid__ = None; + let mut name__ = None; + let mut encryption__ = None; + let mut frame_encoding__ = None; + let mut schema__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::PubHandle => { + if pub_handle__.is_some() { + return Err(serde::de::Error::duplicate_field("pubHandle")); + } + pub_handle__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Sid => { + if sid__.is_some() { + return Err(serde::de::Error::duplicate_field("sid")); + } + sid__ = Some(map_.next_value()?); + } + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); + } + name__ = Some(map_.next_value()?); + } + GeneratedField::Encryption => { + if encryption__.is_some() { + return Err(serde::de::Error::duplicate_field("encryption")); + } + encryption__ = Some(map_.next_value::()? as i32); + } + GeneratedField::FrameEncoding => { + if frame_encoding__.is_some() { + return Err(serde::de::Error::duplicate_field("frameEncoding")); + } + frame_encoding__ = map_.next_value::<::std::option::Option>()?.map(|x| x as i32); + } + GeneratedField::Schema => { + if schema__.is_some() { + return Err(serde::de::Error::duplicate_field("schema")); + } + schema__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(DataTrackInfo { + pub_handle: pub_handle__.unwrap_or_default(), + sid: sid__.unwrap_or_default(), + name: name__.unwrap_or_default(), + encryption: encryption__.unwrap_or_default(), + frame_encoding: frame_encoding__, + schema: schema__, + }) + } + } + deserializer.deserialize_struct("livekit.DataTrackInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for DataTrackSchemaEncoding { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Unspecified => "DATA_TRACK_SCHEMA_ENCODING_UNSPECIFIED", + Self::Protobuf => "DATA_TRACK_SCHEMA_ENCODING_PROTOBUF", + Self::Flatbuffer => "DATA_TRACK_SCHEMA_ENCODING_FLATBUFFER", + Self::Ros1Msg => "DATA_TRACK_SCHEMA_ENCODING_ROS1_MSG", + Self::Ros2Msg => "DATA_TRACK_SCHEMA_ENCODING_ROS2_MSG", + Self::Ros2Idl => "DATA_TRACK_SCHEMA_ENCODING_ROS2_IDL", + Self::OmgIdl => "DATA_TRACK_SCHEMA_ENCODING_OMG_IDL", + Self::JsonSchema => "DATA_TRACK_SCHEMA_ENCODING_JSON_SCHEMA", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for DataTrackSchemaEncoding { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "DATA_TRACK_SCHEMA_ENCODING_UNSPECIFIED", + "DATA_TRACK_SCHEMA_ENCODING_PROTOBUF", + "DATA_TRACK_SCHEMA_ENCODING_FLATBUFFER", + "DATA_TRACK_SCHEMA_ENCODING_ROS1_MSG", + "DATA_TRACK_SCHEMA_ENCODING_ROS2_MSG", + "DATA_TRACK_SCHEMA_ENCODING_ROS2_IDL", + "DATA_TRACK_SCHEMA_ENCODING_OMG_IDL", + "DATA_TRACK_SCHEMA_ENCODING_JSON_SCHEMA", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DataTrackSchemaEncoding; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "DATA_TRACK_SCHEMA_ENCODING_UNSPECIFIED" => Ok(DataTrackSchemaEncoding::Unspecified), + "DATA_TRACK_SCHEMA_ENCODING_PROTOBUF" => Ok(DataTrackSchemaEncoding::Protobuf), + "DATA_TRACK_SCHEMA_ENCODING_FLATBUFFER" => Ok(DataTrackSchemaEncoding::Flatbuffer), + "DATA_TRACK_SCHEMA_ENCODING_ROS1_MSG" => Ok(DataTrackSchemaEncoding::Ros1Msg), + "DATA_TRACK_SCHEMA_ENCODING_ROS2_MSG" => Ok(DataTrackSchemaEncoding::Ros2Msg), + "DATA_TRACK_SCHEMA_ENCODING_ROS2_IDL" => Ok(DataTrackSchemaEncoding::Ros2Idl), + "DATA_TRACK_SCHEMA_ENCODING_OMG_IDL" => Ok(DataTrackSchemaEncoding::OmgIdl), + "DATA_TRACK_SCHEMA_ENCODING_JSON_SCHEMA" => Ok(DataTrackSchemaEncoding::JsonSchema), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for DataTrackSchemaId { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.name.is_empty() { + len += 1; + } + if self.encoding != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.DataTrackSchemaId", len)?; + if !self.name.is_empty() { + struct_ser.serialize_field("name", &self.name)?; + } + if self.encoding != 0 { + let v = DataTrackSchemaEncoding::try_from(self.encoding) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.encoding)))?; + struct_ser.serialize_field("encoding", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DataTrackSchemaId { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "name", + "encoding", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Name, + Encoding, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -12606,10 +13208,8 @@ impl<'de> serde::Deserialize<'de> for DataTrackInfo { E: serde::de::Error, { match value { - "pubHandle" | "pub_handle" => Ok(GeneratedField::PubHandle), - "sid" => Ok(GeneratedField::Sid), "name" => Ok(GeneratedField::Name), - "encryption" => Ok(GeneratedField::Encryption), + "encoding" => Ok(GeneratedField::Encoding), _ => Ok(GeneratedField::__SkipField__), } } @@ -12619,62 +13219,44 @@ impl<'de> serde::Deserialize<'de> for DataTrackInfo { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = DataTrackInfo; + type Value = DataTrackSchemaId; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.DataTrackInfo") + formatter.write_str("struct livekit.DataTrackSchemaId") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut pub_handle__ = None; - let mut sid__ = None; let mut name__ = None; - let mut encryption__ = None; + let mut encoding__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::PubHandle => { - if pub_handle__.is_some() { - return Err(serde::de::Error::duplicate_field("pubHandle")); - } - pub_handle__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::Sid => { - if sid__.is_some() { - return Err(serde::de::Error::duplicate_field("sid")); - } - sid__ = Some(map_.next_value()?); - } GeneratedField::Name => { if name__.is_some() { return Err(serde::de::Error::duplicate_field("name")); } name__ = Some(map_.next_value()?); } - GeneratedField::Encryption => { - if encryption__.is_some() { - return Err(serde::de::Error::duplicate_field("encryption")); + GeneratedField::Encoding => { + if encoding__.is_some() { + return Err(serde::de::Error::duplicate_field("encoding")); } - encryption__ = Some(map_.next_value::()? as i32); + encoding__ = Some(map_.next_value::()? as i32); } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(DataTrackInfo { - pub_handle: pub_handle__.unwrap_or_default(), - sid: sid__.unwrap_or_default(), + Ok(DataTrackSchemaId { name: name__.unwrap_or_default(), - encryption: encryption__.unwrap_or_default(), + encoding: encoding__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("livekit.DataTrackInfo", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.DataTrackSchemaId", FIELDS, GeneratedVisitor) } } impl serde::Serialize for DataTrackSubscriberHandles { @@ -14944,6 +15526,9 @@ impl serde::Serialize for EgressInfo { } if let Some(v) = self.request.as_ref() { match v { + egress_info::Request::Egress(v) => { + struct_ser.serialize_field("egress", v)?; + } egress_info::Request::Replay(v) => { struct_ser.serialize_field("replay", v)?; } @@ -15020,6 +15605,7 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { "backupStorageUsed", "retry_count", "retryCount", + "egress", "replay", "room_composite", "roomComposite", @@ -15053,6 +15639,7 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { ManifestLocation, BackupStorageUsed, RetryCount, + Egress, Replay, RoomComposite, Web, @@ -15102,6 +15689,7 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { "manifestLocation" | "manifest_location" => Ok(GeneratedField::ManifestLocation), "backupStorageUsed" | "backup_storage_used" => Ok(GeneratedField::BackupStorageUsed), "retryCount" | "retry_count" => Ok(GeneratedField::RetryCount), + "egress" => Ok(GeneratedField::Egress), "replay" => Ok(GeneratedField::Replay), "roomComposite" | "room_composite" => Ok(GeneratedField::RoomComposite), "web" => Ok(GeneratedField::Web), @@ -15270,6 +15858,13 @@ impl<'de> serde::Deserialize<'de> for EgressInfo { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::Egress => { + if request__.is_some() { + return Err(serde::de::Error::duplicate_field("egress")); + } + request__ = map_.next_value::<::std::option::Option<_>>()?.map(egress_info::Request::Egress) +; + } GeneratedField::Replay => { if request__.is_some() { return Err(serde::de::Error::duplicate_field("replay")); @@ -18265,9 +18860,263 @@ impl<'de> serde::Deserialize<'de> for GcpUpload { E: serde::de::Error, { match value { - "credentials" => Ok(GeneratedField::Credentials), - "bucket" => Ok(GeneratedField::Bucket), - "proxy" => Ok(GeneratedField::Proxy), + "credentials" => Ok(GeneratedField::Credentials), + "bucket" => Ok(GeneratedField::Bucket), + "proxy" => Ok(GeneratedField::Proxy), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GcpUpload; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.GCPUpload") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut credentials__ = None; + let mut bucket__ = None; + let mut proxy__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Credentials => { + if credentials__.is_some() { + return Err(serde::de::Error::duplicate_field("credentials")); + } + credentials__ = Some(map_.next_value()?); + } + GeneratedField::Bucket => { + if bucket__.is_some() { + return Err(serde::de::Error::duplicate_field("bucket")); + } + bucket__ = Some(map_.next_value()?); + } + GeneratedField::Proxy => { + if proxy__.is_some() { + return Err(serde::de::Error::duplicate_field("proxy")); + } + proxy__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(GcpUpload { + credentials: credentials__.unwrap_or_default(), + bucket: bucket__.unwrap_or_default(), + proxy: proxy__, + }) + } + } + deserializer.deserialize_struct("livekit.GCPUpload", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GetDataBlobRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.request_id != 0 { + len += 1; + } + if !self.participant_identity.is_empty() { + len += 1; + } + if self.key.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.GetDataBlobRequest", len)?; + if self.request_id != 0 { + struct_ser.serialize_field("requestId", &self.request_id)?; + } + if !self.participant_identity.is_empty() { + struct_ser.serialize_field("participantIdentity", &self.participant_identity)?; + } + if let Some(v) = self.key.as_ref() { + struct_ser.serialize_field("key", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetDataBlobRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "request_id", + "requestId", + "participant_identity", + "participantIdentity", + "key", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + RequestId, + ParticipantIdentity, + Key, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "requestId" | "request_id" => Ok(GeneratedField::RequestId), + "participantIdentity" | "participant_identity" => Ok(GeneratedField::ParticipantIdentity), + "key" => Ok(GeneratedField::Key), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetDataBlobRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.GetDataBlobRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut request_id__ = None; + let mut participant_identity__ = None; + let mut key__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::RequestId => { + if request_id__.is_some() { + return Err(serde::de::Error::duplicate_field("requestId")); + } + request_id__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ParticipantIdentity => { + if participant_identity__.is_some() { + return Err(serde::de::Error::duplicate_field("participantIdentity")); + } + participant_identity__ = Some(map_.next_value()?); + } + GeneratedField::Key => { + if key__.is_some() { + return Err(serde::de::Error::duplicate_field("key")); + } + key__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(GetDataBlobRequest { + request_id: request_id__.unwrap_or_default(), + participant_identity: participant_identity__.unwrap_or_default(), + key: key__, + }) + } + } + deserializer.deserialize_struct("livekit.GetDataBlobRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GetDataBlobResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.request_id != 0 { + len += 1; + } + if self.blob.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.GetDataBlobResponse", len)?; + if self.request_id != 0 { + struct_ser.serialize_field("requestId", &self.request_id)?; + } + if let Some(v) = self.blob.as_ref() { + struct_ser.serialize_field("blob", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetDataBlobResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "request_id", + "requestId", + "blob", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + RequestId, + Blob, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "requestId" | "request_id" => Ok(GeneratedField::RequestId), + "blob" => Ok(GeneratedField::Blob), _ => Ok(GeneratedField::__SkipField__), } } @@ -18277,52 +19126,46 @@ impl<'de> serde::Deserialize<'de> for GcpUpload { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GcpUpload; + type Value = GetDataBlobResponse; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct livekit.GCPUpload") + formatter.write_str("struct livekit.GetDataBlobResponse") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut credentials__ = None; - let mut bucket__ = None; - let mut proxy__ = None; + let mut request_id__ = None; + let mut blob__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Credentials => { - if credentials__.is_some() { - return Err(serde::de::Error::duplicate_field("credentials")); - } - credentials__ = Some(map_.next_value()?); - } - GeneratedField::Bucket => { - if bucket__.is_some() { - return Err(serde::de::Error::duplicate_field("bucket")); + GeneratedField::RequestId => { + if request_id__.is_some() { + return Err(serde::de::Error::duplicate_field("requestId")); } - bucket__ = Some(map_.next_value()?); + request_id__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::Proxy => { - if proxy__.is_some() { - return Err(serde::de::Error::duplicate_field("proxy")); + GeneratedField::Blob => { + if blob__.is_some() { + return Err(serde::de::Error::duplicate_field("blob")); } - proxy__ = map_.next_value()?; + blob__ = map_.next_value()?; } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(GcpUpload { - credentials: credentials__.unwrap_or_default(), - bucket: bucket__.unwrap_or_default(), - proxy: proxy__, + Ok(GetDataBlobResponse { + request_id: request_id__.unwrap_or_default(), + blob: blob__, }) } } - deserializer.deserialize_struct("livekit.GCPUpload", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("livekit.GetDataBlobResponse", FIELDS, GeneratedVisitor) } } impl serde::Serialize for GetSipInboundTrunkRequest { @@ -25308,9 +26151,6 @@ impl serde::Serialize for MediaSource { if self.audio.is_some() { len += 1; } - if self.data.is_some() { - len += 1; - } if self.video.is_some() { len += 1; } @@ -25318,9 +26158,6 @@ impl serde::Serialize for MediaSource { if let Some(v) = self.audio.as_ref() { struct_ser.serialize_field("audio", v)?; } - if let Some(v) = self.data.as_ref() { - struct_ser.serialize_field("data", v)?; - } if let Some(v) = self.video.as_ref() { match v { media_source::Video::VideoTrackId(v) => { @@ -25342,7 +26179,6 @@ impl<'de> serde::Deserialize<'de> for MediaSource { { const FIELDS: &[&str] = &[ "audio", - "data", "video_track_id", "videoTrackId", "participant_video", @@ -25352,7 +26188,6 @@ impl<'de> serde::Deserialize<'de> for MediaSource { #[allow(clippy::enum_variant_names)] enum GeneratedField { Audio, - Data, VideoTrackId, ParticipantVideo, __SkipField__, @@ -25378,7 +26213,6 @@ impl<'de> serde::Deserialize<'de> for MediaSource { { match value { "audio" => Ok(GeneratedField::Audio), - "data" => Ok(GeneratedField::Data), "videoTrackId" | "video_track_id" => Ok(GeneratedField::VideoTrackId), "participantVideo" | "participant_video" => Ok(GeneratedField::ParticipantVideo), _ => Ok(GeneratedField::__SkipField__), @@ -25401,7 +26235,6 @@ impl<'de> serde::Deserialize<'de> for MediaSource { V: serde::de::MapAccess<'de>, { let mut audio__ = None; - let mut data__ = None; let mut video__ = None; while let Some(k) = map_.next_key()? { match k { @@ -25411,12 +26244,6 @@ impl<'de> serde::Deserialize<'de> for MediaSource { } audio__ = map_.next_value()?; } - GeneratedField::Data => { - if data__.is_some() { - return Err(serde::de::Error::duplicate_field("data")); - } - data__ = map_.next_value()?; - } GeneratedField::VideoTrackId => { if video__.is_some() { return Err(serde::de::Error::duplicate_field("videoTrackId")); @@ -25437,7 +26264,6 @@ impl<'de> serde::Deserialize<'de> for MediaSource { } Ok(MediaSource { audio: audio__, - data: data__, video: video__, }) } @@ -29566,6 +30392,12 @@ impl serde::Serialize for PublishDataTrackRequest { if self.encryption != 0 { len += 1; } + if self.frame_encoding.is_some() { + len += 1; + } + if self.schema.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.PublishDataTrackRequest", len)?; if self.pub_handle != 0 { struct_ser.serialize_field("pubHandle", &self.pub_handle)?; @@ -29578,6 +30410,14 @@ impl serde::Serialize for PublishDataTrackRequest { .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.encryption)))?; struct_ser.serialize_field("encryption", &v)?; } + if let Some(v) = self.frame_encoding.as_ref() { + let v = DataTrackFrameEncoding::try_from(*v) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", *v)))?; + struct_ser.serialize_field("frameEncoding", &v)?; + } + if let Some(v) = self.schema.as_ref() { + struct_ser.serialize_field("schema", v)?; + } struct_ser.end() } } @@ -29592,6 +30432,9 @@ impl<'de> serde::Deserialize<'de> for PublishDataTrackRequest { "pubHandle", "name", "encryption", + "frame_encoding", + "frameEncoding", + "schema", ]; #[allow(clippy::enum_variant_names)] @@ -29599,6 +30442,8 @@ impl<'de> serde::Deserialize<'de> for PublishDataTrackRequest { PubHandle, Name, Encryption, + FrameEncoding, + Schema, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -29624,6 +30469,8 @@ impl<'de> serde::Deserialize<'de> for PublishDataTrackRequest { "pubHandle" | "pub_handle" => Ok(GeneratedField::PubHandle), "name" => Ok(GeneratedField::Name), "encryption" => Ok(GeneratedField::Encryption), + "frameEncoding" | "frame_encoding" => Ok(GeneratedField::FrameEncoding), + "schema" => Ok(GeneratedField::Schema), _ => Ok(GeneratedField::__SkipField__), } } @@ -29646,6 +30493,8 @@ impl<'de> serde::Deserialize<'de> for PublishDataTrackRequest { let mut pub_handle__ = None; let mut name__ = None; let mut encryption__ = None; + let mut frame_encoding__ = None; + let mut schema__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::PubHandle => { @@ -29668,6 +30517,18 @@ impl<'de> serde::Deserialize<'de> for PublishDataTrackRequest { } encryption__ = Some(map_.next_value::()? as i32); } + GeneratedField::FrameEncoding => { + if frame_encoding__.is_some() { + return Err(serde::de::Error::duplicate_field("frameEncoding")); + } + frame_encoding__ = map_.next_value::<::std::option::Option>()?.map(|x| x as i32); + } + GeneratedField::Schema => { + if schema__.is_some() { + return Err(serde::de::Error::duplicate_field("schema")); + } + schema__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -29677,6 +30538,8 @@ impl<'de> serde::Deserialize<'de> for PublishDataTrackRequest { pub_handle: pub_handle__.unwrap_or_default(), name: name__.unwrap_or_default(), encryption: encryption__.unwrap_or_default(), + frame_encoding: frame_encoding__, + schema: schema__, }) } } @@ -32891,6 +33754,7 @@ impl serde::Serialize for request_response::Reason { Self::InvalidName => "INVALID_NAME", Self::DuplicateHandle => "DUPLICATE_HANDLE", Self::DuplicateName => "DUPLICATE_NAME", + Self::InvalidRequest => "INVALID_REQUEST", }; serializer.serialize_str(variant) } @@ -32913,6 +33777,7 @@ impl<'de> serde::Deserialize<'de> for request_response::Reason { "INVALID_NAME", "DUPLICATE_HANDLE", "DUPLICATE_NAME", + "INVALID_REQUEST", ]; struct GeneratedVisitor; @@ -32964,6 +33829,7 @@ impl<'de> serde::Deserialize<'de> for request_response::Reason { "INVALID_NAME" => Ok(request_response::Reason::InvalidName), "DUPLICATE_HANDLE" => Ok(request_response::Reason::DuplicateHandle), "DUPLICATE_NAME" => Ok(request_response::Reason::DuplicateName), + "INVALID_REQUEST" => Ok(request_response::Reason::InvalidRequest), _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), } } @@ -42797,6 +43663,12 @@ impl serde::Serialize for SignalRequest { signal_request::Message::UpdateDataSubscription(v) => { struct_ser.serialize_field("updateDataSubscription", v)?; } + signal_request::Message::StoreDataBlobRequest(v) => { + struct_ser.serialize_field("storeDataBlobRequest", v)?; + } + signal_request::Message::GetDataBlobRequest(v) => { + struct_ser.serialize_field("getDataBlobRequest", v)?; + } } } struct_ser.end() @@ -42841,6 +43713,10 @@ impl<'de> serde::Deserialize<'de> for SignalRequest { "unpublishDataTrackRequest", "update_data_subscription", "updateDataSubscription", + "store_data_blob_request", + "storeDataBlobRequest", + "get_data_blob_request", + "getDataBlobRequest", ]; #[allow(clippy::enum_variant_names)] @@ -42865,6 +43741,8 @@ impl<'de> serde::Deserialize<'de> for SignalRequest { PublishDataTrackRequest, UnpublishDataTrackRequest, UpdateDataSubscription, + StoreDataBlobRequest, + GetDataBlobRequest, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -42907,6 +43785,8 @@ impl<'de> serde::Deserialize<'de> for SignalRequest { "publishDataTrackRequest" | "publish_data_track_request" => Ok(GeneratedField::PublishDataTrackRequest), "unpublishDataTrackRequest" | "unpublish_data_track_request" => Ok(GeneratedField::UnpublishDataTrackRequest), "updateDataSubscription" | "update_data_subscription" => Ok(GeneratedField::UpdateDataSubscription), + "storeDataBlobRequest" | "store_data_blob_request" => Ok(GeneratedField::StoreDataBlobRequest), + "getDataBlobRequest" | "get_data_blob_request" => Ok(GeneratedField::GetDataBlobRequest), _ => Ok(GeneratedField::__SkipField__), } } @@ -43066,6 +43946,20 @@ impl<'de> serde::Deserialize<'de> for SignalRequest { return Err(serde::de::Error::duplicate_field("updateDataSubscription")); } message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_request::Message::UpdateDataSubscription) +; + } + GeneratedField::StoreDataBlobRequest => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("storeDataBlobRequest")); + } + message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_request::Message::StoreDataBlobRequest) +; + } + GeneratedField::GetDataBlobRequest => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("getDataBlobRequest")); + } + message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_request::Message::GetDataBlobRequest) ; } GeneratedField::__SkipField__ => { @@ -43181,6 +44075,12 @@ impl serde::Serialize for SignalResponse { signal_response::Message::DataTrackSubscriberHandles(v) => { struct_ser.serialize_field("dataTrackSubscriberHandles", v)?; } + signal_response::Message::StoreDataBlobResponse(v) => { + struct_ser.serialize_field("storeDataBlobResponse", v)?; + } + signal_response::Message::GetDataBlobResponse(v) => { + struct_ser.serialize_field("getDataBlobResponse", v)?; + } } } struct_ser.end() @@ -43240,6 +44140,10 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { "unpublishDataTrackResponse", "data_track_subscriber_handles", "dataTrackSubscriberHandles", + "store_data_blob_response", + "storeDataBlobResponse", + "get_data_blob_response", + "getDataBlobResponse", ]; #[allow(clippy::enum_variant_names)] @@ -43272,6 +44176,8 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { PublishDataTrackResponse, UnpublishDataTrackResponse, DataTrackSubscriberHandles, + StoreDataBlobResponse, + GetDataBlobResponse, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -43322,6 +44228,8 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { "publishDataTrackResponse" | "publish_data_track_response" => Ok(GeneratedField::PublishDataTrackResponse), "unpublishDataTrackResponse" | "unpublish_data_track_response" => Ok(GeneratedField::UnpublishDataTrackResponse), "dataTrackSubscriberHandles" | "data_track_subscriber_handles" => Ok(GeneratedField::DataTrackSubscriberHandles), + "storeDataBlobResponse" | "store_data_blob_response" => Ok(GeneratedField::StoreDataBlobResponse), + "getDataBlobResponse" | "get_data_blob_response" => Ok(GeneratedField::GetDataBlobResponse), _ => Ok(GeneratedField::__SkipField__), } } @@ -43536,6 +44444,20 @@ impl<'de> serde::Deserialize<'de> for SignalResponse { return Err(serde::de::Error::duplicate_field("dataTrackSubscriberHandles")); } message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::DataTrackSubscriberHandles) +; + } + GeneratedField::StoreDataBlobResponse => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("storeDataBlobResponse")); + } + message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::StoreDataBlobResponse) +; + } + GeneratedField::GetDataBlobResponse => { + if message__.is_some() { + return Err(serde::de::Error::duplicate_field("getDataBlobResponse")); + } + message__ = map_.next_value::<::std::option::Option<_>>()?.map(signal_response::Message::GetDataBlobResponse) ; } GeneratedField::__SkipField__ => { @@ -45100,6 +46022,236 @@ impl<'de> serde::Deserialize<'de> for StorageConfig { deserializer.deserialize_struct("livekit.StorageConfig", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for StoreDataBlobRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.request_id != 0 { + len += 1; + } + if self.blob.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.StoreDataBlobRequest", len)?; + if self.request_id != 0 { + struct_ser.serialize_field("requestId", &self.request_id)?; + } + if let Some(v) = self.blob.as_ref() { + struct_ser.serialize_field("blob", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for StoreDataBlobRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "request_id", + "requestId", + "blob", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + RequestId, + Blob, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "requestId" | "request_id" => Ok(GeneratedField::RequestId), + "blob" => Ok(GeneratedField::Blob), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = StoreDataBlobRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.StoreDataBlobRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut request_id__ = None; + let mut blob__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::RequestId => { + if request_id__.is_some() { + return Err(serde::de::Error::duplicate_field("requestId")); + } + request_id__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Blob => { + if blob__.is_some() { + return Err(serde::de::Error::duplicate_field("blob")); + } + blob__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(StoreDataBlobRequest { + request_id: request_id__.unwrap_or_default(), + blob: blob__, + }) + } + } + deserializer.deserialize_struct("livekit.StoreDataBlobRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for StoreDataBlobResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.request_id != 0 { + len += 1; + } + if self.key.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("livekit.StoreDataBlobResponse", len)?; + if self.request_id != 0 { + struct_ser.serialize_field("requestId", &self.request_id)?; + } + if let Some(v) = self.key.as_ref() { + struct_ser.serialize_field("key", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for StoreDataBlobResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "request_id", + "requestId", + "key", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + RequestId, + Key, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "requestId" | "request_id" => Ok(GeneratedField::RequestId), + "key" => Ok(GeneratedField::Key), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = StoreDataBlobResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct livekit.StoreDataBlobResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut request_id__ = None; + let mut key__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::RequestId => { + if request_id__.is_some() { + return Err(serde::de::Error::duplicate_field("requestId")); + } + request_id__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Key => { + if key__.is_some() { + return Err(serde::de::Error::duplicate_field("key")); + } + key__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(StoreDataBlobResponse { + request_id: request_id__.unwrap_or_default(), + key: key__, + }) + } + } + deserializer.deserialize_struct("livekit.StoreDataBlobResponse", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for StreamInfo { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index e58d172df..9dd32aa0f 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -31,15 +31,16 @@ use crate::{ ByteStreamInfo, ByteStreamWriter, StreamByteOptions, StreamResult, StreamTextOptions, TextStreamInfo, TextStreamWriter, }, - data_track::{self, DataTrack, DataTrackOptions, Local}, + data_track::{self, DataTrack, DataTrackOptions, DataTrackSchemaId, Local}, e2ee::EncryptionType, options::{self, compute_video_encodings, video_layers_from_encodings, TrackPublishOptions}, prelude::*, room::rpc::{RpcError, RpcErrorCode, RpcInvocationData}, rtc_engine::lk_runtime::LkRuntime, - rtc_engine::{EngineError, RtcEngine}, + rtc_engine::{EngineError, EngineResult, RtcEngine}, ChatMessage, DataPacket, RoomSession, SipDTMF, Transcription, }; +use bytes::Bytes; use chrono::Utc; use libwebrtc::{ native::{create_random_uuid, packet_trailer}, @@ -917,6 +918,144 @@ impl LocalParticipant { pub fn update_data_encryption_status(&self, _is_encrypted: bool) { // Local participants don't receive data messages, so this is a no-op } + + /// Stores 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 [`get_schema`](Self::get_schema). Define a + /// schema before publishing any data track that references it, so that + /// subscribers can resolve the schema by its ID. + /// + /// A schema can only be defined once. Attempting to redefine an existing + /// schema returns an error. + /// + /// # Arguments + /// + /// * `id` - Identifies the schema; the same ID is provided when publishing a + /// data track that uses it. + /// * `definition` - The schema definition, stored as-is. It is neither parsed + /// nor validated against its [encoding](DataTrackSchemaId::encoding), so + /// the caller is responsible for ensuring it is well-formed. + /// + pub async fn define_schema(&self, id: DataTrackSchemaId, definition: String) -> RoomResult<()> { + self.store_data_blob(id.into(), definition.into()).await?; + Ok(()) + } + + /// Retrieves the definition for a data track schema. + /// + /// Called by a subscriber that wants to inspect the schema a participant + /// [defined](Self::define_schema) for a data track it is publishing. Returns + /// an error if the participant has not defined a schema with this ID. + /// + /// # Arguments + /// + /// * `id` - Identifies the schema to retrieve. + /// * `participant` - Identity of the participant that defined the schema. + /// + pub async fn get_schema( + &self, + id: DataTrackSchemaId, + participant: ParticipantIdentity, + ) -> RoomResult { + let contents = self + .get_data_blob(id.into(), participant) + .await + .map_err(|err| RoomError::Internal(format!("failed to fetch schema: {err}")))?; + + let definition = String::from_utf8(contents.to_vec()).map_err(|err| { + RoomError::Internal(format!("schema definition is not valid UTF-8: {err}")) + })?; + Ok(definition) + } + + // TODO: unify request/response logic, timeout behavior across SDK. + const DATA_BLOB_REQUEST_TIMEOUT: Duration = Duration::from_secs(5); + + /// Stores an arbitrary blob of data on the server, keyed by `key`. + async fn store_data_blob(&self, key: proto::DataBlobKey, contents: Bytes) -> EngineResult<()> { + let blob = proto::DataBlob { key: Some(key), contents: contents.into() }; + + let session = self.inner.rtc_engine.session(); + let request_id = session.signal_client().next_request_id(); + + // Success is reported via `StoreDataBlobResponse` and error via `RequestResponse`; + // both carry the request id, so both paths are correlated by it. + let store_ok_response = session.store_data_blob_response(request_id); + let store_error_response = session.get_response(request_id); + + let request = proto::StoreDataBlobRequest { blob: Some(blob), request_id }; + self.inner + .rtc_engine + .send_request(proto::signal_request::Message::StoreDataBlobRequest(request)) + .await; + + let response = timeout(Self::DATA_BLOB_REQUEST_TIMEOUT, async { + tokio::select! { + _ = store_ok_response => Ok(()), + error = store_error_response => Err(error), + } + }) + .await + .map_err(|_| { + EngineError::Signal(SignalError::Timeout("store data blob timed out".into())) + })?; + + match response { + Ok(()) => Ok(()), + Err(error) => Err(EngineError::Internal( + format!("store data blob request failed ({:?}): {}", error.reason(), error.message) + .into(), + )), + } + } + + /// Retrieves a blob of data previously stored by `participant` under `key`. + async fn get_data_blob( + &self, + key: proto::DataBlobKey, + participant: ParticipantIdentity, + ) -> EngineResult { + let session = self.inner.rtc_engine.session(); + let request_id = session.signal_client().next_request_id(); + + // Success is reported via `GetDataBlobResponse` and error via `RequestResponse`; + // both carry the request id, so both paths are correlated by it. + let get_ok_response = session.get_data_blob_response(request_id); + let get_error_response = session.get_response(request_id); + + let request = proto::GetDataBlobRequest { + key: Some(key), + participant_identity: participant.0, + request_id, + }; + self.inner + .rtc_engine + .send_request(proto::signal_request::Message::GetDataBlobRequest(request)) + .await; + + let response = timeout(Self::DATA_BLOB_REQUEST_TIMEOUT, async { + tokio::select! { + response = get_ok_response => Ok(response), + error = get_error_response => Err(error), + } + }) + .await + .map_err(|_| EngineError::Signal(SignalError::Timeout("get data blob timed out".into())))?; + + match response { + Ok(response) => { + let blob = response.blob.ok_or_else(|| { + EngineError::Internal("get data blob response is malformed".into()) + })?; + Ok(blob.contents.into()) + } + Err(error) => Err(EngineError::Internal( + format!("get data blob request failed ({:?}): {}", error.reason(), error.message) + .into(), + )), + } + } } #[cfg(test)] diff --git a/livekit/src/rtc_engine/rtc_session.rs b/livekit/src/rtc_engine/rtc_session.rs index eb8d12a8f..efc2bdcd6 100644 --- a/livekit/src/rtc_engine/rtc_session.rs +++ b/livekit/src/rtc_engine/rtc_session.rs @@ -395,6 +395,11 @@ struct SessionInner { pending_requests: Mutex>>, + pending_store_data_blob_requests: + Mutex>>, + pending_get_data_blob_requests: + Mutex>>, + e2ee_manager: Option, subscriber_primary: bool, pc_state_notify: Notify, @@ -607,6 +612,8 @@ impl RtcSession { negotiation_debouncer: Default::default(), negotiation_queue: NegotiationQueue::new(), pending_requests: Default::default(), + pending_get_data_blob_requests: Default::default(), + pending_store_data_blob_requests: Default::default(), e2ee_manager, subscriber_primary, pc_state_notify: Notify::new(), @@ -909,6 +916,16 @@ impl RtcSession { pub async fn get_response(&self, request_id: u32) -> proto::RequestResponse { self.inner.get_response(request_id).await } + + /// Awaits the successful [`GetDataBlobResponse`][proto::GetDataBlobResponse] for `request_id`. + pub async fn get_data_blob_response(&self, request_id: u32) -> proto::GetDataBlobResponse { + self.inner.get_data_blob_response(request_id).await + } + + /// Awaits the successful [`StoreDataBlobResponse`][proto::StoreDataBlobResponse] for `request_id`. + pub async fn store_data_blob_response(&self, request_id: u32) -> proto::StoreDataBlobResponse { + self.inner.store_data_blob_response(request_id).await + } } impl SessionInner { @@ -1332,6 +1349,20 @@ impl SessionInner { self.handle_media_sections_requirement(req)?; } } + proto::signal_response::Message::GetDataBlobResponse(response) => { + if let Some(tx) = + self.pending_get_data_blob_requests.lock().remove(&response.request_id) + { + let _ = tx.send(response); + } + } + proto::signal_response::Message::StoreDataBlobResponse(response) => { + if let Some(tx) = + self.pending_store_data_blob_requests.lock().remove(&response.request_id) + { + let _ = tx.send(response); + } + } _ => {} } @@ -2246,8 +2277,45 @@ impl SessionInner { async fn get_response(&self, request_id: u32) -> proto::RequestResponse { let (tx, rx) = oneshot::channel(); self.pending_requests.lock().insert(request_id, tx); + let _guard = PendingResponseGuard::new(&self.pending_requests, request_id); rx.await.unwrap() } + + async fn get_data_blob_response(&self, request_id: u32) -> proto::GetDataBlobResponse { + let (tx, rx) = oneshot::channel(); + self.pending_get_data_blob_requests.lock().insert(request_id, tx); + let _guard = PendingResponseGuard::new(&self.pending_get_data_blob_requests, request_id); + rx.await.expect("data blob response sender dropped") + } + + async fn store_data_blob_response(&self, request_id: u32) -> proto::StoreDataBlobResponse { + let (tx, rx) = oneshot::channel(); + self.pending_store_data_blob_requests.lock().insert(request_id, tx); + let _guard = PendingResponseGuard::new(&self.pending_store_data_blob_requests, request_id); + rx.await.expect("store data blob response sender dropped") + } +} + +/// Removes a pending response registration when dropped. +/// +/// Installed alongside a registration so that abandoning the wait (e.g. a timeout or a +/// losing [`tokio::select!`] branch) cannot leave a stale entry behind. Dropping after the +/// response has already been delivered is a no-op since the entry is removed on delivery. +struct PendingResponseGuard<'a, T> { + map: &'a Mutex>>, + request_id: u32, +} + +impl<'a, T> PendingResponseGuard<'a, T> { + fn new(map: &'a Mutex>>, request_id: u32) -> Self { + Self { map, request_id } + } +} + +impl Drop for PendingResponseGuard<'_, T> { + fn drop(&mut self) { + self.map.lock().remove(&self.request_id); + } } /// Emit incoming data track packets as session events. diff --git a/livekit/tests/data_track_test.rs b/livekit/tests/data_track_test.rs index 6b7930deb..a23b152ea 100644 --- a/livekit/tests/data_track_test.rs +++ b/livekit/tests/data_track_test.rs @@ -160,6 +160,35 @@ async fn test_publish_duplicate_name() -> Result<()> { Ok(()) } +#[cfg(feature = "__lk-e2e-test")] +#[test_log::test(tokio::test)] +async fn test_publish_with_schema_and_frame_encoding() -> Result<()> { + use livekit::data_track::{DataTrackFrameEncoding, DataTrackSchemaEncoding, DataTrackSchemaId}; + + let mut rooms = test_rooms(2).await?; + let (pub_room, _) = rooms.pop().unwrap(); + let (_, mut sub_room_event_rx) = rooms.pop().unwrap(); + + let schema_id = DataTrackSchemaId::new("my_schema", DataTrackSchemaEncoding::JsonSchema); + let frame_encoding = DataTrackFrameEncoding::Json; + + let options = DataTrackOptions::new("my_track") + .with_schema(schema_id.clone()) + .with_frame_encoding(frame_encoding); + + let local_track = pub_room.local_participant().publish_data_track(options).await?; + assert_eq!(local_track.info().schema(), Some(&schema_id)); + assert_eq!(local_track.info().frame_encoding(), Some(frame_encoding)); + + // The subscriber should observe the same schema and frame encoding metadata. + let remote_track = + timeout(Duration::from_secs(5), wait_for_remote_track(&mut sub_room_event_rx)).await??; + assert_eq!(remote_track.info().schema(), Some(&schema_id)); + assert_eq!(remote_track.info().frame_encoding(), Some(frame_encoding)); + + Ok(()) +} + #[cfg(feature = "__lk-e2e-test")] #[test_log::test(tokio::test)] async fn test_e2ee() -> Result<()> { diff --git a/livekit/tests/schema_storage_test.rs b/livekit/tests/schema_storage_test.rs new file mode 100644 index 000000000..3629077d2 --- /dev/null +++ b/livekit/tests/schema_storage_test.rs @@ -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. + +#[cfg(feature = "__lk-e2e-test")] +use { + anyhow::{Ok, Result}, + common::test_rooms, + livekit::data_track::{DataTrackSchemaEncoding, DataTrackSchemaId}, +}; + +mod common; + +const MAX_SCHEMA_DEFINITION_SIZE: usize = 60_000; + +#[cfg(feature = "__lk-e2e-test")] +#[test_log::test(tokio::test)] +async fn test_define_schema() -> Result<()> { + let mut rooms = test_rooms(2).await?; + let (pub_room, _) = rooms.pop().unwrap(); + let (sub_room, _) = rooms.pop().unwrap(); + let identity = pub_room.local_participant().identity(); + + let id = DataTrackSchemaId::new("some_schema", DataTrackSchemaEncoding::JsonSchema); + let definition = "a".repeat(MAX_SCHEMA_DEFINITION_SIZE); + + pub_room.local_participant().define_schema(id.clone(), definition.clone()).await?; + + let retrieved = sub_room.local_participant().get_schema(id, identity).await?; + assert_eq!(retrieved, definition); + + Ok(()) +} + +#[cfg(feature = "__lk-e2e-test")] +#[test_log::test(tokio::test)] +async fn test_define_schema_over_limit() -> Result<()> { + let (room, _) = test_rooms(1).await?.pop().unwrap(); + + let id = DataTrackSchemaId::new("some_schema", DataTrackSchemaEncoding::JsonSchema); + let definition = "a".repeat(2 * MAX_SCHEMA_DEFINITION_SIZE); // Deliberately over size limit + + let result = room.local_participant().define_schema(id, definition).await; + assert!(result.is_err()); + + Ok(()) +} + +#[cfg(feature = "__lk-e2e-test")] +#[test_log::test(tokio::test)] +async fn test_define_schema_duplicate() -> Result<()> { + let (room, _) = test_rooms(1).await?.pop().unwrap(); + + let id = DataTrackSchemaId::new("some_schema", DataTrackSchemaEncoding::JsonSchema); + let definition = "a".repeat(MAX_SCHEMA_DEFINITION_SIZE); + + room.local_participant().define_schema(id.clone(), definition.clone()).await?; + + // Define the same schema again + let result = room.local_participant().define_schema(id, definition).await; + assert!(result.is_err()); + + Ok(()) +} + +#[cfg(feature = "__lk-e2e-test")] +#[test_log::test(tokio::test)] +async fn test_get_undefined_schema() -> Result<()> { + let (room, _) = test_rooms(1).await?.pop().unwrap(); + let identity = room.local_participant().identity(); + + let id = DataTrackSchemaId::new("undefined", DataTrackSchemaEncoding::JsonSchema); + let result = room.local_participant().get_schema(id, identity).await; + assert!(result.is_err()); + + Ok(()) +} \ No newline at end of file