1use chrono::{DateTime, Utc};
16use livekit_common::EncryptionType;
17use parking_lot::RwLock;
18use std::{collections::HashMap, sync::Arc};
19
20use crate::{
21 types::{ByteHeader, ContentHeader, Header, OperationType, TextHeader},
22 utils::StreamError,
23};
24
25#[derive(Clone, Debug)]
27pub struct ByteStreamInfo {
28 pub id: String,
30 pub topic: String,
32 pub timestamp: DateTime<Utc>,
34 pub total_length: Option<u64>,
36 pub mime_type: String,
38 pub name: String,
40 pub encryption_type: EncryptionType,
42 #[cfg(feature = "test-utils")]
44 pub is_compressed: bool,
45 #[cfg(feature = "test-utils")]
47 pub is_inline: bool,
48
49 attributes_map: Arc<RwLock<HashMap<String, String>>>,
52}
53
54impl ByteStreamInfo {
55 pub fn attributes(&self) -> HashMap<String, String> {
60 self.attributes_map.read().clone()
61 }
62}
63
64#[derive(Clone, Debug)]
66pub struct TextStreamInfo {
67 pub id: String,
69 pub topic: String,
71 pub timestamp: DateTime<Utc>,
73 pub total_length: Option<u64>,
75 pub mime_type: String,
77 pub operation_type: OperationType,
78 pub version: i32,
79 pub reply_to_stream_id: Option<String>,
80 pub attached_stream_ids: Vec<String>,
81 pub generated: bool,
82 pub encryption_type: EncryptionType,
84 #[cfg(feature = "test-utils")]
86 pub is_compressed: bool,
87 #[cfg(feature = "test-utils")]
89 pub is_inline: bool,
90
91 #[doc(hidden)]
94 pub attributes_map: Arc<RwLock<HashMap<String, String>>>,
95}
96
97impl TextStreamInfo {
98 pub fn attributes(&self) -> HashMap<String, String> {
103 self.attributes_map.read().clone()
104 }
105}
106
107impl TryFrom<Header> for AnyStreamInfo {
110 type Error = StreamError;
111
112 fn try_from(header: Header) -> Result<Self, Self::Error> {
113 Self::try_from_with_encryption(header, EncryptionType::None)
114 }
115}
116
117impl AnyStreamInfo {
118 pub fn try_from_with_encryption(
119 mut header: Header,
120 encryption_type: EncryptionType,
121 ) -> Result<Self, StreamError> {
122 let Some(content_header) = header.content_header.take() else {
123 Err(StreamError::InvalidHeader)?
124 };
125 let info = match content_header {
126 ContentHeader::ByteHeader(byte_header) => Self::Byte(
127 ByteStreamInfo::from_headers_with_encryption(header, byte_header, encryption_type),
128 ),
129 ContentHeader::TextHeader(text_header) => Self::Text(
130 TextStreamInfo::from_headers_with_encryption(header, text_header, encryption_type),
131 ),
132 };
133 Ok(info)
134 }
135}
136
137impl ByteStreamInfo {
138 pub(crate) fn from_headers(header: Header, byte_header: ByteHeader) -> Self {
139 Self::from_headers_with_encryption(header, byte_header, EncryptionType::None)
140 }
141
142 pub(crate) fn from_headers_with_encryption(
143 header: Header,
144 byte_header: ByteHeader,
145 encryption_type: EncryptionType,
146 ) -> Self {
147 Self {
148 id: header.stream_id.to_string(),
149 topic: header.topic,
150 timestamp: DateTime::<Utc>::from_timestamp_millis(header.timestamp)
151 .unwrap_or_else(|| Utc::now()),
152 total_length: header.total_length,
153 attributes_map: Arc::new(RwLock::new(header.attributes)),
154 mime_type: header.mime_type,
155 name: byte_header.name,
156 encryption_type,
157 #[cfg(feature = "test-utils")]
158 is_compressed: header.compression != crate::types::CompressionType::None,
159 #[cfg(feature = "test-utils")]
160 is_inline: header.inline_content.is_some_and(|c| !c.is_empty()),
161 }
162 }
163}
164
165impl TextStreamInfo {
166 pub(crate) fn from_headers(header: Header, text_header: TextHeader) -> Self {
167 Self::from_headers_with_encryption(header, text_header, EncryptionType::None)
168 }
169
170 pub(crate) fn from_headers_with_encryption(
171 header: Header,
172 text_header: TextHeader,
173 encryption_type: EncryptionType,
174 ) -> Self {
175 Self {
176 id: header.stream_id.to_string(),
177 topic: header.topic,
178 timestamp: DateTime::<Utc>::from_timestamp_millis(header.timestamp)
179 .unwrap_or_else(|| Utc::now()),
180 total_length: header.total_length,
181 attributes_map: Arc::new(RwLock::new(header.attributes)),
182 mime_type: header.mime_type,
183 operation_type: text_header.operation_type,
184 version: text_header.version,
185 reply_to_stream_id: text_header.reply_to_stream_id.map(|stream_id| stream_id.into()),
186 attached_stream_ids: text_header
187 .attached_stream_ids
188 .into_iter()
189 .map(Into::into)
190 .collect(),
191 generated: text_header.generated,
192 encryption_type,
193 #[cfg(feature = "test-utils")]
194 is_compressed: header.compression != crate::types::CompressionType::None,
195 #[cfg(feature = "test-utils")]
196 is_inline: header.inline_content.is_some_and(|c| !c.is_empty()),
197 }
198 }
199}
200
201#[derive(Clone, Debug)]
204pub(crate) enum AnyStreamInfo {
205 Byte(ByteStreamInfo),
206 Text(TextStreamInfo),
207}
208
209impl AnyStreamInfo {
210 livekit_common::enum_dispatch!(
211 [Byte, Text];
212 pub fn id(self: &Self) -> &str;
213 pub fn total_length(self: &Self) -> Option<u64>;
214 pub fn encryption_type(self: &Self) -> EncryptionType;
215 pub(crate) fn attributes_map(self: &Self) -> Arc<RwLock<HashMap<String, String>>>;
216 );
217}
218
219#[rustfmt::skip]
220macro_rules! stream_info {
221 () => {
222 pub(crate) fn id(&self) -> &str { &self.id }
223 pub(crate) fn total_length(&self) -> Option<u64> { self.total_length }
224 pub(crate) fn encryption_type(&self) -> EncryptionType { self.encryption_type }
225 pub(crate) fn attributes_map(self: &Self) -> Arc<RwLock<HashMap<String, String>>> {
226 self.attributes_map.clone()
227 }
228 };
229}
230
231impl ByteStreamInfo {
232 stream_info!();
233}
234
235impl TextStreamInfo {
236 stream_info!();
237}
238
239impl From<ByteStreamInfo> for AnyStreamInfo {
240 fn from(info: ByteStreamInfo) -> Self {
241 Self::Byte(info)
242 }
243}
244
245impl From<TextStreamInfo> for AnyStreamInfo {
246 fn from(info: TextStreamInfo) -> Self {
247 Self::Text(info)
248 }
249}