1 use std::iter::FromIterator;
2 
3 use lazy_static::lazy_static;
4 
5 use crate::{
6     algorithms::{select_settings_candidates, SelectSettingsError},
7     errors::OverconstrainedError,
8     property::all::{name::*, names as all_properties},
9     AdvancedMediaTrackConstraints, FacingMode, MandatoryMediaTrackConstraints,
10     MediaTrackConstraints, MediaTrackSettings, MediaTrackSupportedConstraints, ResizeMode,
11     ResolvedAdvancedMediaTrackConstraints, ResolvedMandatoryMediaTrackConstraints,
12     ResolvedMediaTrackConstraint, ResolvedMediaTrackConstraintSet, ResolvedMediaTrackConstraints,
13     ResolvedValueConstraint, ResolvedValueRangeConstraint, ResolvedValueSequenceConstraint,
14     SanitizedMediaTrackConstraints,
15 };
16 
17 use super::DeviceInformationExposureMode;
18 
19 lazy_static! {
20     static ref VIDEO_IDEAL: MediaTrackSettings = MediaTrackSettings::from_iter([
21         (&ASPECT_RATIO, 0.5625.into()),
22         (&FACING_MODE, FacingMode::user().into()),
23         (&FRAME_RATE, 60.0.into()),
24         (&WIDTH, 1920.into()),
25         (&HEIGHT, 1080.into()),
26         (&RESIZE_MODE, ResizeMode::none().into()),
27     ]);
28     static ref VIDEO_480P: MediaTrackSettings = MediaTrackSettings::from_iter([
29         (&DEVICE_ID, "480p".into()),
30         (&ASPECT_RATIO, 0.5625.into()),
31         (&FACING_MODE, FacingMode::user().into()),
32         (&FRAME_RATE, 240.into()),
33         (&WIDTH, 720.into()),
34         (&HEIGHT, 480.into()),
35         (&RESIZE_MODE, ResizeMode::crop_and_scale().into()),
36     ]);
37     static ref VIDEO_720P: MediaTrackSettings = MediaTrackSettings::from_iter([
38         (&DEVICE_ID, "720p".into()),
39         (&ASPECT_RATIO, 0.5625.into()),
40         (&FACING_MODE, FacingMode::user().into()),
41         (&FRAME_RATE, 120.into()),
42         (&WIDTH, 1280.into()),
43         (&HEIGHT, 720.into()),
44         (&RESIZE_MODE, ResizeMode::crop_and_scale().into()),
45     ]);
46     static ref VIDEO_1080P: MediaTrackSettings = MediaTrackSettings::from_iter([
47         (&DEVICE_ID, "1080p".into()),
48         (&ASPECT_RATIO, 0.5625.into()),
49         (&FACING_MODE, FacingMode::user().into()),
50         (&FRAME_RATE, 60.into()),
51         (&WIDTH, 1920.into()),
52         (&HEIGHT, 1080.into()),
53         (&RESIZE_MODE, ResizeMode::none().into()),
54     ]);
55     static ref VIDEO_1440P: MediaTrackSettings = MediaTrackSettings::from_iter([
56         (&DEVICE_ID, "1440p".into()),
57         (&ASPECT_RATIO, 0.5625.into()),
58         (&FACING_MODE, FacingMode::user().into()),
59         (&FRAME_RATE, 30.into()),
60         (&WIDTH, 2560.into()),
61         (&HEIGHT, 1440.into()),
62         (&RESIZE_MODE, ResizeMode::none().into()),
63     ]);
64     static ref VIDEO_2160P: MediaTrackSettings = MediaTrackSettings::from_iter([
65         (&DEVICE_ID, "2160p".into()),
66         (&ASPECT_RATIO, 0.5625.into()),
67         (&FACING_MODE, FacingMode::user().into()),
68         (&FRAME_RATE, 15.into()),
69         (&WIDTH, 3840.into()),
70         (&HEIGHT, 2160.into()),
71         (&RESIZE_MODE, ResizeMode::none().into()),
72     ]);
73 }
74 
75 fn default_possible_settings() -> Vec<MediaTrackSettings> {
76     vec![
77         VIDEO_480P.clone(),
78         VIDEO_720P.clone(),
79         VIDEO_1080P.clone(),
80         VIDEO_1440P.clone(),
81         VIDEO_2160P.clone(),
82     ]
83 }
84 
85 fn default_supported_constraints() -> MediaTrackSupportedConstraints {
86     MediaTrackSupportedConstraints::from_iter(all_properties().into_iter().cloned())
87 }
88 
89 fn test_overconstrained(
90     possible_settings: &[MediaTrackSettings],
91     mandatory_constraints: ResolvedMandatoryMediaTrackConstraints,
92     exposure_mode: DeviceInformationExposureMode,
93 ) -> OverconstrainedError {
94     let constraints = ResolvedMediaTrackConstraints {
95         mandatory: mandatory_constraints,
96         advanced: ResolvedAdvancedMediaTrackConstraints::default(),
97     }
98     .to_sanitized(&default_supported_constraints());
99 
100     let result = select_settings_candidates(possible_settings.iter(), &constraints, exposure_mode);
101 
102     let actual = result.err().unwrap();
103 
104     let SelectSettingsError::Overconstrained(overconstrained_error) = actual;
105 
106     overconstrained_error
107 }
108 
109 fn test_constrained(
110     possible_settings: &[MediaTrackSettings],
111     mandatory_constraints: ResolvedMandatoryMediaTrackConstraints,
112     advanced_constraints: ResolvedAdvancedMediaTrackConstraints,
113 ) -> Vec<&MediaTrackSettings> {
114     let constraints = ResolvedMediaTrackConstraints {
115         mandatory: mandatory_constraints,
116         advanced: advanced_constraints,
117     }
118     .to_sanitized(&default_supported_constraints());
119 
120     let result = select_settings_candidates(
121         possible_settings.iter(),
122         &constraints,
123         DeviceInformationExposureMode::Exposed,
124     );
125 
126     result.unwrap()
127 }
128 
129 mod unconstrained {
130     use super::*;
131 
132     fn default_constraints() -> MediaTrackConstraints {
133         MediaTrackConstraints {
134             mandatory: MandatoryMediaTrackConstraints::default(),
135             advanced: AdvancedMediaTrackConstraints::default(),
136         }
137     }
138 
139     fn default_resolved_constraints() -> ResolvedMediaTrackConstraints {
140         default_constraints().into_resolved()
141     }
142 
143     fn default_sanitized_constraints() -> SanitizedMediaTrackConstraints {
144         default_resolved_constraints().into_sanitized(&default_supported_constraints())
145     }
146 
147     #[test]
148     fn pass_through() {
149         let possible_settings = default_possible_settings();
150         let sanitized_constraints = default_sanitized_constraints();
151 
152         let actual = select_settings_candidates(
153             &possible_settings[..],
154             &sanitized_constraints,
155             DeviceInformationExposureMode::Exposed,
156         )
157         .unwrap();
158         let expected: Vec<_> = possible_settings.iter().collect();
159 
160         assert_eq!(actual, expected);
161     }
162 }
163 
164 mod overconstrained {
165     use crate::MediaTrackProperty;
166 
167     use super::*;
168 
169     #[test]
170     fn protected() {
171         let error = test_overconstrained(
172             &default_possible_settings(),
173             ResolvedMandatoryMediaTrackConstraints::from_iter([(
174                 GROUP_ID.clone(),
175                 ResolvedValueConstraint::default()
176                     .exact("missing-group".to_owned())
177                     .into(),
178             )]),
179             DeviceInformationExposureMode::Protected,
180         );
181 
182         assert_eq!(error.constraint, MediaTrackProperty::from(""));
183         assert_eq!(error.message, None);
184     }
185 
186     mod exposed {
187         use super::*;
188 
189         #[test]
190         fn missing() {
191             let error = test_overconstrained(
192                 &default_possible_settings(),
193                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
194                     GROUP_ID.clone(),
195                     ResolvedValueConstraint::default()
196                         .exact("missing-group".to_owned())
197                         .into(),
198                 )]),
199                 DeviceInformationExposureMode::Exposed,
200             );
201 
202             let constraint = &error.constraint;
203             let err_message = error.message.as_ref().expect("Error message.");
204 
205             assert_eq!(constraint, &GROUP_ID);
206             assert_eq!(
207                 err_message,
208                 "Setting was missing (does not satisfy (x == \"missing-group\"))."
209             );
210         }
211 
212         #[test]
213         fn mismatch() {
214             let error = test_overconstrained(
215                 &default_possible_settings(),
216                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
217                     DEVICE_ID.clone(),
218                     ResolvedValueConstraint::default()
219                         .exact("mismatched-device".to_owned())
220                         .into(),
221                 )]),
222                 DeviceInformationExposureMode::Exposed,
223             );
224 
225             let constraint = &error.constraint;
226             let err_message = error.message.as_ref().expect("Error message.");
227 
228             assert_eq!(constraint, &DEVICE_ID);
229             assert_eq!(
230             err_message,
231             "Setting was a mismatch ([\"1080p\", \"1440p\", \"2160p\", \"480p\", \"720p\"] do not satisfy (x == \"mismatched-device\"))."
232         );
233         }
234 
235         #[test]
236         fn too_small() {
237             let error = test_overconstrained(
238                 &default_possible_settings(),
239                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
240                     FRAME_RATE.clone(),
241                     ResolvedValueRangeConstraint::default().min(1000).into(),
242                 )]),
243                 DeviceInformationExposureMode::Exposed,
244             );
245 
246             let constraint = &error.constraint;
247             let err_message = error.message.as_ref().expect("Error message.");
248 
249             assert_eq!(constraint, &FRAME_RATE);
250             assert_eq!(
251                 err_message,
252                 "Setting was too small ([120, 15, 240, 30, 60] do not satisfy (1000 <= x))."
253             );
254         }
255 
256         #[test]
257         fn too_large() {
258             let error = test_overconstrained(
259                 &default_possible_settings(),
260                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
261                     FRAME_RATE.clone(),
262                     ResolvedValueRangeConstraint::default().max(10).into(),
263                 )]),
264                 DeviceInformationExposureMode::Exposed,
265             );
266 
267             let constraint = &error.constraint;
268             let err_message = error.message.as_ref().expect("Error message.");
269 
270             assert_eq!(constraint, &FRAME_RATE);
271             assert_eq!(
272                 err_message,
273                 "Setting was too large ([120, 15, 240, 30, 60] do not satisfy (x <= 10))."
274             );
275         }
276     }
277 }
278 
279 mod constrained {
280     use super::*;
281 
282     #[test]
283     fn specific_device_id() {
284         let possible_settings = default_possible_settings();
285 
286         for target_settings in possible_settings.iter() {
287             let setting = match target_settings.get(&DEVICE_ID) {
288                 Some(setting) => setting,
289                 None => continue,
290             };
291 
292             let actual = test_constrained(
293                 &possible_settings,
294                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
295                     DEVICE_ID.clone(),
296                     ResolvedMediaTrackConstraint::exact_from(setting.clone()),
297                 )]),
298                 ResolvedAdvancedMediaTrackConstraints::default(),
299             );
300 
301             let expected = vec![target_settings];
302 
303             assert_eq!(actual, expected);
304         }
305     }
306 
307     mod exact {
308         use super::*;
309 
310         #[test]
311         fn value() {
312             let possible_settings = vec![
313                 MediaTrackSettings::from_iter([
314                     (&DEVICE_ID, "a".into()),
315                     (&GROUP_ID, "group-0".into()),
316                 ]),
317                 MediaTrackSettings::from_iter([
318                     (&DEVICE_ID, "b".into()),
319                     (&GROUP_ID, "group-1".into()),
320                 ]),
321                 MediaTrackSettings::from_iter([
322                     (&DEVICE_ID, "c".into()),
323                     (&GROUP_ID, "group-2".into()),
324                 ]),
325             ];
326 
327             let actual = test_constrained(
328                 &possible_settings,
329                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
330                     &GROUP_ID,
331                     ResolvedValueConstraint::default()
332                         .exact("group-1".to_owned())
333                         .into(),
334                 )]),
335                 ResolvedAdvancedMediaTrackConstraints::default(),
336             );
337 
338             let expected = vec![&possible_settings[1]];
339 
340             assert_eq!(actual, expected);
341         }
342 
343         #[test]
344         fn value_range() {
345             let possible_settings = vec![
346                 MediaTrackSettings::from_iter([(&DEVICE_ID, "a".into()), (&FRAME_RATE, 15.into())]),
347                 MediaTrackSettings::from_iter([(&DEVICE_ID, "b".into()), (&FRAME_RATE, 30.into())]),
348                 MediaTrackSettings::from_iter([(&DEVICE_ID, "c".into()), (&FRAME_RATE, 60.into())]),
349             ];
350 
351             let actual = test_constrained(
352                 &possible_settings,
353                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
354                     &FRAME_RATE,
355                     ResolvedValueRangeConstraint::default().exact(30).into(),
356                 )]),
357                 ResolvedAdvancedMediaTrackConstraints::default(),
358             );
359 
360             let expected = vec![&possible_settings[1]];
361 
362             assert_eq!(actual, expected);
363         }
364 
365         #[test]
366         fn value_sequence() {
367             let possible_settings = vec![
368                 MediaTrackSettings::from_iter([
369                     (&DEVICE_ID, "a".into()),
370                     (&GROUP_ID, "group-0".into()),
371                 ]),
372                 MediaTrackSettings::from_iter([
373                     (&DEVICE_ID, "b".into()),
374                     (&GROUP_ID, "group-1".into()),
375                 ]),
376                 MediaTrackSettings::from_iter([
377                     (&DEVICE_ID, "c".into()),
378                     (&GROUP_ID, "group-2".into()),
379                 ]),
380             ];
381 
382             let actual = test_constrained(
383                 &possible_settings,
384                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
385                     &GROUP_ID,
386                     ResolvedValueSequenceConstraint::default()
387                         .exact(vec!["group-1".to_owned(), "group-3".to_owned()])
388                         .into(),
389                 )]),
390                 ResolvedAdvancedMediaTrackConstraints::default(),
391             );
392 
393             let expected = vec![&possible_settings[1]];
394 
395             assert_eq!(actual, expected);
396         }
397     }
398 
399     mod ideal {
400         use super::*;
401 
402         #[test]
403         fn value() {
404             let possible_settings = vec![
405                 MediaTrackSettings::from_iter([
406                     (&DEVICE_ID, "a".into()),
407                     (&GROUP_ID, "group-0".into()),
408                 ]),
409                 MediaTrackSettings::from_iter([
410                     (&DEVICE_ID, "b".into()),
411                     (&GROUP_ID, "group-1".into()),
412                 ]),
413                 MediaTrackSettings::from_iter([
414                     (&DEVICE_ID, "c".into()),
415                     (&GROUP_ID, "group-2".into()),
416                 ]),
417             ];
418 
419             let actual = test_constrained(
420                 &possible_settings,
421                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
422                     &GROUP_ID,
423                     ResolvedValueConstraint::default()
424                         .ideal("group-1".to_owned())
425                         .into(),
426                 )]),
427                 ResolvedAdvancedMediaTrackConstraints::default(),
428             );
429 
430             let expected = vec![&possible_settings[1]];
431 
432             assert_eq!(actual, expected);
433         }
434 
435         #[test]
436         fn value_range() {
437             let possible_settings = vec![
438                 MediaTrackSettings::from_iter([(&DEVICE_ID, "a".into()), (&FRAME_RATE, 15.into())]),
439                 MediaTrackSettings::from_iter([(&DEVICE_ID, "b".into()), (&FRAME_RATE, 30.into())]),
440                 MediaTrackSettings::from_iter([(&DEVICE_ID, "c".into()), (&FRAME_RATE, 60.into())]),
441             ];
442 
443             let actual = test_constrained(
444                 &possible_settings,
445                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
446                     &FRAME_RATE,
447                     ResolvedValueRangeConstraint::default().ideal(32).into(),
448                 )]),
449                 ResolvedAdvancedMediaTrackConstraints::default(),
450             );
451 
452             let expected = vec![&possible_settings[1]];
453 
454             assert_eq!(actual, expected);
455         }
456 
457         #[test]
458         fn value_sequence() {
459             let possible_settings = vec![
460                 MediaTrackSettings::from_iter([
461                     (&DEVICE_ID, "a".into()),
462                     (&GROUP_ID, "group-0".into()),
463                 ]),
464                 MediaTrackSettings::from_iter([
465                     (&DEVICE_ID, "b".into()),
466                     (&GROUP_ID, "group-1".into()),
467                 ]),
468                 MediaTrackSettings::from_iter([
469                     (&DEVICE_ID, "c".into()),
470                     (&GROUP_ID, "group-2".into()),
471                 ]),
472             ];
473 
474             let actual = test_constrained(
475                 &possible_settings,
476                 ResolvedMandatoryMediaTrackConstraints::from_iter([(
477                     &GROUP_ID,
478                     ResolvedValueSequenceConstraint::default()
479                         .ideal(vec!["group-1".to_owned(), "group-3".to_owned()])
480                         .into(),
481                 )]),
482                 ResolvedAdvancedMediaTrackConstraints::default(),
483             );
484 
485             let expected = vec![&possible_settings[1]];
486 
487             assert_eq!(actual, expected);
488         }
489     }
490 }
491 
492 // ```
493 //                        ┌
494 // mandatory constraints: ┤   ┄───────────────────────────────────────────┤
495 //                        └
496 //                        ┌
497 //  advanced constraints: ┤                    ├─┤         ├────────────────────────────┄
498 //                        └
499 //                        ┌
500 //     possible settings: ┤   ●─────────────●──────────────●──────────────●─────────────●
501 //                        └  480p          720p          1080p          1440p         2160p
502 //                                                         └───────┬──────┘
503 //     selected settings: ─────────────────────────────────────────┘
504 // ```
505 mod smoke {
506     use super::*;
507 
508     #[test]
509     fn native() {
510         let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![
511             &DEVICE_ID,
512             &HEIGHT,
513             &WIDTH,
514             &RESIZE_MODE,
515         ]);
516 
517         let possible_settings = vec![
518             MediaTrackSettings::from_iter([
519                 (&DEVICE_ID, "480p".into()),
520                 (&HEIGHT, 480.into()),
521                 (&WIDTH, 720.into()),
522                 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()),
523             ]),
524             MediaTrackSettings::from_iter([
525                 (&DEVICE_ID, "720p".into()),
526                 (&HEIGHT, 720.into()),
527                 (&WIDTH, 1280.into()),
528                 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()),
529             ]),
530             MediaTrackSettings::from_iter([
531                 (&DEVICE_ID, "1080p".into()),
532                 (&HEIGHT, 1080.into()),
533                 (&WIDTH, 1920.into()),
534                 (&RESIZE_MODE, ResizeMode::none().into()),
535             ]),
536             MediaTrackSettings::from_iter([
537                 (&DEVICE_ID, "1440p".into()),
538                 (&HEIGHT, 1440.into()),
539                 (&WIDTH, 2560.into()),
540                 (&RESIZE_MODE, ResizeMode::none().into()),
541             ]),
542             MediaTrackSettings::from_iter([
543                 (&DEVICE_ID, "2160p".into()),
544                 (&HEIGHT, 2160.into()),
545                 (&WIDTH, 3840.into()),
546                 (&RESIZE_MODE, ResizeMode::none().into()),
547             ]),
548         ];
549 
550         let constraints: ResolvedMediaTrackConstraints = ResolvedMediaTrackConstraints {
551             mandatory: ResolvedMandatoryMediaTrackConstraints::from_iter([
552                 (
553                     &WIDTH,
554                     ResolvedValueRangeConstraint::default().max(2560).into(),
555                 ),
556                 (
557                     &HEIGHT,
558                     ResolvedValueRangeConstraint::default().max(1440).into(),
559                 ),
560                 // Unsupported constraint, which should thus get ignored:
561                 (
562                     &FRAME_RATE,
563                     ResolvedValueRangeConstraint::default().exact(30.0).into(),
564                 ),
565             ]),
566             advanced: ResolvedAdvancedMediaTrackConstraints::from_iter([
567                 // The first advanced constraint set of "exact 800p" does not match
568                 // any candidate and should thus get ignored by the algorithm:
569                 ResolvedMediaTrackConstraintSet::from_iter([(
570                     &HEIGHT,
571                     ResolvedValueRangeConstraint::default().exact(800).into(),
572                 )]),
573                 // The second advanced constraint set of "no resizing" does match
574                 // candidates and should thus be applied by the algorithm:
575                 ResolvedMediaTrackConstraintSet::from_iter([(
576                     &RESIZE_MODE,
577                     ResolvedValueConstraint::default()
578                         .exact(ResizeMode::none())
579                         .into(),
580                 )]),
581             ]),
582         };
583 
584         let sanitized_constraints = constraints.to_sanitized(&supported_constraints);
585 
586         let actual = select_settings_candidates(
587             &possible_settings,
588             &sanitized_constraints,
589             DeviceInformationExposureMode::Exposed,
590         )
591         .unwrap();
592 
593         let expected = vec![&possible_settings[2], &possible_settings[3]];
594 
595         assert_eq!(actual, expected);
596     }
597 
598     #[cfg(feature = "serde")]
599     #[test]
600     fn json() {
601         let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![
602             &DEVICE_ID,
603             &HEIGHT,
604             &WIDTH,
605             &RESIZE_MODE,
606         ]);
607 
608         // Deserialize possible settings from JSON:
609         let possible_settings: Vec<MediaTrackSettings> = {
610             let json = serde_json::json!([
611                 { "deviceId": "480p", "width": 720, "height": 480, "resizeMode": "crop-and-scale" },
612                 { "deviceId": "720p", "width": 1280, "height": 720, "resizeMode": "crop-and-scale" },
613                 { "deviceId": "1080p", "width": 1920, "height": 1080, "resizeMode": "none" },
614                 { "deviceId": "1440p", "width": 2560, "height": 1440, "resizeMode": "none" },
615                 { "deviceId": "2160p", "width": 3840, "height": 2160, "resizeMode": "none" },
616             ]);
617             serde_json::from_value(json).unwrap()
618         };
619 
620         // Deserialize constraints from JSON:
621         let constraints: MediaTrackConstraints = {
622             let json = serde_json::json!({
623                 "width": {
624                     "max": 2560,
625                 },
626                 "height": {
627                     "max": 1440,
628                 },
629                 // Unsupported constraint, which should thus get ignored:
630                 "frameRate": {
631                     "exact": 30.0
632                 },
633                 "advanced": [
634                     // The first advanced constraint set of "exact 800p" does not match
635                     // any candidate and should thus get ignored by the algorithm:
636                     { "height": 800 },
637                     // The second advanced constraint set of "no resizing" does match
638                     // candidates and should thus be applied by the algorithm:
639                     { "resizeMode": "none" },
640                 ]
641             });
642             serde_json::from_value(json).unwrap()
643         };
644 
645         // Resolve bare values to proper constraints:
646         let resolved_constraints = constraints.into_resolved();
647 
648         // Sanitize constraints, removing empty and unsupported constraints:
649         let sanitized_constraints = resolved_constraints.into_sanitized(&supported_constraints);
650 
651         let actual = select_settings_candidates(
652             &possible_settings,
653             &sanitized_constraints,
654             DeviceInformationExposureMode::Exposed,
655         )
656         .unwrap();
657 
658         let expected = vec![&possible_settings[2], &possible_settings[3]];
659 
660         assert_eq!(actual, expected);
661     }
662 }
663