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