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