Skip to main content

livekit_api/services/
room.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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>, // TODO(theomonnom): Better API?
32}
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, // No effect if left empty
40}
41
42#[derive(Debug, Clone, Default)]
43pub struct RemoveParticipantOptions {
44    /// Revoke all tokens issued to this participant before this Unix timestamp (ms).
45    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    /// Create a room with an explicit subscriber playout delay.
95    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}