1use dart_sys::Dart_Handle;
8use derive_more::with_trait::From;
9use medea_macro::dart_bridge;
10
11use crate::{
12 api::DartValue,
13 media::{
14 AudioTrackConstraints, DeviceVideoTrackConstraints,
15 DisplayVideoTrackConstraints,
16 constraints::{ConstrainBoolean, ConstrainString, ConstrainU32},
17 },
18 platform::dart::utils::handle::DartHandle,
19};
20
21#[dart_bridge("flutter/lib/src/native/platform/constraints.g.dart")]
22mod constraints {
23 use dart_sys::Dart_Handle;
24
25 use crate::{api::DartValue, platform::Error};
26
27 extern "C" {
28 pub fn init_device_constraints() -> Result<Dart_Handle, Error>;
32
33 pub fn init_display_constraints() -> Result<Dart_Handle, Error>;
37
38 pub fn new_video_constraints() -> Result<Dart_Handle, Error>;
42
43 pub fn new_audio_constraints() -> Result<Dart_Handle, Error>;
47
48 pub fn set_video_constraint_value(
53 constraints: Dart_Handle,
54 kind: i64,
55 value: DartValue,
56 ) -> Result<(), Error>;
57
58 pub fn set_audio_constraint_value(
63 constraints: Dart_Handle,
64 kind: i64,
65 value: DartValue,
66 ) -> Result<(), Error>;
67
68 pub fn set_video_constraint(
74 constraints: Dart_Handle,
75 ty: i64,
76 video: Dart_Handle,
77 ) -> Result<(), Error>;
78
79 pub fn set_display_video_constraint(
85 constraints: Dart_Handle,
86 ty: i64,
87 video: Dart_Handle,
88 ) -> Result<(), Error>;
89
90 pub fn set_audio_constraint(
96 constraints: Dart_Handle,
97 ty: i64,
98 audio: Dart_Handle,
99 ) -> Result<(), Error>;
100 }
101}
102
103enum VideoConstraintKind {
107 FacingMode = 0,
108 DeviceId = 1,
109 Width = 2,
110 Height = 3,
111 FrameRate = 4,
112}
113
114enum AudioConstraintKind {
118 DeviceId = 0,
119 AutoGainControl = 1,
120 NoiseSuppression = 2,
121 NoiseSuppressionLevel = 3,
122 HighPassFilter = 4,
123 EchoCancellation = 5,
124}
125
126enum ConstraintType {
130 Optional = 0,
133
134 Mandatory = 1,
137}
138
139#[derive(Debug)]
143pub struct MediaTrackConstraints {
144 optional: DartHandle,
147
148 mandatory: DartHandle,
151}
152
153#[derive(Clone, Debug, From)]
157pub struct MediaStreamConstraints(DartHandle);
158
159impl From<MediaStreamConstraints> for Dart_Handle {
160 fn from(from: MediaStreamConstraints) -> Self {
161 from.0.get()
162 }
163}
164
165impl Default for MediaStreamConstraints {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171impl MediaStreamConstraints {
172 #[must_use]
174 pub fn new() -> Self {
175 let constraints =
176 unsafe { constraints::init_device_constraints() }.unwrap();
177 unsafe { Self(DartHandle::new(constraints)) }
178 }
179
180 pub fn audio(&mut self, audio: AudioTrackConstraints) {
185 let audio = MediaTrackConstraints::from(audio);
186 unsafe {
187 constraints::set_audio_constraint(
188 self.0.get(),
189 ConstraintType::Mandatory as i64,
190 audio.mandatory.get(),
191 )
192 }
193 .unwrap();
194 unsafe {
195 constraints::set_audio_constraint(
196 self.0.get(),
197 ConstraintType::Optional as i64,
198 audio.optional.get(),
199 )
200 }
201 .unwrap();
202 }
203
204 pub fn video(&mut self, video: DeviceVideoTrackConstraints) {
209 let video = MediaTrackConstraints::from(video);
210 unsafe {
211 constraints::set_video_constraint(
212 self.0.get(),
213 ConstraintType::Mandatory as i64,
214 video.mandatory.get(),
215 )
216 }
217 .unwrap();
218 unsafe {
219 constraints::set_video_constraint(
220 self.0.get(),
221 ConstraintType::Optional as i64,
222 video.optional.get(),
223 )
224 }
225 .unwrap();
226 }
227}
228
229#[derive(Clone, Debug, From)]
233pub struct DisplayMediaStreamConstraints(DartHandle);
234
235impl From<DisplayMediaStreamConstraints> for Dart_Handle {
236 fn from(from: DisplayMediaStreamConstraints) -> Self {
237 from.0.get()
238 }
239}
240
241impl Default for DisplayMediaStreamConstraints {
242 fn default() -> Self {
243 Self::new()
244 }
245}
246
247impl DisplayMediaStreamConstraints {
248 #[must_use]
250 pub fn new() -> Self {
251 let constraints =
252 unsafe { constraints::init_display_constraints() }.unwrap();
253 unsafe { Self(DartHandle::new(constraints)) }
254 }
255
256 pub fn video(&mut self, video: DisplayVideoTrackConstraints) {
261 let video = MediaTrackConstraints::from(video);
262 unsafe {
263 constraints::set_display_video_constraint(
264 self.0.get(),
265 ConstraintType::Mandatory as i64,
266 video.mandatory.get(),
267 )
268 }
269 .unwrap();
270 unsafe {
271 constraints::set_display_video_constraint(
272 self.0.get(),
273 ConstraintType::Optional as i64,
274 video.optional.get(),
275 )
276 }
277 .unwrap();
278 }
279}
280
281#[expect(clippy::fallible_impl_from, reason = "FFI error is unexpected")]
282impl From<AudioTrackConstraints> for MediaTrackConstraints {
283 fn from(from: AudioTrackConstraints) -> Self {
284 let optional = {
285 let audio =
286 unsafe { constraints::new_audio_constraints() }.unwrap();
287 unsafe { DartHandle::new(audio) }
288 };
289 let mandatory = {
290 let audio =
291 unsafe { constraints::new_audio_constraints() }.unwrap();
292 unsafe { DartHandle::new(audio) }
293 };
294 let parse_bool_const = |c: ConstrainBoolean| -> (&DartHandle, bool) {
295 match c {
296 ConstrainBoolean::Exact(val) => (&mandatory, val),
297 ConstrainBoolean::Ideal(val) => (&optional, val),
298 }
299 };
300
301 if let Some(device_id) = from.device_id {
302 match device_id {
303 ConstrainString::Exact(device_id) => unsafe {
304 constraints::set_audio_constraint_value(
305 mandatory.get(),
306 AudioConstraintKind::DeviceId as i64,
307 DartValue::from(device_id),
308 )
309 }
310 .unwrap(),
311 ConstrainString::Ideal(device_id) => unsafe {
312 constraints::set_audio_constraint_value(
313 optional.get(),
314 AudioConstraintKind::DeviceId as i64,
315 DartValue::from(device_id),
316 )
317 }
318 .unwrap(),
319 }
320 }
321 if let Some(agc) = from.auto_gain_control {
322 let (kind, val) = parse_bool_const(agc);
323
324 unsafe {
325 constraints::set_audio_constraint_value(
326 kind.get(),
327 AudioConstraintKind::AutoGainControl as i64,
328 DartValue::from(val),
329 )
330 .unwrap();
331 };
332 }
333 if let Some(aec) = from.echo_cancellation {
334 let (kind, val) = parse_bool_const(aec);
335
336 unsafe {
337 constraints::set_audio_constraint_value(
338 kind.get(),
339 AudioConstraintKind::EchoCancellation as i64,
340 DartValue::from(val),
341 )
342 .unwrap();
343 }
344 }
345 if let Some(hpf) = from.high_pass_filter {
346 let (kind, val) = parse_bool_const(hpf);
347
348 unsafe {
349 constraints::set_audio_constraint_value(
350 kind.get(),
351 AudioConstraintKind::HighPassFilter as i64,
352 DartValue::from(val),
353 )
354 .unwrap();
355 }
356 }
357 if let Some(ns) = from.noise_suppression {
358 let (kind, val) = parse_bool_const(ns);
359
360 unsafe {
361 constraints::set_audio_constraint_value(
362 kind.get(),
363 AudioConstraintKind::NoiseSuppression as i64,
364 DartValue::from(val),
365 )
366 .unwrap();
367 }
368 }
369 if let Some(nsl) = from.noise_suppression_level {
370 unsafe {
371 constraints::set_audio_constraint_value(
372 optional.get(),
373 AudioConstraintKind::NoiseSuppressionLevel as i64,
374 DartValue::from(nsl as i64),
375 )
376 .unwrap();
377 }
378 }
379
380 Self { optional, mandatory }
381 }
382}
383
384#[expect(clippy::fallible_impl_from, reason = "FFI error is unexpected")]
385impl From<DeviceVideoTrackConstraints> for MediaTrackConstraints {
386 fn from(from: DeviceVideoTrackConstraints) -> Self {
387 let optional = {
388 let video =
389 unsafe { constraints::new_video_constraints() }.unwrap();
390 unsafe { DartHandle::new(video) }
391 };
392 let mandatory = {
393 let video =
394 unsafe { constraints::new_video_constraints() }.unwrap();
395 unsafe { DartHandle::new(video) }
396 };
397
398 if let Some(device_id) = from.device_id {
399 unsafe {
400 set_constrain_string(
401 device_id,
402 VideoConstraintKind::DeviceId,
403 &optional,
404 &mandatory,
405 );
406 }
407 }
408 if let Some(facing_mode) = from.facing_mode {
409 match facing_mode {
410 ConstrainString::Exact(facing_mode) => unsafe {
411 constraints::set_video_constraint_value(
412 mandatory.get(),
413 VideoConstraintKind::FacingMode as i64,
414 DartValue::from(facing_mode as i64),
415 )
416 }
417 .unwrap(),
418 ConstrainString::Ideal(facing_mode) => unsafe {
419 constraints::set_video_constraint_value(
420 optional.get(),
421 VideoConstraintKind::FacingMode as i64,
422 DartValue::from(facing_mode as i64),
423 )
424 }
425 .unwrap(),
426 }
427 }
428 if let Some(width) = from.width {
429 unsafe {
430 set_video_constrain_u32(
431 width,
432 VideoConstraintKind::Width,
433 &optional,
434 &mandatory,
435 );
436 }
437 }
438 if let Some(height) = from.height {
439 unsafe {
440 set_video_constrain_u32(
441 height,
442 VideoConstraintKind::Height,
443 &optional,
444 &mandatory,
445 );
446 }
447 }
448
449 Self { optional, mandatory }
450 }
451}
452
453#[expect(clippy::fallible_impl_from, reason = "FFI error is unexpected")]
454impl From<DisplayVideoTrackConstraints> for MediaTrackConstraints {
455 fn from(from: DisplayVideoTrackConstraints) -> Self {
456 let optional = {
457 let video =
458 unsafe { constraints::new_video_constraints() }.unwrap();
459 unsafe { DartHandle::new(video) }
460 };
461 let mandatory = {
462 let video =
463 unsafe { constraints::new_video_constraints() }.unwrap();
464 unsafe { DartHandle::new(video) }
465 };
466
467 if let Some(device_id) = from.device_id {
468 unsafe {
469 set_constrain_string(
470 device_id,
471 VideoConstraintKind::DeviceId,
472 &optional,
473 &mandatory,
474 );
475 }
476 }
477 if let Some(width) = from.width {
478 unsafe {
479 set_video_constrain_u32(
480 width,
481 VideoConstraintKind::Width,
482 &optional,
483 &mandatory,
484 );
485 }
486 }
487 if let Some(height) = from.height {
488 unsafe {
489 set_video_constrain_u32(
490 height,
491 VideoConstraintKind::Height,
492 &optional,
493 &mandatory,
494 );
495 }
496 }
497 if let Some(frame_rate) = from.frame_rate {
498 unsafe {
499 set_video_constrain_u32(
500 frame_rate,
501 VideoConstraintKind::FrameRate,
502 &optional,
503 &mandatory,
504 );
505 }
506 }
507
508 Self { optional, mandatory }
509 }
510}
511
512unsafe fn set_constrain_string<T>(
515 constrain: ConstrainString<T>,
516 kind: VideoConstraintKind,
517 optional: &DartHandle,
518 mandatory: &DartHandle,
519) where
520 DartValue: From<T>,
521{
522 match constrain {
523 ConstrainString::Exact(val) => unsafe {
524 constraints::set_video_constraint_value(
525 mandatory.get(),
526 kind as i64,
527 DartValue::from(val),
528 )
529 }
530 .unwrap(),
531 ConstrainString::Ideal(val) => unsafe {
532 constraints::set_video_constraint_value(
533 optional.get(),
534 kind as i64,
535 DartValue::from(val),
536 )
537 }
538 .unwrap(),
539 }
540}
541
542unsafe fn set_video_constrain_u32(
545 constrain: ConstrainU32,
546 kind: VideoConstraintKind,
547 optional: &DartHandle,
548 mandatory: &DartHandle,
549) {
550 match constrain {
551 ConstrainU32::Ideal(val) => unsafe {
552 constraints::set_video_constraint_value(
553 optional.get(),
554 kind as i64,
555 DartValue::from(val),
556 )
557 }
558 .unwrap(),
559 ConstrainU32::Exact(val) => unsafe {
560 constraints::set_video_constraint_value(
561 mandatory.get(),
562 kind as i64,
563 DartValue::from(val),
564 )
565 }
566 .unwrap(),
567 ConstrainU32::Range(min, _) => unsafe {
568 constraints::set_video_constraint_value(
570 mandatory.get(),
571 kind as i64,
572 DartValue::from(min),
573 )
574 }
575 .unwrap(),
576 }
577}