medea_jason/platform/dart/
constraints.rs

1//! Representations of [MediaTrackConstraints][0] and
2//! [MediaStreamConstraints][1].
3//!
4//! [0]: https://w3.org/TR/mediacapture-streams#media-track-constraints
5//! [1]: https://w3.org/TR/mediacapture-streams#mediastreamconstraints
6
7use 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        /// Initializes new empty [MediaStreamConstraints][0].
29        ///
30        /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamconstraints
31        pub fn init_device_constraints() -> Result<Dart_Handle, Error>;
32
33        /// Initializes new empty [MediaStreamConstraints][0] for display.
34        ///
35        /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamconstraints
36        pub fn init_display_constraints() -> Result<Dart_Handle, Error>;
37
38        /// Initializes a new empty [MediaStreamConstraints.video][0].
39        ///
40        /// [0]: https://tinyurl.com/3yvnbb9e
41        pub fn new_video_constraints() -> Result<Dart_Handle, Error>;
42
43        /// Initializes a new empty [MediaStreamConstraints.audio][0].
44        ///
45        /// [0]: https://tinyurl.com/5bmrr4w5
46        pub fn new_audio_constraints() -> Result<Dart_Handle, Error>;
47
48        /// Specifies the provided setting of a
49        /// [MediaStreamConstraints.video][0] (for example `facingMode`).
50        ///
51        /// [0]: https://tinyurl.com/3yvnbb9e
52        pub fn set_video_constraint_value(
53            constraints: Dart_Handle,
54            kind: i64,
55            value: DartValue,
56        ) -> Result<(), Error>;
57
58        /// Specifies the provided setting of a
59        /// [MediaStreamConstraints.audio][0] (for example `deviceId`).
60        ///
61        /// [0]: https://tinyurl.com/5bmrr4w5
62        pub fn set_audio_constraint_value(
63            constraints: Dart_Handle,
64            kind: i64,
65            value: DartValue,
66        ) -> Result<(), Error>;
67
68        /// Specifies the provided nature and settings of a `video`
69        /// [MediaStreamTrack][1] to the given [MediaStreamConstraints][0].
70        ///
71        /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamconstraints
72        /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack
73        pub fn set_video_constraint(
74            constraints: Dart_Handle,
75            ty: i64,
76            video: Dart_Handle,
77        ) -> Result<(), Error>;
78
79        /// Specifies the provided nature and settings of a display `video`
80        /// [MediaStreamTrack][1] to the given [MediaStreamConstraints][0].
81        ///
82        /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamconstraints
83        /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack
84        pub fn set_display_video_constraint(
85            constraints: Dart_Handle,
86            ty: i64,
87            video: Dart_Handle,
88        ) -> Result<(), Error>;
89
90        /// Specifies the provided nature and settings of an `audio`
91        /// [MediaStreamTrack][1] to the given [MediaStreamConstraints][0].
92        ///
93        /// [0]: https://w3.org/TR/mediacapture-streams#mediastreamconstraints
94        /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack
95        pub fn set_audio_constraint(
96            constraints: Dart_Handle,
97            ty: i64,
98            audio: Dart_Handle,
99        ) -> Result<(), Error>;
100    }
101}
102
103/// Kind of a [MediaStreamConstraints.video][0] setting.
104///
105/// [0]: https://tinyurl.com/3yvnbb9e
106enum VideoConstraintKind {
107    FacingMode = 0,
108    DeviceId = 1,
109    Width = 2,
110    Height = 3,
111    FrameRate = 4,
112}
113
114/// Kind of a [MediaStreamConstraints.audio][0] setting.
115///
116/// [0]: https://tinyurl.com/5bmrr4w5
117enum AudioConstraintKind {
118    DeviceId = 0,
119    AutoGainControl = 1,
120    NoiseSuppression = 2,
121    NoiseSuppressionLevel = 3,
122    HighPassFilter = 4,
123    EchoCancellation = 5,
124}
125
126/// Indicator of necessity of a [MediaStreamConstraints] setting.
127///
128/// [0]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints
129enum ConstraintType {
130    /// Not necessary, so if the device doesn't fit to the provided constraint,
131    /// it still can be used.
132    Optional = 0,
133
134    /// Necessary, so if the device doesn't fit to the provided constraint, it
135    /// can't be used.
136    Mandatory = 1,
137}
138
139/// Dart side representation of [MediaTrackConstraints][0].
140///
141/// [0]: https://w3.org/TR/mediacapture-streams#media-track-constraints
142#[derive(Debug)]
143pub struct MediaTrackConstraints {
144    /// Optional setting, so if the device doesn't fit to the provided
145    /// constraint, it still can be used.
146    optional: DartHandle,
147
148    /// Necessary setting, so if the device doesn't fit to the provided
149    /// constraint, it can't be used.
150    mandatory: DartHandle,
151}
152
153/// Dart side representation of [MediaStreamConstraints][0].
154///
155/// [0]: https://w3.org/TR/mediacapture-streams#dom-mediastreamconstraints
156#[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    /// Creates new empty [`MediaStreamConstraints`].
173    #[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    /// Specifies the provided nature and settings of an `audio`
181    /// [MediaStreamTrack][1] to these [`MediaStreamConstraints`].
182    ///
183    /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack
184    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    /// Specifies the provided nature and settings of a `video`
205    /// [MediaStreamTrack][1] to these [`MediaStreamConstraints`].
206    ///
207    /// [1]: https://w3.org/TR/mediacapture-streams/#mediastreamtrack
208    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/// Dart side representation of [DisplayMediaStreamConstraints][0].
230///
231/// [0]: https://w3.org/TR/screen-capture#dom-displaymediastreamconstraints
232#[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    /// Creates new empty [`DisplayMediaStreamConstraints`] .
249    #[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    /// Specifies the provided nature and settings of a `video`
257    /// [MediaStreamTrack][1] to these [`DisplayMediaStreamConstraints`].
258    ///
259    /// [1]: https://w3.org/TR/mediacapture-streams#mediastreamtrack
260    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
512/// Applies the specified [`ConstrainString`] to the provided  `optional` and
513/// `mandatory` [`DartHandle`]s representing the Dart side constraints.
514unsafe 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
542/// Applies the specified [`ConstrainU32`] to the provided  `optional` and
543/// `mandatory` [`DartHandle`]s representing the Dart side constraints.
544unsafe 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            // TODO: Implement range constraints in `medea_flutter_webrtc`.
569            constraints::set_video_constraint_value(
570                mandatory.get(),
571                kind as i64,
572                DartValue::from(min),
573            )
574        }
575        .unwrap(),
576    }
577}