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