Skip to main content

livekit_datatrack/
track.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{
16    packet::Handle,
17    schema::{DataTrackFrameEncoding, DataTrackSchemaId},
18};
19use from_variants::FromVariants;
20use std::{
21    fmt::Display,
22    marker::PhantomData,
23    sync::{Arc, RwLock},
24};
25use thiserror::Error;
26
27/// Track for communicating application-specific data between participants in room.
28#[derive(Debug, Clone)]
29pub struct DataTrack<L> {
30    pub(crate) info: Arc<DataTrackInfo>,
31    pub(crate) inner: Arc<DataTrackInner>,
32    /// Marker indicating local or remote.
33    pub(crate) _location: PhantomData<L>,
34}
35
36#[derive(Debug, Clone, FromVariants)]
37pub(crate) enum DataTrackInner {
38    Local(crate::local::LocalTrackInner),
39    Remote(crate::remote::RemoteTrackInner),
40}
41
42impl<L> DataTrack<L> {
43    /// Information about the data track.
44    pub fn info(&self) -> &DataTrackInfo {
45        &self.info
46    }
47
48    /// Whether or not the track is currently published.
49    pub fn is_published(&self) -> bool {
50        match self.inner.as_ref() {
51            DataTrackInner::Local(inner) => inner.is_published(),
52            DataTrackInner::Remote(inner) => inner.is_published(),
53        }
54    }
55
56    /// Waits asynchronously until the track is unpublished.
57    ///
58    /// Use this to trigger follow-up work once the track is no longer published.
59    /// If the track is already unpublished, this method returns immediately.
60    ///
61    pub async fn wait_for_unpublish(&self) {
62        match self.inner.as_ref() {
63            DataTrackInner::Local(inner) => inner.wait_for_unpublish().await,
64            DataTrackInner::Remote(inner) => inner.wait_for_unpublish().await,
65        }
66    }
67}
68
69/// Information about a published data track.
70#[derive(Debug, Clone)]
71#[cfg_attr(test, derive(fake::Dummy))]
72pub struct DataTrackInfo {
73    pub(crate) sid: Arc<RwLock<DataTrackSid>>,
74    pub(crate) pub_handle: Handle,
75    pub(crate) name: String,
76    pub(crate) uses_e2ee: bool,
77    pub(crate) schema: Option<DataTrackSchemaId>,
78    pub(crate) frame_encoding: Option<DataTrackFrameEncoding>,
79}
80
81impl DataTrackInfo {
82    /// Unique track identifier assigned by the SFU.
83    ///
84    /// This identifier may change if a reconnect occurs. Use [`Self::name`] if a
85    /// stable identifier is needed.
86    ///
87    pub fn sid(&self) -> DataTrackSid {
88        self.sid.read().unwrap().clone()
89    }
90
91    /// Name of the track assigned by the publisher.
92    pub fn name(&self) -> &str {
93        &self.name
94    }
95
96    /// Whether or not frames sent on the track use end-to-end encryption.
97    pub fn uses_e2ee(&self) -> bool {
98        self.uses_e2ee
99    }
100
101    /// Schema associated with frames sent on the track.
102    ///
103    /// Returns `None` if the publisher did not associate a
104    /// [`DataTrackSchemaId`] with the track.
105    ///
106    pub fn schema(&self) -> Option<&DataTrackSchemaId> {
107        self.schema.as_ref()
108    }
109
110    /// Encoding of frames sent on the track.
111    ///
112    /// Returns `None` if the publisher did not specify a
113    /// [`DataTrackFrameEncoding`] for the track.
114    ///
115    pub fn frame_encoding(&self) -> Option<&DataTrackFrameEncoding> {
116        self.frame_encoding.as_ref()
117    }
118}
119
120/// SFU-assigned identifier uniquely identifying a data track.
121#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
122pub struct DataTrackSid(String);
123
124#[derive(Debug, Error)]
125#[error("Invalid data track SID")]
126pub struct DataTrackSidError;
127
128impl DataTrackSid {
129    const PREFIX: &str = "DTR_";
130}
131
132impl TryFrom<String> for DataTrackSid {
133    type Error = DataTrackSidError;
134
135    fn try_from(raw_id: String) -> Result<Self, Self::Error> {
136        if raw_id.starts_with(Self::PREFIX) {
137            Ok(Self(raw_id))
138        } else {
139            Err(DataTrackSidError)
140        }
141    }
142}
143
144impl From<DataTrackSid> for String {
145    fn from(id: DataTrackSid) -> Self {
146        id.0
147    }
148}
149
150impl Display for DataTrackSid {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        write!(f, "{}", self.0)
153    }
154}
155
156#[cfg(test)]
157impl fake::Dummy<fake::Faker> for DataTrackSid {
158    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &fake::Faker, rng: &mut R) -> Self {
159        const BASE_57_ALPHABET: &[u8; 57] =
160            b"23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
161        let random_id: String = (0..12)
162            .map(|_| {
163                let idx = rng.random_range(0..BASE_57_ALPHABET.len());
164                BASE_57_ALPHABET[idx] as char
165            })
166            .collect();
167        Self::try_from(format!("{}{}", Self::PREFIX, random_id)).unwrap()
168    }
169}