Skip to main content

livekit_datatrack/remote/
mod.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::api::{DataTrack, DataTrackFrame, DataTrackInfo, DataTrackInner, InternalError};
16use events::{InputEvent, SetPipelineOptions, SubscribeRequest};
17use livekit_runtime::timeout;
18use std::{
19    marker::PhantomData,
20    pin::Pin,
21    sync::Arc,
22    task::{Context, Poll},
23    time::Duration,
24};
25use thiserror::Error;
26use tokio::sync::{mpsc, oneshot, watch};
27use tokio_stream::{wrappers::BroadcastStream, Stream};
28
29pub(crate) mod events;
30pub(crate) mod manager;
31pub(crate) mod proto;
32
33mod depacketizer;
34mod pipeline;
35
36/// Data track published by a remote participant.
37pub type RemoteDataTrack = DataTrack<Remote>;
38
39/// Marker type indicating a [`DataTrack`] belongs to a remote participant.
40///
41/// See also: [`RemoteDataTrack`]
42///
43#[derive(Debug, Clone)]
44pub struct Remote;
45
46impl DataTrack<Remote> {
47    pub(crate) fn new(info: Arc<DataTrackInfo>, inner: RemoteTrackInner) -> Self {
48        Self { info, inner: Arc::new(inner.into()), _location: PhantomData }
49    }
50
51    fn inner(&self) -> &RemoteTrackInner {
52        match &*self.inner {
53            DataTrackInner::Remote(inner) => inner,
54            DataTrackInner::Local(_) => unreachable!(), // Safe (type state)
55        }
56    }
57}
58
59impl DataTrack<Remote> {
60    /// Subscribes to the data track.
61    ///
62    /// # Returns
63    ///
64    /// A stream that yields [`DataTrackFrame`]s as they arrive.
65    ///
66    /// # Options
67    ///
68    /// To set custom subscription options, see [`Self::subscribe_with_options`].
69    ///
70    /// # Multiple Subscriptions
71    ///
72    /// An application may call `subscribe` more than once to process frames in
73    /// multiple places. For example, one async task might plot values on a graph
74    /// while another writes them to a file.
75    ///
76    /// Internally, only the first call to `subscribe` communicates with the SFU and
77    /// allocates the resources required to receive frames. Additional subscriptions
78    /// reuse the same underlying pipeline and do not trigger additional signaling.
79    ///
80    /// Note that newly created subscriptions only receive frames published after
81    /// the initial subscription is established.
82    ///
83    pub async fn subscribe(&self) -> Result<DataTrackStream, DataTrackSubscribeError> {
84        self.subscribe_with_options(DataTrackSubscribeOptions::default()).await
85    }
86
87    /// Subscribes to the data track, specifying custom options.
88    ///
89    /// Same usage and return as [`Self::subscribe`] with an additional argument
90    /// to specify options.
91    ///
92    pub async fn subscribe_with_options(
93        &self,
94        options: DataTrackSubscribeOptions,
95    ) -> Result<DataTrackStream, DataTrackSubscribeError> {
96        let (result_tx, result_rx) = oneshot::channel();
97        let subscribe_event = SubscribeRequest { sid: self.info.sid(), options, result_tx };
98        self.inner()
99            .event_in_tx
100            .upgrade()
101            .ok_or(DataTrackSubscribeError::Disconnected)?
102            .send(subscribe_event.into())
103            .await
104            .map_err(|_| DataTrackSubscribeError::Disconnected)?;
105
106        // TODO: standardize timeout
107        let frame_rx = timeout(Duration::from_secs(10), result_rx)
108            .await
109            .map_err(|_| DataTrackSubscribeError::Timeout)?
110            .map_err(|_| DataTrackSubscribeError::Disconnected)??;
111
112        Ok(DataTrackStream { inner: BroadcastStream::new(frame_rx) })
113    }
114
115    /// Identity of the participant who published the track.
116    pub fn publisher_identity(&self) -> &str {
117        &self.inner().publisher_identity
118    }
119
120    /// Configures options for the pipeline handling incoming packets for this track.
121    ///
122    /// These options apply to all current and future subscriptions of this track, and may be
123    /// set at any time. New options take affect with the next received packet.
124    ///
125    pub fn set_pipeline_options(&self, options: RemoteDataTrackPipelineOptions) {
126        let Some(event_in_tx) = self.inner().event_in_tx.upgrade() else {
127            return;
128        };
129        let event = SetPipelineOptions { sid: self.info.sid(), options };
130        _ = event_in_tx.try_send(event.into());
131    }
132}
133
134/// A stream of [`DataTrackFrame`]s received from a [`RemoteDataTrack`].
135pub struct DataTrackStream {
136    inner: BroadcastStream<DataTrackFrame>,
137}
138
139impl Stream for DataTrackStream {
140    type Item = DataTrackFrame;
141
142    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
143        let this = self.get_mut();
144        loop {
145            match Pin::new(&mut this.inner).poll_next(cx) {
146                Poll::Ready(Some(Ok(frame))) => return Poll::Ready(Some(frame)),
147                Poll::Ready(Some(Err(_))) => continue,
148                Poll::Ready(None) => return Poll::Ready(None),
149                Poll::Pending => return Poll::Pending,
150            }
151        }
152    }
153}
154
155#[derive(Debug, Clone)]
156pub(crate) struct RemoteTrackInner {
157    publisher_identity: Arc<str>,
158    published_rx: watch::Receiver<bool>,
159    event_in_tx: mpsc::WeakSender<InputEvent>,
160}
161
162impl RemoteTrackInner {
163    pub(crate) fn is_published(&self) -> bool {
164        *self.published_rx.borrow()
165    }
166
167    pub(crate) async fn wait_for_unpublish(&self) {
168        let mut published_rx = self.published_rx.clone();
169        _ = published_rx.wait_for(|is_published| !*is_published).await
170    }
171}
172
173#[derive(Debug, Error)]
174#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
175#[cfg_attr(feature = "uniffi", uniffi(flat_error))]
176pub enum DataTrackSubscribeError {
177    #[error("The track has been unpublished and is no longer available")]
178    Unpublished,
179    #[error("Request to subscribe to data track timed-out")]
180    Timeout,
181    #[error("Cannot subscribe to data track when disconnected")]
182    Disconnected,
183    #[error(transparent)]
184    Internal(#[from] InternalError),
185}
186
187/// Options for subscribing to a data track.
188///
189/// # Examples
190///
191/// Specify a custom buffer size:
192///
193/// ```
194/// # use livekit_datatrack::api::DataTrackSubscribeOptions;
195/// let options = DataTrackSubscribeOptions::default()
196///     .with_buffer_size(128); // Buffer 128 frames internally
197///
198/// # assert_eq!(options.buffer_size(), 128);
199/// ```
200///
201#[derive(Debug, Clone)]
202pub struct DataTrackSubscribeOptions {
203    buffer_size: usize,
204}
205
206impl DataTrackSubscribeOptions {
207    /// Creates subscribe options with default values.
208    ///
209    /// Equivalent to [`Self::default`].
210    ///
211    pub fn new() -> Self {
212        Self { buffer_size: 16 }
213    }
214
215    /// Returns the maximum number of received frames buffered internally.
216    pub fn buffer_size(&self) -> usize {
217        self.buffer_size
218    }
219
220    /// Sets the maximum number of received frames buffered internally.
221    ///
222    /// Zero is not a valid buffer size; if a value of zero is provided, it will be clamped to one.
223    ///
224    /// Note: if there is already an active subscription for a given track, specifying a
225    /// different buffer size when obtaining a new subscription will have no effect.
226    ///
227    pub fn with_buffer_size(mut self, mut frames: usize) -> Self {
228        if frames == 0 {
229            log::warn!("Zero is not a valid buffer size, using one");
230            frames = 1;
231        }
232        self.buffer_size = frames;
233        self
234    }
235}
236
237impl Default for DataTrackSubscribeOptions {
238    fn default() -> Self {
239        Self::new()
240    }
241}
242
243/// Track-level options that configure how the incoming-frame pipeline reassembles packets
244/// for a [`RemoteDataTrack`].
245///
246/// Applied via [`RemoteDataTrack::set_pipeline_options`].
247///
248/// # Examples
249///
250/// Allow up to 10 partial frames to be assembled concurrently:
251///
252/// ```
253/// # use livekit_datatrack::api::RemoteDataTrackPipelineOptions;
254/// let options = RemoteDataTrackPipelineOptions::default()
255///     .with_max_partial_frames(10);
256///
257/// # assert_eq!(options.max_partial_frames(), 10);
258/// ```
259///
260#[derive(Debug, Clone)]
261pub struct RemoteDataTrackPipelineOptions {
262    max_partial_frames: usize,
263}
264
265impl RemoteDataTrackPipelineOptions {
266    /// Creates pipeline options with default values.
267    ///
268    /// Equivalent to [`Self::default`].
269    ///
270    pub fn new() -> Self {
271        Self { max_partial_frames: 1 }
272    }
273
274    /// Maximum number of partial frames the depacketizer will track concurrently for this
275    /// track.
276    ///
277    /// Defaults to `1`. Higher values give more out-of-order tolerance for high-frequency
278    /// senders at the cost of additional buffering.
279    ///
280    pub fn max_partial_frames(&self) -> usize {
281        self.max_partial_frames
282    }
283
284    /// Sets the maximum number of partial frames the depacketizer will track concurrently.
285    ///
286    /// Zero is not a valid value; if a value of zero is provided, it will be clamped to one.
287    ///
288    pub fn with_max_partial_frames(mut self, mut frames: usize) -> Self {
289        if frames == 0 {
290            log::warn!("Zero is not a valid value for max_partial_frames, using one");
291            frames = 1;
292        }
293        self.max_partial_frames = frames;
294        self
295    }
296}
297
298impl Default for RemoteDataTrackPipelineOptions {
299    fn default() -> Self {
300        Self::new()
301    }
302}