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