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 {
65 Self {
66 base: ServiceBase::with_api_key(api_key, api_secret),
67 client: TwirpClient::new(host, LIVEKIT_PACKAGE, None),
68 }
69 }
70
71 pub fn new(host: &str) -> ServiceResult<Self> {
72 let (api_key, api_secret) = get_env_keys()?;
73 Ok(Self::with_api_key(host, &api_key, &api_secret))
74 }
75
76 pub async fn create_room(
77 &self,
78 name: &str,
79 options: CreateRoomOptions,
80 ) -> ServiceResult<proto::Room> {
81 self.create_room_request(proto::CreateRoomRequest {
82 name: name.to_owned(),
83 empty_timeout: options.empty_timeout,
84 departure_timeout: options.departure_timeout,
85 max_participants: options.max_participants,
86 node_id: options.node_id,
87 metadata: options.metadata,
88 egress: options.egress,
89 ..Default::default()
90 })
91 .await
92 }
93
94 pub async fn create_room_with_playout_delay(
96 &self,
97 name: &str,
98 options: CreateRoomOptions,
99 min_playout_delay: u32,
100 max_playout_delay: u32,
101 ) -> ServiceResult<proto::Room> {
102 self.create_room_request(proto::CreateRoomRequest {
103 name: name.to_owned(),
104 empty_timeout: options.empty_timeout,
105 departure_timeout: options.departure_timeout,
106 max_participants: options.max_participants,
107 node_id: options.node_id,
108 metadata: options.metadata,
109 egress: options.egress,
110 min_playout_delay,
111 max_playout_delay,
112 ..Default::default()
113 })
114 .await
115 }
116
117 async fn create_room_request(
118 &self,
119 request: proto::CreateRoomRequest,
120 ) -> ServiceResult<proto::Room> {
121 self.client
122 .request(
123 SVC,
124 "CreateRoom",
125 request,
126 self.base
127 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
128 )
129 .await
130 .map_err(Into::into)
131 }
132
133 pub async fn list_rooms(&self, names: Vec<String>) -> ServiceResult<Vec<proto::Room>> {
134 let resp: proto::ListRoomsResponse = self
135 .client
136 .request(
137 SVC,
138 "ListRooms",
139 proto::ListRoomsRequest { names },
140 self.base
141 .auth_header(VideoGrants { room_list: true, ..Default::default() }, None)?,
142 )
143 .await?;
144
145 Ok(resp.rooms)
146 }
147
148 pub async fn delete_room(&self, room: &str) -> ServiceResult<()> {
149 self.client
150 .request(
151 SVC,
152 "DeleteRoom",
153 proto::DeleteRoomRequest { room: room.to_owned() },
154 self.base
155 .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
156 )
157 .await
158 .map_err(Into::into)
159 }
160
161 pub async fn update_room_metadata(
162 &self,
163 room: &str,
164 metadata: &str,
165 ) -> ServiceResult<proto::Room> {
166 self.client
167 .request(
168 SVC,
169 "UpdateRoomMetadata",
170 proto::UpdateRoomMetadataRequest {
171 room: room.to_owned(),
172 metadata: metadata.to_owned(),
173 },
174 self.base.auth_header(
175 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
176 None,
177 )?,
178 )
179 .await
180 .map_err(Into::into)
181 }
182
183 pub async fn list_participants(
184 &self,
185 room: &str,
186 ) -> ServiceResult<Vec<proto::ParticipantInfo>> {
187 let resp: proto::ListParticipantsResponse = self
188 .client
189 .request(
190 SVC,
191 "ListParticipants",
192 proto::ListParticipantsRequest { room: room.to_owned() },
193 self.base.auth_header(
194 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
195 None,
196 )?,
197 )
198 .await?;
199
200 Ok(resp.participants)
201 }
202
203 pub async fn get_participant(
204 &self,
205 room: &str,
206 identity: &str,
207 ) -> ServiceResult<proto::ParticipantInfo> {
208 self.client
209 .request(
210 SVC,
211 "GetParticipant",
212 proto::RoomParticipantIdentity {
213 room: room.to_owned(),
214 identity: identity.to_owned(),
215 ..Default::default()
216 },
217 self.base.auth_header(
218 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
219 None,
220 )?,
221 )
222 .await
223 .map_err(Into::into)
224 }
225
226 pub async fn remove_participant(&self, room: &str, identity: &str) -> ServiceResult<()> {
227 self.remove_participant_with_options(room, identity, RemoveParticipantOptions::default())
228 .await
229 }
230
231 pub async fn remove_participant_with_options(
232 &self,
233 room: &str,
234 identity: &str,
235 options: RemoveParticipantOptions,
236 ) -> ServiceResult<()> {
237 self.client
238 .request(
239 SVC,
240 "RemoveParticipant",
241 proto::RoomParticipantIdentity {
242 room: room.to_owned(),
243 identity: identity.to_owned(),
244 revoke_token_ts: options.revoke_token_ts,
245 },
246 self.base.auth_header(
247 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
248 None,
249 )?,
250 )
251 .await
252 .map_err(Into::into)
253 }
254
255 pub async fn forward_participant(
256 &self,
257 room: &str,
258 identity: &str,
259 destination_room: &str,
260 ) -> ServiceResult<()> {
261 self.client
262 .request(
263 SVC,
264 "ForwardParticipant",
265 proto::ForwardParticipantRequest {
266 room: room.to_owned(),
267 identity: identity.to_owned(),
268 destination_room: destination_room.to_owned(),
269 },
270 self.base.auth_header(
271 VideoGrants {
272 room_admin: true,
273 room: room.to_owned(),
274 destination_room: destination_room.to_owned(),
275 ..Default::default()
276 },
277 None,
278 )?,
279 )
280 .await
281 .map_err(Into::into)
282 }
283
284 pub async fn move_participant(
285 &self,
286 room: &str,
287 identity: &str,
288 destination_room: &str,
289 ) -> ServiceResult<()> {
290 self.client
291 .request(
292 SVC,
293 "MoveParticipant",
294 proto::MoveParticipantRequest {
295 room: room.to_owned(),
296 identity: identity.to_owned(),
297 destination_room: destination_room.to_owned(),
298 },
299 self.base.auth_header(
300 VideoGrants {
301 room_admin: true,
302 room: room.to_owned(),
303 destination_room: destination_room.to_owned(),
304 ..Default::default()
305 },
306 None,
307 )?,
308 )
309 .await
310 .map_err(Into::into)
311 }
312
313 pub async fn mute_published_track(
314 &self,
315 room: &str,
316 identity: &str,
317 track_sid: &str,
318 muted: bool,
319 ) -> ServiceResult<proto::TrackInfo> {
320 let resp: proto::MuteRoomTrackResponse = self
321 .client
322 .request(
323 SVC,
324 "MutePublishedTrack",
325 proto::MuteRoomTrackRequest {
326 room: room.to_owned(),
327 identity: identity.to_owned(),
328 track_sid: track_sid.to_owned(),
329 muted,
330 },
331 self.base.auth_header(
332 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
333 None,
334 )?,
335 )
336 .await?;
337
338 Ok(resp.track.unwrap())
339 }
340
341 pub async fn update_participant(
342 &self,
343 room: &str,
344 identity: &str,
345 options: UpdateParticipantOptions,
346 ) -> ServiceResult<proto::ParticipantInfo> {
347 self.client
348 .request(
349 SVC,
350 "UpdateParticipant",
351 proto::UpdateParticipantRequest {
352 room: room.to_owned(),
353 identity: identity.to_owned(),
354 permission: options.permission,
355 metadata: options.metadata,
356 attributes: options.attributes.to_owned(),
357 name: options.name,
358 },
359 self.base.auth_header(
360 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
361 None,
362 )?,
363 )
364 .await
365 .map_err(Into::into)
366 }
367
368 pub async fn update_subscriptions(
369 &self,
370 room: &str,
371 identity: &str,
372 track_sids: Vec<String>,
373 subscribe: bool,
374 ) -> ServiceResult<()> {
375 self.client
376 .request(
377 SVC,
378 "UpdateSubscriptions",
379 proto::UpdateSubscriptionsRequest {
380 room: room.to_owned(),
381 identity: identity.to_owned(),
382 track_sids,
383 subscribe,
384 ..Default::default()
385 },
386 self.base.auth_header(
387 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
388 None,
389 )?,
390 )
391 .await
392 .map_err(Into::into)
393 }
394
395 pub async fn send_data(
396 &self,
397 room: &str,
398 data: Vec<u8>,
399 options: SendDataOptions,
400 ) -> ServiceResult<()> {
401 let mut rng = rand::rng();
402 let nonce: Vec<u8> = (0..16).map(|_| rng.random::<u8>()).collect();
403 #[allow(deprecated)]
404 self.client
405 .request(
406 SVC,
407 "SendData",
408 proto::SendDataRequest {
409 room: room.to_owned(),
410 data,
411 destination_sids: options.destination_sids,
412 topic: options.topic,
413 kind: options.kind as i32,
414 destination_identities: options.destination_identities,
415 nonce,
416 },
417 self.base.auth_header(
418 VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() },
419 None,
420 )?,
421 )
422 .await
423 .map_err(Into::into)
424 }
425}