1use livekit_protocol as proto;
16use std::collections::HashMap;
17
18use super::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE};
19use crate::services::twirp_client::TwirpClient;
20use livekit_token::{get_env_keys, VideoGrants};
21use rand::Rng;
22
23const SVC: &str = "RoomService";
24
25#[derive(Debug, Clone, Default)]
26pub struct CreateRoomOptions {
27 pub empty_timeout: u32,
28 pub departure_timeout: u32,
29 pub max_participants: u32,
30 pub node_id: String,
31 pub metadata: String,
32 pub egress: Option<proto::RoomEgress>, }
34
35#[derive(Debug, Clone, Default)]
36pub struct UpdateParticipantOptions {
37 pub metadata: String,
38 pub attributes: HashMap<String, String>,
39 pub permission: Option<proto::ParticipantPermission>,
40 pub name: String, }
42
43#[derive(Debug, Clone, Default)]
44pub struct RemoveParticipantOptions {
45 pub revoke_token_ts: i64,
47}
48
49#[derive(Debug, Clone, Default)]
50pub struct SendDataOptions {
51 pub kind: proto::data_packet::Kind,
52 #[deprecated(note = "Use destination_identities instead")]
53 pub destination_sids: Vec<String>,
54 pub destination_identities: Vec<String>,
55 pub topic: Option<String>,
56}
57
58#[derive(Debug)]
59pub struct RoomClient {
60 base: ServiceBase,
61 client: TwirpClient,
62}
63
64impl RoomClient {
65 pub fn with_api_key(host: &str, api_key: &str, api_secret: &str) -> Self {
67 Self::build(
68 host,
69 ServiceBase::with_api_key(api_key, api_secret),
70 crate::http_client::Client::new(),
71 )
72 }
73
74 pub fn with_token(host: &str, token: &str) -> Self {
76 Self::build(host, ServiceBase::with_token(token), crate::http_client::Client::new())
77 }
78
79 pub(crate) fn build(host: &str, base: ServiceBase, client: crate::http_client::Client) -> Self {
82 Self { base, client: TwirpClient::with_client(host, LIVEKIT_PACKAGE, None, client) }
83 }
84
85 #[cfg(test)]
86 pub(crate) fn with_default_headers(mut self, headers: http::HeaderMap) -> Self {
87 self.client = self.client.with_default_headers(headers);
88 self
89 }
90
91 pub fn new(host: &str) -> ServiceResult<Self> {
94 let (api_key, api_secret) = get_env_keys()?;
95 Ok(Self::with_api_key(host, &api_key, &api_secret))
96 }
97
98 pub fn with_failover(mut self, enabled: bool) -> Self {
101 self.client = self.client.with_failover(enabled);
102 self
103 }
104
105 pub fn with_request_timeout(mut self, timeout: std::time::Duration) -> Self {
107 self.client = self.client.with_request_timeout(timeout);
108 self
109 }
110
111 pub async fn create_room(
112 &self,
113 name: &str,
114 options: CreateRoomOptions,
115 ) -> ServiceResult<proto::Room> {
116 self.create_room_request(proto::CreateRoomRequest {
117 name: name.to_owned(),
118 empty_timeout: options.empty_timeout,
119 departure_timeout: options.departure_timeout,
120 max_participants: options.max_participants,
121 node_id: options.node_id,
122 metadata: options.metadata,
123 egress: options.egress,
124 ..Default::default()
125 })
126 .await
127 }
128
129 pub async fn create_room_with_playout_delay(
131 &self,
132 name: &str,
133 options: CreateRoomOptions,
134 min_playout_delay: u32,
135 max_playout_delay: u32,
136 ) -> ServiceResult<proto::Room> {
137 self.create_room_request(proto::CreateRoomRequest {
138 name: name.to_owned(),
139 empty_timeout: options.empty_timeout,
140 departure_timeout: options.departure_timeout,
141 max_participants: options.max_participants,
142 node_id: options.node_id,
143 metadata: options.metadata,
144 egress: options.egress,
145 min_playout_delay,
146 max_playout_delay,
147 ..Default::default()
148 })
149 .await
150 }
151
152 async fn create_room_request(
153 &self,
154 request: proto::CreateRoomRequest,
155 ) -> ServiceResult<proto::Room> {
156 self.client
157 .request(
158 SVC,
159 "CreateRoom",
160 request,
161 self.base
162 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
163 )
164 .await
165 .map_err(Into::into)
166 }
167
168 pub async fn list_rooms(&self, names: Vec<String>) -> ServiceResult<Vec<proto::Room>> {
169 let resp: proto::ListRoomsResponse = self
170 .client
171 .request(
172 SVC,
173 "ListRooms",
174 proto::ListRoomsRequest { names },
175 self.base
176 .auth_header(VideoGrants { room_list: true, ..Default::default() }, None)?,
177 )
178 .await?;
179
180 Ok(resp.rooms)
181 }
182
183 pub async fn delete_room(&self, room: &str) -> ServiceResult<()> {
184 self.client
185 .request(
186 SVC,
187 "DeleteRoom",
188 proto::DeleteRoomRequest { room: room.to_owned() },
189 self.base
190 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
191 )
192 .await
193 .map_err(Into::into)
194 }
195
196 pub async fn update_room_metadata(
197 &self,
198 room: &str,
199 metadata: &str,
200 ) -> ServiceResult<proto::Room> {
201 self.client
202 .request(
203 SVC,
204 "UpdateRoomMetadata",
205 proto::UpdateRoomMetadataRequest {
206 room: room.to_owned(),
207 metadata: metadata.to_owned(),
208 },
209 self.base.auth_header(
210 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
211 None,
212 )?,
213 )
214 .await
215 .map_err(Into::into)
216 }
217
218 pub async fn list_participants(
219 &self,
220 room: &str,
221 ) -> ServiceResult<Vec<proto::ParticipantInfo>> {
222 let resp: proto::ListParticipantsResponse = self
223 .client
224 .request(
225 SVC,
226 "ListParticipants",
227 proto::ListParticipantsRequest { room: room.to_owned() },
228 self.base.auth_header(
229 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
230 None,
231 )?,
232 )
233 .await?;
234
235 Ok(resp.participants)
236 }
237
238 pub async fn get_participant(
239 &self,
240 room: &str,
241 identity: &str,
242 ) -> ServiceResult<proto::ParticipantInfo> {
243 self.client
244 .request(
245 SVC,
246 "GetParticipant",
247 proto::RoomParticipantIdentity {
248 room: room.to_owned(),
249 identity: identity.to_owned(),
250 ..Default::default()
251 },
252 self.base.auth_header(
253 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
254 None,
255 )?,
256 )
257 .await
258 .map_err(Into::into)
259 }
260
261 pub async fn remove_participant(&self, room: &str, identity: &str) -> ServiceResult<()> {
262 self.remove_participant_with_options(room, identity, RemoveParticipantOptions::default())
263 .await
264 }
265
266 pub async fn remove_participant_with_options(
267 &self,
268 room: &str,
269 identity: &str,
270 options: RemoveParticipantOptions,
271 ) -> ServiceResult<()> {
272 self.client
273 .request(
274 SVC,
275 "RemoveParticipant",
276 proto::RoomParticipantIdentity {
277 room: room.to_owned(),
278 identity: identity.to_owned(),
279 revoke_token_ts: options.revoke_token_ts,
280 },
281 self.base.auth_header(
282 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
283 None,
284 )?,
285 )
286 .await
287 .map_err(Into::into)
288 }
289
290 pub async fn forward_participant(
291 &self,
292 room: &str,
293 identity: &str,
294 destination_room: &str,
295 ) -> ServiceResult<()> {
296 self.client
297 .request(
298 SVC,
299 "ForwardParticipant",
300 proto::ForwardParticipantRequest {
301 room: room.to_owned(),
302 identity: identity.to_owned(),
303 destination_room: destination_room.to_owned(),
304 },
305 self.base.auth_header(
306 VideoGrants {
307 room_admin: true,
308 room: room.to_owned(),
309 destination_room: destination_room.to_owned(),
310 ..Default::default()
311 },
312 None,
313 )?,
314 )
315 .await
316 .map_err(Into::into)
317 }
318
319 pub async fn move_participant(
320 &self,
321 room: &str,
322 identity: &str,
323 destination_room: &str,
324 ) -> ServiceResult<()> {
325 self.client
326 .request(
327 SVC,
328 "MoveParticipant",
329 proto::MoveParticipantRequest {
330 room: room.to_owned(),
331 identity: identity.to_owned(),
332 destination_room: destination_room.to_owned(),
333 },
334 self.base.auth_header(
335 VideoGrants {
336 room_admin: true,
337 room: room.to_owned(),
338 destination_room: destination_room.to_owned(),
339 ..Default::default()
340 },
341 None,
342 )?,
343 )
344 .await
345 .map_err(Into::into)
346 }
347
348 pub async fn mute_published_track(
349 &self,
350 room: &str,
351 identity: &str,
352 track_sid: &str,
353 muted: bool,
354 ) -> ServiceResult<proto::TrackInfo> {
355 let resp: proto::MuteRoomTrackResponse = self
356 .client
357 .request(
358 SVC,
359 "MutePublishedTrack",
360 proto::MuteRoomTrackRequest {
361 room: room.to_owned(),
362 identity: identity.to_owned(),
363 track_sid: track_sid.to_owned(),
364 muted,
365 },
366 self.base.auth_header(
367 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
368 None,
369 )?,
370 )
371 .await?;
372
373 Ok(resp.track.unwrap())
374 }
375
376 pub async fn update_participant(
377 &self,
378 room: &str,
379 identity: &str,
380 options: UpdateParticipantOptions,
381 ) -> ServiceResult<proto::ParticipantInfo> {
382 self.client
383 .request(
384 SVC,
385 "UpdateParticipant",
386 proto::UpdateParticipantRequest {
387 room: room.to_owned(),
388 identity: identity.to_owned(),
389 permission: options.permission,
390 metadata: options.metadata,
391 attributes: options.attributes.to_owned(),
392 name: options.name,
393 },
394 self.base.auth_header(
395 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
396 None,
397 )?,
398 )
399 .await
400 .map_err(Into::into)
401 }
402
403 pub async fn update_subscriptions(
404 &self,
405 room: &str,
406 identity: &str,
407 track_sids: Vec<String>,
408 subscribe: bool,
409 ) -> ServiceResult<()> {
410 self.client
411 .request(
412 SVC,
413 "UpdateSubscriptions",
414 proto::UpdateSubscriptionsRequest {
415 room: room.to_owned(),
416 identity: identity.to_owned(),
417 track_sids,
418 subscribe,
419 ..Default::default()
420 },
421 self.base.auth_header(
422 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
423 None,
424 )?,
425 )
426 .await
427 .map_err(Into::into)
428 }
429
430 pub async fn send_data(
431 &self,
432 room: &str,
433 data: Vec<u8>,
434 options: SendDataOptions,
435 ) -> ServiceResult<()> {
436 let mut rng = rand::rng();
437 let nonce: Vec<u8> = (0..16).map(|_| rng.random::<u8>()).collect();
438 #[allow(deprecated)]
439 self.client
440 .request(
441 SVC,
442 "SendData",
443 proto::SendDataRequest {
444 room: room.to_owned(),
445 data,
446 destination_sids: options.destination_sids,
447 topic: options.topic,
448 kind: options.kind as i32,
449 destination_identities: options.destination_identities,
450 nonce,
451 },
452 self.base.auth_header(
453 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
454 None,
455 )?,
456 )
457 .await
458 .map_err(Into::into)
459 }
460}