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::packet::Handle;
16use from_variants::FromVariants;
17use std::{
18    fmt::Display,
19    marker::PhantomData,
20    sync::{Arc, RwLock},
21};
22use thiserror::Error;
23
24/// Track for communicating application-specific data between participants in room.
25#[derive(Debug, Clone)]
26pub struct DataTrack<L> {
27    pub(crate) info: Arc<DataTrackInfo>,
28    pub(crate) inner: Arc<DataTrackInner>,
29    /// Marker indicating local or remote.
30    pub(crate) _location: PhantomData<L>,
31}
32
33#[derive(Debug, Clone, FromVariants)]
34pub(crate) enum DataTrackInner {
35    Local(crate::local::LocalTrackInner),
36    Remote(crate::remote::RemoteTrackInner),
37}
38
39impl<L> DataTrack<L> {
40    /// Information about the data track.
41    pub fn info(&self) -> &DataTrackInfo {
42        &self.info
43    }
44
45    /// Whether or not the track is still published.
46    pub fn is_published(&self) -> bool {
47        match self.inner.as_ref() {
48            DataTrackInner::Local(inner) => inner.is_published(),
49            DataTrackInner::Remote(inner) => inner.is_published(),
50        }
51    }
52
53    /// Waits asynchronously until the track is unpublished.
54    ///
55    /// Use this to trigger follow-up work once the track is no longer published.
56    /// If the track is already unpublished, this method returns immediately.
57    ///
58    pub async fn wait_for_unpublish(&self) {
59        match self.inner.as_ref() {
60            DataTrackInner::Local(inner) => inner.wait_for_unpublish().await,
61            DataTrackInner::Remote(inner) => inner.wait_for_unpublish().await,
62        }
63    }
64}
65
66/// Information about a published data track.
67#[derive(Debug, Clone)]
68#[cfg_attr(test, derive(fake::Dummy))]
69pub struct DataTrackInfo {
70    pub(crate) sid: Arc<RwLock<DataTrackSid>>,
71    pub(crate) pub_handle: Handle,
72    pub(crate) name: String,
73    pub(crate) uses_e2ee: bool,
74}
75
76impl DataTrackInfo {
77    /// Unique track identifier assigned by the SFU.
78    ///
79    /// This identifier may change if a reconnect occurs. Use [`Self::name`] if a
80    /// stable identifier is needed.
81    ///
82    pub fn sid(&self) -> DataTrackSid {
83        self.sid.read().unwrap().clone()
84    }
85
86    /// Name of the track assigned by the publisher.
87    pub fn name(&self) -> &str {
88        &self.name
89    }
90
91    /// Whether or not frames sent on the track use end-to-end encryption.
92    pub fn uses_e2ee(&self) -> bool {
93        self.uses_e2ee
94    }
95}
96
97/// SFU-assigned identifier uniquely identifying a data track.
98#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
99pub struct DataTrackSid(String);
100
101#[derive(Debug, Error)]
102#[error("Invalid data track SID")]
103pub struct DataTrackSidError;
104
105impl DataTrackSid {
106    const PREFIX: &str = "DTR_";
107}
108
109impl TryFrom<String> for DataTrackSid {
110    type Error = DataTrackSidError;
111
112    fn try_from(raw_id: String) -> Result<Self, Self::Error> {
113        if raw_id.starts_with(Self::PREFIX) {
114            Ok(Self(raw_id))
115        } else {
116            Err(DataTrackSidError)
117        }
118    }
119}
120
121impl From<DataTrackSid> for String {
122    fn from(id: DataTrackSid) -> Self {
123        id.0
124    }
125}
126
127impl Display for DataTrackSid {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        write!(f, "{}", self.0)
130    }
131}
132
133#[cfg(test)]
134impl fake::Dummy<fake::Faker> for DataTrackSid {
135    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &fake::Faker, rng: &mut R) -> Self {
136        const BASE_57_ALPHABET: &[u8; 57] =
137            b"23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
138        let random_id: String = (0..12)
139            .map(|_| {
140                let idx = rng.random_range(0..BASE_57_ALPHABET.len());
141                BASE_57_ALPHABET[idx] as char
142            })
143            .collect();
144        Self::try_from(format!("{}{}", Self::PREFIX, random_id)).unwrap()
145    }
146}