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}