1mod tones;
4use std::sync::Arc;
5#[cfg(not(feature = "desktop-media"))]
6use std::sync::Mutex;
7pub use tones::DesktopCallTone;
8#[cfg(feature = "desktop-media")]
9mod audio;
10#[cfg(feature = "desktop-media")]
11mod devices;
12#[cfg(feature = "desktop-media")]
13mod engine;
14#[cfg(feature = "desktop-media")]
15mod video;
16#[cfg(feature = "desktop-media")]
17mod video_rgb;
18
19#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)]
20pub struct DesktopAudioDevice {
21 pub id: String,
22 pub name: String,
23}
24
25#[derive(Clone, Debug, Default, PartialEq, Eq, uniffi::Record)]
26pub struct DesktopAudioDevices {
27 pub microphones: Vec<DesktopAudioDevice>,
28 pub speakers: Vec<DesktopAudioDevice>,
29 pub microphone: String,
30 pub speaker: String,
31 pub error: Option<String>,
32}
33
34#[derive(Clone, Debug, uniffi::Enum)]
35pub enum DesktopCallEvent {
36 Encoded {
37 kind: u8,
38 timestamp_us: u64,
39 key_frame: bool,
40 data: Vec<u8>,
41 },
42 Video {
43 local: bool,
44 width: u32,
45 height: u32,
46 rgba: Vec<u8>,
47 },
48 Ready,
49 RequestKeyFrame,
50 Error {
51 message: String,
52 },
53}
54
55#[derive(uniffi::Object)]
56pub struct DesktopCallMedia {
57 #[cfg(feature = "desktop-media")]
58 engine: engine::Engine,
59 #[cfg(not(feature = "desktop-media"))]
60 error: Mutex<bool>,
61}
62#[uniffi::export]
63impl DesktopCallMedia {
64 #[uniffi::constructor]
65 pub fn new() -> Arc<Self> {
66 Arc::new(Self {
67 #[cfg(feature = "desktop-media")]
68 engine: engine::Engine::new(),
69 #[cfg(not(feature = "desktop-media"))]
70 error: Mutex::new(true),
71 })
72 }
73 pub fn configure(&self, muted: bool, video: bool, bitrate: u32, key_generation: u32) {
74 #[cfg(feature = "desktop-media")]
75 self.engine.configure(muted, video, bitrate, key_generation);
76 #[cfg(not(feature = "desktop-media"))]
77 let _ = (muted, video, bitrate, key_generation);
78 }
79 pub fn set_external_video(&self, enabled: bool) {
82 #[cfg(feature = "desktop-media")]
83 self.engine.set_external_video(enabled);
84 #[cfg(not(feature = "desktop-media"))]
85 let _ = enabled;
86 }
87 pub fn submit_video_frame(&self, width: u32, height: u32, rgba: Vec<u8>) {
89 #[cfg(feature = "desktop-media")]
90 self.engine.submit_video_frame(width, height, rgba);
91 #[cfg(not(feature = "desktop-media"))]
92 let _ = (width, height, rgba);
93 }
94 pub fn audio_devices(&self) -> DesktopAudioDevices {
96 #[cfg(feature = "desktop-media")]
97 {
98 self.engine.audio_devices()
99 }
100 #[cfg(not(feature = "desktop-media"))]
101 {
102 DesktopAudioDevices::default()
103 }
104 }
105 pub fn select_audio_devices(&self, microphone: String, speaker: String) {
106 #[cfg(feature = "desktop-media")]
107 self.engine.select_audio_devices(microphone, speaker);
108 #[cfg(not(feature = "desktop-media"))]
109 let _ = (microphone, speaker);
110 }
111 pub fn receive(
112 &self,
113 kind: u8,
114 sequence: u32,
115 timestamp_us: u64,
116 key_frame: bool,
117 data: Vec<u8>,
118 ) {
119 #[cfg(feature = "desktop-media")]
120 self.engine
121 .receive(kind, sequence, timestamp_us, key_frame, data);
122 #[cfg(not(feature = "desktop-media"))]
123 let _ = (kind, sequence, timestamp_us, key_frame, data);
124 }
125 pub fn poll(&self) -> Vec<DesktopCallEvent> {
127 #[cfg(feature = "desktop-media")]
128 {
129 self.engine.poll()
130 }
131 #[cfg(not(feature = "desktop-media"))]
132 {
133 if let Ok(mut error) = self.error.lock() {
134 if std::mem::take(&mut *error) {
135 return vec![DesktopCallEvent::Error {
136 message: "Calls are unavailable in this build.".into(),
137 }];
138 }
139 }
140 Vec::new()
141 }
142 }
143 pub fn stop(&self) {
144 #[cfg(feature = "desktop-media")]
145 self.engine.stop();
146 }
147}
148impl Drop for DesktopCallMedia {
149 fn drop(&mut self) {
150 self.stop();
151 }
152}
153
154#[cfg(all(feature = "desktop-media", feature = "stack-fixture"))]
157pub struct DesktopVideoTranscoder {
158 decoder: video::VideoDecoder,
159 encoder: video::VideoEncoder,
160 pacer: video::FramePacer,
161 key_generation: u32,
162}
163#[cfg(all(feature = "desktop-media", feature = "stack-fixture"))]
164impl DesktopVideoTranscoder {
165 pub fn new() -> Result<Self, String> {
166 Ok(Self {
167 decoder: video::VideoDecoder::new().map_err(|e| e.to_string())?,
168 encoder: video::VideoEncoder::new(1_200_000).map_err(|e| e.to_string())?,
169 pacer: video::FramePacer::default(),
170 key_generation: 0,
171 })
172 }
173 pub fn receive(
174 &mut self,
175 seq: u32,
176 key: bool,
177 bytes: Vec<u8>,
178 target: u32,
179 generation: u32,
180 ) -> Vec<DesktopCallEvent> {
181 let now = std::time::Instant::now();
182 self.decoder.receive(seq, key, bytes, now);
183 let mut output = Vec::new();
184 for event in self.decoder.drain(now) {
185 match event {
186 DesktopCallEvent::Video {
187 width,
188 height,
189 rgba,
190 ..
191 } => {
192 let (w, h, fps) = video::settings(target, width, height);
193 if !self.pacer.accept(now, fps) {
194 continue;
195 }
196 let Some(image) = image::RgbaImage::from_raw(width, height, rgba) else {
197 continue;
198 };
199 let scaled = image::DynamicImage::ImageRgba8(image)
200 .resize_exact(w, h, image::imageops::FilterType::Triangle)
201 .into_rgb8();
202 if let Ok(Some((data, key_frame))) = self.encoder.encode(
203 scaled.as_raw(),
204 w,
205 h,
206 target,
207 self.key_generation != generation,
208 ) {
209 self.key_generation = generation;
210 output.push(DesktopCallEvent::Encoded {
211 kind: 2,
212 timestamp_us: 0,
213 key_frame,
214 data,
215 });
216 }
217 }
218 DesktopCallEvent::RequestKeyFrame => output.push(event),
219 _ => {}
220 }
221 }
222 output
223 }
224}