1use std::sync::Arc;
4
5use futures_util::Stream;
6use reqwest::Method;
7use serde::{Deserialize, Serialize};
8use serde_json::{Map, Value};
9
10use crate::client::{CallOptions, Client};
11use crate::common::{
12 CompositionConfig, OnFailureConfig, ParticipantFileFormat, RecordingFile, RecordingFileFormat,
13 ResourceLinks, TrackFileFormat, TrackKind, TranscriptionConfig, WebhookDeliverySummary,
14};
15use crate::error::{Error, Result};
16use crate::pagination::{auto_page, paginate, ListParams, Page, PageFetcher};
17use crate::query::QueryBuilder;
18use crate::resources::egress::{
19 composition_to_config, to_egress_handle, Composition, EgressHandle, EgressType, StopTarget,
20 StopWire,
21};
22use crate::resources::escape;
23
24const PATH: &str = "/v2/recordings";
25const PARTICIPANT_PATH: &str = "/v2/recordings/participant";
26const TRACK_PATH: &str = "/v2/recordings/participant/track";
27const COMPOSITE_PATH: &str = "/v2/recordings/composite";
28const MERGE_PATH: &str = "/v2/recordings/participant/merge";
29
30#[derive(Debug, Clone, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct Recording {
36 pub id: String,
38 pub room_id: Option<String>,
40 pub file: Option<RecordingFile>,
42 pub webhook: Option<WebhookDeliverySummary>,
44 pub start: Option<String>,
46 pub end: Option<String>,
48 #[serde(default, deserialize_with = "crate::common::null_to_default")]
50 pub links: ResourceLinks,
51 #[serde(flatten)]
53 pub extra: Map<String, Value>,
54}
55
56#[derive(Debug, Clone, Deserialize)]
58#[serde(rename_all = "camelCase")]
59pub struct IndividualRecording {
60 pub id: String,
62 pub room_id: Option<String>,
64 pub participant_name: Option<String>,
66 #[serde(default, deserialize_with = "crate::common::null_to_default")]
68 pub files: Vec<RecordingFile>,
69 pub webhook: Option<WebhookDeliverySummary>,
71 pub start: Option<String>,
73 pub end: Option<String>,
75 #[serde(flatten)]
77 pub extra: Map<String, Value>,
78}
79
80#[derive(Debug, Clone, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct CompositeRecording {
84 pub id: String,
86 pub meeting_id: Option<String>,
88 pub room_id: Option<String>,
90 pub session_id: Option<String>,
92 #[serde(default, deserialize_with = "crate::common::null_to_default")]
94 pub participants: Vec<Value>,
95 pub file_format: Option<String>,
97 pub start: Option<String>,
99 pub end: Option<String>,
101 pub file_id: Option<String>,
103 pub file: Option<RecordingFile>,
105 #[serde(flatten)]
107 pub extra: Map<String, Value>,
108}
109
110#[derive(Debug, Clone, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct MergeChannel {
114 pub recording_id: Option<String>,
116 pub participant_id: Option<String>,
118}
119
120#[derive(Debug, Clone, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct MergeRecording {
124 pub id: String,
126 #[serde(default, deserialize_with = "crate::common::null_to_default")]
128 pub channel1: Vec<MergeChannel>,
129 #[serde(default, deserialize_with = "crate::common::null_to_default")]
131 pub channel2: Vec<MergeChannel>,
132 pub start: Option<String>,
134 pub end: Option<String>,
136 pub status: Option<String>,
138 pub meeting_id: Option<String>,
140 pub session_id: Option<String>,
142 pub file: Option<RecordingFile>,
144 #[serde(flatten)]
146 pub extra: Map<String, Value>,
147}
148
149#[derive(Debug, Clone, Deserialize)]
151pub struct MergeRecordingResult {
152 pub message: String,
154 pub recording: MergeRecording,
156}
157
158#[derive(Debug, Clone, Default)]
162pub struct RecordingStartParams {
163 pub composition: Option<Composition>,
165 pub transcription: Option<TranscriptionConfig>,
167 pub on_failure: Option<OnFailureConfig>,
169 pub file_format: Option<RecordingFileFormat>,
171 pub webhook_url: Option<String>,
173 pub dir_path: Option<String>,
175 pub resource_id: Option<String>,
177 pub pre_signed_url: Option<String>,
179}
180
181#[derive(Debug, Serialize)]
184#[serde(rename_all = "camelCase")]
185struct RecordingStartWire<'a> {
186 room_id: &'a str,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 config: Option<CompositionConfig>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 template_url: Option<String>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 aws_dir_path: Option<&'a str>,
193 #[serde(skip_serializing_if = "Option::is_none")]
194 transcription: Option<&'a TranscriptionConfig>,
195 #[serde(skip_serializing_if = "Option::is_none")]
196 on_failure: Option<&'a OnFailureConfig>,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 file_format: Option<RecordingFileFormat>,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 webhook_url: Option<&'a str>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 resource_id: Option<&'a str>,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pre_signed_url: Option<&'a str>,
206}
207
208#[derive(Debug, Clone, Default)]
210pub struct RecordingGetParams {
211 pub with_transcription: Option<bool>,
213}
214
215#[derive(Debug, Clone, Default)]
217pub struct ParticipantRecordingStartParams {
218 pub participant_id: String,
220 pub file_format: Option<ParticipantFileFormat>,
222 pub webhook_url: Option<String>,
224 pub dir_path: Option<String>,
226 pub metadata: Option<Value>,
228}
229
230#[derive(Debug, Serialize)]
231#[serde(rename_all = "camelCase")]
232struct ParticipantRecordingStartWire<'a> {
233 room_id: &'a str,
234 participant_id: &'a str,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 file_format: Option<ParticipantFileFormat>,
237 #[serde(skip_serializing_if = "Option::is_none")]
238 webhook_url: Option<&'a str>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 bucket_dir_path: Option<&'a str>,
241 #[serde(skip_serializing_if = "Option::is_none")]
242 metadata: Option<&'a Value>,
243}
244
245#[derive(Debug, Clone)]
247pub struct TrackRecordingStartParams {
248 pub participant_id: String,
250 pub kind: TrackKind,
252 pub file_format: Option<TrackFileFormat>,
254 pub webhook_url: Option<String>,
256 pub dir_path: Option<String>,
258}
259
260#[derive(Debug, Serialize)]
261#[serde(rename_all = "camelCase")]
262struct TrackRecordingStartWire<'a> {
263 room_id: &'a str,
264 participant_id: &'a str,
265 kind: TrackKind,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 file_format: Option<TrackFileFormat>,
268 #[serde(skip_serializing_if = "Option::is_none")]
269 webhook_url: Option<&'a str>,
270 #[serde(skip_serializing_if = "Option::is_none")]
271 bucket_dir_path: Option<&'a str>,
272}
273
274#[derive(Debug, Clone, Serialize)]
276#[serde(rename_all = "camelCase")]
277pub struct CompositeParticipantSelector {
278 pub participant_id: String,
280 #[serde(skip_serializing_if = "Vec::is_empty")]
282 pub kind: Vec<TrackKind>,
283}
284
285#[derive(Debug, Clone, Default, Serialize)]
287#[serde(rename_all = "camelCase")]
288pub struct CompositeWatermark {
289 #[serde(rename = "type")]
291 pub kind: String,
292 #[serde(skip_serializing_if = "Option::is_none")]
294 pub image_url: Option<String>,
295 #[serde(skip_serializing_if = "Option::is_none")]
297 pub image_base64: Option<String>,
298 #[serde(skip_serializing_if = "Option::is_none")]
300 pub text: Option<String>,
301 #[serde(skip_serializing_if = "Option::is_none")]
303 pub timezone: Option<String>,
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub topic: Option<String>,
307}
308
309#[derive(Debug, Clone, Default)]
311pub struct CompositeRecordingStartParams {
312 pub participants: Vec<CompositeParticipantSelector>,
314 pub watermarks: Vec<CompositeWatermark>,
316 pub webhook_url: Option<String>,
318 pub dir_path: Option<String>,
320 pub pre_signed_url: Option<String>,
322 pub file_format: Option<String>,
324}
325
326#[derive(Debug, Serialize)]
327#[serde(rename_all = "camelCase")]
328struct CompositeRecordingStartWire<'a> {
329 room_id: &'a str,
330 #[serde(skip_serializing_if = "<[_]>::is_empty")]
331 participants: &'a [CompositeParticipantSelector],
332 #[serde(skip_serializing_if = "<[_]>::is_empty")]
333 watermarks: &'a [CompositeWatermark],
334 #[serde(skip_serializing_if = "Option::is_none")]
335 webhook_url: Option<&'a str>,
336 #[serde(skip_serializing_if = "Option::is_none")]
337 bucket_dir_path: Option<&'a str>,
338 #[serde(rename = "preSignedURL", skip_serializing_if = "Option::is_none")]
340 pre_signed_url: Option<&'a str>,
341 #[serde(skip_serializing_if = "Option::is_none")]
342 file_format: Option<&'a str>,
343}
344
345#[derive(Debug, Clone, Serialize)]
347#[serde(rename_all = "camelCase")]
348pub struct MergeChannelEntry {
349 pub participant_id: String,
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub recording_id: Option<String>,
354 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
356 pub kind: Option<String>,
357}
358
359#[derive(Debug, Clone, Default, Serialize)]
361#[serde(rename_all = "camelCase")]
362pub struct MergeRecordingCreateParams {
363 pub session_id: String,
365 pub channel1: Vec<MergeChannelEntry>,
367 pub channel2: Vec<MergeChannelEntry>,
369 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
371 pub kind: Option<String>,
372 #[serde(skip_serializing_if = "Option::is_none")]
374 pub webhook_url: Option<String>,
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub dir_path: Option<String>,
378}
379
380#[derive(Debug, Clone, Default)]
384pub struct RecordingListParams {
385 pub page: Option<u32>,
387 pub per_page: Option<u32>,
389 pub cursor: Option<String>,
391 pub room_id: Option<String>,
393 pub session_id: Option<String>,
395 pub user_id: Option<String>,
397 pub query: Option<String>,
399 pub with_transcription: Option<bool>,
401}
402
403#[derive(Debug, Clone, Default)]
405pub struct IndividualRecordingListParams {
406 pub page: Option<u32>,
408 pub per_page: Option<u32>,
410 pub cursor: Option<String>,
412 pub room_id: Option<String>,
414 pub session_id: Option<String>,
416 pub participant_id: Option<String>,
418 pub user_id: Option<String>,
420}
421
422#[derive(Debug, Clone, Default)]
424pub struct CompositeRecordingListParams {
425 pub page: Option<u32>,
427 pub per_page: Option<u32>,
429 pub cursor: Option<String>,
431 pub room_id: Option<String>,
433 pub session_id: Option<String>,
435}
436
437#[derive(Debug, Clone, Default)]
439pub struct MergeRecordingListParams {
440 pub page: Option<u32>,
442 pub per_page: Option<u32>,
444 pub cursor: Option<String>,
446 pub status: Option<String>,
448 pub room_id: Option<String>,
450 pub session_id: Option<String>,
452 pub id: Option<String>,
454}
455
456macro_rules! pagination {
457 ($name:ident) => {
458 impl $name {
459 fn pagination(&self) -> ListParams {
460 ListParams {
461 page: self.page,
462 per_page: self.per_page,
463 cursor: self.cursor.clone(),
464 }
465 }
466 }
467 };
468}
469
470pagination!(RecordingListParams);
471pagination!(IndividualRecordingListParams);
472pagination!(CompositeRecordingListParams);
473pagination!(MergeRecordingListParams);
474
475#[derive(Debug, Clone, Copy)]
479pub struct RecordingParticipantResource<'a> {
480 client: &'a Client,
481}
482
483impl<'a> RecordingParticipantResource<'a> {
484 pub async fn start(
486 &self,
487 room_id: &str,
488 params: ParticipantRecordingStartParams,
489 ) -> Result<String> {
490 let body = ParticipantRecordingStartWire {
491 room_id,
492 participant_id: ¶ms.participant_id,
493 file_format: params.file_format,
494 webhook_url: params.webhook_url.as_deref(),
495 bucket_dir_path: params.dir_path.as_deref(),
496 metadata: params.metadata.as_ref(),
497 };
498 let path = format!("{PARTICIPANT_PATH}/start");
499 self.client
500 .message(Method::POST, &path, CallOptions::json(&body)?)
501 .await
502 }
503
504 pub async fn stop(&self, room_id: &str, participant_id: &str) -> Result<String> {
506 let body = serde_json::json!({"roomId": room_id, "participantId": participant_id});
507 let path = format!("{PARTICIPANT_PATH}/stop");
508 self.client
509 .message(Method::POST, &path, CallOptions::json(&body)?)
510 .await
511 }
512
513 pub async fn list(
515 &self,
516 params: IndividualRecordingListParams,
517 ) -> Result<Page<IndividualRecording>> {
518 let fetcher = individual_fetcher(self.client, PARTICIPANT_PATH, ¶ms);
519 paginate(fetcher, ¶ms.pagination(), "data", None).await
520 }
521
522 pub fn list_stream(
524 &self,
525 params: IndividualRecordingListParams,
526 ) -> impl Stream<Item = Result<IndividualRecording>> + Send {
527 let fetcher = individual_fetcher(self.client, PARTICIPANT_PATH, ¶ms);
528 auto_page(fetcher, params.pagination(), "data", None)
529 }
530
531 pub async fn get(&self, id: &str) -> Result<IndividualRecording> {
533 let path = format!("{PARTICIPANT_PATH}/{}", escape(id));
534 self.client
535 .json(Method::GET, &path, CallOptions::new())
536 .await
537 }
538
539 pub async fn delete(&self, id: &str) -> Result<String> {
541 let path = format!("{PARTICIPANT_PATH}/{}", escape(id));
542 self.client
543 .message(Method::DELETE, &path, CallOptions::new())
544 .await
545 }
546}
547
548#[derive(Debug, Clone, Copy)]
550pub struct RecordingTrackResource<'a> {
551 client: &'a Client,
552}
553
554impl<'a> RecordingTrackResource<'a> {
555 pub async fn start(&self, room_id: &str, params: TrackRecordingStartParams) -> Result<String> {
557 let body = TrackRecordingStartWire {
558 room_id,
559 participant_id: ¶ms.participant_id,
560 kind: params.kind,
561 file_format: params.file_format,
562 webhook_url: params.webhook_url.as_deref(),
563 bucket_dir_path: params.dir_path.as_deref(),
564 };
565 let path = format!("{TRACK_PATH}/start");
566 self.client
567 .message(Method::POST, &path, CallOptions::json(&body)?)
568 .await
569 }
570
571 pub async fn stop(
573 &self,
574 room_id: &str,
575 participant_id: &str,
576 kind: TrackKind,
577 ) -> Result<String> {
578 let body = serde_json::json!({
579 "roomId": room_id, "participantId": participant_id, "kind": kind,
580 });
581 let path = format!("{TRACK_PATH}/stop");
582 self.client
583 .message(Method::POST, &path, CallOptions::json(&body)?)
584 .await
585 }
586
587 pub async fn list(
589 &self,
590 params: IndividualRecordingListParams,
591 ) -> Result<Page<IndividualRecording>> {
592 let fetcher = individual_fetcher(self.client, TRACK_PATH, ¶ms);
593 paginate(fetcher, ¶ms.pagination(), "data", None).await
594 }
595
596 pub fn list_stream(
598 &self,
599 params: IndividualRecordingListParams,
600 ) -> impl Stream<Item = Result<IndividualRecording>> + Send {
601 let fetcher = individual_fetcher(self.client, TRACK_PATH, ¶ms);
602 auto_page(fetcher, params.pagination(), "data", None)
603 }
604
605 pub async fn get(&self, id: &str) -> Result<IndividualRecording> {
607 let path = format!("{TRACK_PATH}/{}", escape(id));
608 self.client
609 .json(Method::GET, &path, CallOptions::new())
610 .await
611 }
612
613 pub async fn delete(&self, id: &str) -> Result<String> {
615 let path = format!("{TRACK_PATH}/{}", escape(id));
616 self.client
617 .message(Method::DELETE, &path, CallOptions::new())
618 .await
619 }
620}
621
622#[derive(Debug, Clone, Copy)]
624pub struct RecordingCompositeResource<'a> {
625 client: &'a Client,
626}
627
628impl<'a> RecordingCompositeResource<'a> {
629 pub async fn start(
632 &self,
633 room_id: &str,
634 params: CompositeRecordingStartParams,
635 ) -> Result<EgressHandle> {
636 let body = CompositeRecordingStartWire {
637 room_id,
638 participants: ¶ms.participants,
639 watermarks: ¶ms.watermarks,
640 webhook_url: params.webhook_url.as_deref(),
641 bucket_dir_path: params.dir_path.as_deref(),
642 pre_signed_url: params.pre_signed_url.as_deref(),
643 file_format: params.file_format.as_deref(),
644 };
645 let path = format!("{COMPOSITE_PATH}/start");
646 let raw = self
647 .client
648 .maybe_json(Method::POST, &path, CallOptions::json(&body)?)
649 .await?;
650 Ok(to_egress_handle(EgressType::Composite, room_id, raw))
651 }
652
653 pub async fn stop(&self, handle: &EgressHandle) -> Result<String> {
659 let id = handle
660 .id
661 .as_deref()
662 .filter(|id| !id.is_empty())
663 .ok_or_else(|| {
664 Error::validation(
665 "recordings.composite().stop() requires a recordingId, from the start handle",
666 )
667 })?;
668 let body = serde_json::json!({"roomId": handle.room_id, "recordingId": id});
669 let path = format!("{COMPOSITE_PATH}/stop");
670 self.client
671 .message(Method::POST, &path, CallOptions::json(&body)?)
672 .await
673 }
674
675 pub async fn list(
677 &self,
678 params: CompositeRecordingListParams,
679 ) -> Result<Page<CompositeRecording>> {
680 paginate(self.fetcher(¶ms), ¶ms.pagination(), "data", None).await
681 }
682
683 pub fn list_stream(
685 &self,
686 params: CompositeRecordingListParams,
687 ) -> impl Stream<Item = Result<CompositeRecording>> + Send {
688 auto_page(self.fetcher(¶ms), params.pagination(), "data", None)
689 }
690
691 pub async fn get(&self, id: &str) -> Result<CompositeRecording> {
693 let path = format!("{COMPOSITE_PATH}/{}", escape(id));
694 self.client
695 .json(Method::GET, &path, CallOptions::new())
696 .await
697 }
698
699 pub async fn delete(&self, id: &str) -> Result<String> {
701 let path = format!("{COMPOSITE_PATH}/{}", escape(id));
702 self.client
703 .message(Method::DELETE, &path, CallOptions::new())
704 .await
705 }
706
707 fn fetcher(&self, params: &CompositeRecordingListParams) -> PageFetcher {
708 let client = self.client.clone();
709 let params = params.clone();
710 Arc::new(move |page, per_page| {
711 let client = client.clone();
712 let params = params.clone();
713 Box::pin(async move {
714 let query = QueryBuilder::new()
715 .opt("page", page)
716 .opt("perPage", per_page)
717 .opt_str("roomId", params.room_id.as_deref())
718 .opt_str("sessionId", params.session_id.as_deref())
719 .into_pairs();
720 client
721 .json::<Value>(Method::GET, COMPOSITE_PATH, CallOptions::new().query(query))
722 .await
723 })
724 })
725 }
726}
727
728#[derive(Debug, Clone, Copy)]
731pub struct MergeRecordingResource<'a> {
732 client: &'a Client,
733}
734
735impl<'a> MergeRecordingResource<'a> {
736 pub async fn create(&self, params: MergeRecordingCreateParams) -> Result<MergeRecordingResult> {
738 self.client
739 .json(Method::POST, MERGE_PATH, CallOptions::json(¶ms)?)
740 .await
741 }
742
743 pub async fn list(&self, params: MergeRecordingListParams) -> Result<Page<MergeRecording>> {
745 paginate(
747 self.fetcher(¶ms),
748 ¶ms.pagination(),
749 "recordings",
750 None,
751 )
752 .await
753 }
754
755 pub fn list_stream(
757 &self,
758 params: MergeRecordingListParams,
759 ) -> impl Stream<Item = Result<MergeRecording>> + Send {
760 auto_page(
761 self.fetcher(¶ms),
762 params.pagination(),
763 "recordings",
764 None,
765 )
766 }
767
768 pub async fn get(&self, muxer_id: &str) -> Result<MergeRecording> {
770 let path = format!("{MERGE_PATH}/{}", escape(muxer_id));
771 self.client
772 .json(Method::GET, &path, CallOptions::new())
773 .await
774 }
775
776 fn fetcher(&self, params: &MergeRecordingListParams) -> PageFetcher {
777 let client = self.client.clone();
778 let params = params.clone();
779 Arc::new(move |page, per_page| {
780 let client = client.clone();
781 let params = params.clone();
782 Box::pin(async move {
783 let query = QueryBuilder::new()
784 .opt("page", page)
785 .opt("perPage", per_page)
786 .opt_str("status", params.status.as_deref())
787 .opt_str("roomId", params.room_id.as_deref())
788 .opt_str("sessionId", params.session_id.as_deref())
789 .opt_str("id", params.id.as_deref())
790 .into_pairs();
791 client
792 .json::<Value>(Method::GET, MERGE_PATH, CallOptions::new().query(query))
793 .await
794 })
795 })
796 }
797}
798
799fn individual_fetcher(
801 client: &Client,
802 path: &'static str,
803 params: &IndividualRecordingListParams,
804) -> PageFetcher {
805 let client = client.clone();
806 let params = params.clone();
807 Arc::new(move |page, per_page| {
808 let client = client.clone();
809 let params = params.clone();
810 Box::pin(async move {
811 let query = QueryBuilder::new()
812 .opt("page", page)
813 .opt("perPage", per_page)
814 .opt_str("roomId", params.room_id.as_deref())
815 .opt_str("sessionId", params.session_id.as_deref())
816 .opt_str("participantId", params.participant_id.as_deref())
817 .opt_str("userId", params.user_id.as_deref())
818 .into_pairs();
819 client
820 .json::<Value>(Method::GET, path, CallOptions::new().query(query))
821 .await
822 })
823 })
824}
825
826#[derive(Debug, Clone, Copy)]
833pub struct RecordingsResource<'a> {
834 client: &'a Client,
835}
836
837impl<'a> RecordingsResource<'a> {
838 pub(crate) fn new(client: &'a Client) -> Self {
839 Self { client }
840 }
841
842 pub fn participant(&self) -> RecordingParticipantResource<'a> {
844 RecordingParticipantResource {
845 client: self.client,
846 }
847 }
848
849 pub fn track(&self) -> RecordingTrackResource<'a> {
851 RecordingTrackResource {
852 client: self.client,
853 }
854 }
855
856 pub fn composite(&self) -> RecordingCompositeResource<'a> {
858 RecordingCompositeResource {
859 client: self.client,
860 }
861 }
862
863 pub fn merge(&self) -> MergeRecordingResource<'a> {
865 MergeRecordingResource {
866 client: self.client,
867 }
868 }
869
870 pub async fn start(&self, room_id: &str, params: RecordingStartParams) -> Result<EgressHandle> {
872 let mapped = composition_to_config(params.composition.as_ref(), None);
873 let body = RecordingStartWire {
874 room_id,
875 config: mapped.config,
876 template_url: mapped.template_url,
877 aws_dir_path: params.dir_path.as_deref(),
878 transcription: params.transcription.as_ref(),
879 on_failure: params.on_failure.as_ref(),
880 file_format: params.file_format,
881 webhook_url: params.webhook_url.as_deref(),
882 resource_id: params.resource_id.as_deref(),
883 pre_signed_url: params.pre_signed_url.as_deref(),
884 };
885 let path = format!("{PATH}/start");
886 let raw = self
887 .client
888 .maybe_json(Method::POST, &path, CallOptions::json(&body)?)
889 .await?;
890 Ok(to_egress_handle(EgressType::Recording, room_id, raw))
891 }
892
893 pub async fn stop(&self, target: impl Into<StopTarget>) -> Result<String> {
895 let target = target.into();
896 let path = format!("{PATH}/end");
897 self.client
898 .message(
899 Method::POST,
900 &path,
901 CallOptions::json(StopWire::from(&target))?,
902 )
903 .await
904 }
905
906 pub async fn list(&self, params: RecordingListParams) -> Result<Page<Recording>> {
908 paginate(self.fetcher(¶ms), ¶ms.pagination(), "data", None).await
909 }
910
911 pub fn list_stream(
913 &self,
914 params: RecordingListParams,
915 ) -> impl Stream<Item = Result<Recording>> + Send {
916 auto_page(self.fetcher(¶ms), params.pagination(), "data", None)
917 }
918
919 pub async fn get(&self, id: &str, params: RecordingGetParams) -> Result<Recording> {
921 let query = QueryBuilder::new()
922 .opt("withTranscription", params.with_transcription)
923 .into_pairs();
924 let path = format!("{PATH}/{}", escape(id));
925 self.client
926 .json(Method::GET, &path, CallOptions::new().query(query))
927 .await
928 }
929
930 pub async fn delete(&self, id: &str) -> Result<String> {
932 let path = format!("{PATH}/{}", escape(id));
933 self.client
934 .message(Method::DELETE, &path, CallOptions::new())
935 .await
936 }
937
938 fn fetcher(&self, params: &RecordingListParams) -> PageFetcher {
939 let client = self.client.clone();
940 let params = params.clone();
941 Arc::new(move |page, per_page| {
942 let client = client.clone();
943 let params = params.clone();
944 Box::pin(async move {
945 let query = QueryBuilder::new()
946 .opt("page", page)
947 .opt("perPage", per_page)
948 .opt_str("roomId", params.room_id.as_deref())
949 .opt_str("sessionId", params.session_id.as_deref())
950 .opt_str("userId", params.user_id.as_deref())
951 .opt_str("query", params.query.as_deref())
952 .opt("withTranscription", params.with_transcription)
953 .into_pairs();
954 client
955 .json::<Value>(Method::GET, PATH, CallOptions::new().query(query))
956 .await
957 })
958 })
959 }
960}