Skip to main content

livekit_datatrack/
frame.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 bytes::Bytes;
16use core::fmt;
17use std::time::{Duration, SystemTime, UNIX_EPOCH};
18
19/// A frame published on a data track, consisting of a payload and optional metadata.
20///
21/// # Examples
22///
23/// Create a frame from a [`Vec`] payload:
24///
25/// ```
26/// # use livekit_datatrack::api::DataTrackFrame;
27/// let some_payload = vec![0xFA; 256];
28/// let frame: DataTrackFrame = some_payload.into();
29///
30/// assert_eq!(frame.payload().len(), 256);
31/// ```
32///
33#[derive(Clone, Default)]
34pub struct DataTrackFrame {
35    pub(crate) payload: Bytes,
36    pub(crate) user_timestamp: Option<u64>,
37}
38
39impl DataTrackFrame {
40    /// Returns the frame's payload.
41    pub fn payload(&self) -> Bytes {
42        self.payload.clone() // Cheap clone
43    }
44
45    /// Returns the frame's user timestamp, if one is associated.
46    pub fn user_timestamp(&self) -> Option<u64> {
47        self.user_timestamp
48    }
49
50    /// If the frame has a user timestamp, calculate how long has passed
51    /// relative to the current system time.
52    ///
53    /// If a timestamp is present, it is assumed it is a UNIX timestamp in milliseconds
54    /// (as can be set with [`Self::with_user_timestamp_now`] on the publisher side).
55    /// If the timestamp is invalid or not present, the result is none.
56    ///
57    pub fn duration_since_timestamp(&self) -> Option<Duration> {
58        let ts = self.user_timestamp?;
59        let ts_time = UNIX_EPOCH.checked_add(Duration::from_millis(ts))?;
60        SystemTime::now()
61            .duration_since(ts_time)
62            .inspect_err(|err| log::error!("Failed to calculate duration: {err}"))
63            .ok()
64    }
65}
66
67impl DataTrackFrame {
68    /// Creates a frame from the given payload.
69    pub fn new(payload: impl Into<Bytes>) -> Self {
70        Self { payload: payload.into(), ..Default::default() }
71    }
72
73    /// Associates a user timestamp with the frame.
74    pub fn with_user_timestamp(mut self, value: u64) -> Self {
75        self.user_timestamp = Some(value);
76        self
77    }
78
79    /// Associates the current Unix timestamp (in milliseconds) with the frame.
80    pub fn with_user_timestamp_now(mut self) -> Self {
81        let timestamp = SystemTime::now()
82            .duration_since(std::time::UNIX_EPOCH)
83            .map(|d| d.as_millis() as u64)
84            .inspect_err(|err| log::error!("Failed to get system time: {err}"))
85            .ok();
86        self.user_timestamp = timestamp;
87        self
88    }
89}
90
91impl fmt::Debug for DataTrackFrame {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        f.debug_struct("DataTrackFrame")
94            .field("payload_len", &self.payload.len())
95            .field("user_timestamp", &self.user_timestamp)
96            .finish()
97    }
98}
99
100// MARK: - From implementations
101
102impl From<Bytes> for DataTrackFrame {
103    fn from(bytes: Bytes) -> Self {
104        Self { payload: bytes, ..Default::default() }
105    }
106}
107
108impl From<&'static [u8]> for DataTrackFrame {
109    fn from(slice: &'static [u8]) -> Self {
110        Self { payload: slice.into(), ..Default::default() }
111    }
112}
113
114impl From<Vec<u8>> for DataTrackFrame {
115    fn from(vec: Vec<u8>) -> Self {
116        Self { payload: vec.into(), ..Default::default() }
117    }
118}
119
120impl From<Box<[u8]>> for DataTrackFrame {
121    fn from(slice: Box<[u8]>) -> Self {
122        Self { payload: slice.into(), ..Default::default() }
123    }
124}