1 use std::collections::HashMap;
2 
3 use crate::{
4     algorithms::{
5         select_settings::{ConstraintFailureInfo, DeviceInformationExposureMode},
6         FitnessDistance,
7     },
8     errors::OverconstrainedError,
9     MediaTrackSettings, SanitizedMediaTrackConstraintSet,
10 };
11 
12 /// Returns the set of settings for which all mandatory constraints'
13 /// fitness distance is finite.
14 ///
15 /// Implements step 5 of the `SelectSettings` algorithm:
16 /// https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
17 pub(super) fn apply_mandatory_constraints<'a, I>(
18     candidates: I,
19     mandatory_constraints: &SanitizedMediaTrackConstraintSet,
20     exposure_mode: DeviceInformationExposureMode,
21 ) -> Result<Vec<(&'a MediaTrackSettings, f64)>, OverconstrainedError>
22 where
23     I: IntoIterator<Item = &'a MediaTrackSettings>,
24 {
25     // As specified in step 3 of the `SelectSettings` algorithm:
26     // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
27     //
28     // > For every possible settings dictionary of copy compute its fitness distance,
29     // > treating bare values of properties as ideal values. Let candidates be the
30     // > set of settings dictionaries for which the fitness distance is finite.
31 
32     let mut feasible_candidates: Vec<(&'a MediaTrackSettings, f64)> = vec![];
33     let mut failed_constraints: HashMap<String, ConstraintFailureInfo> = Default::default();
34 
35     for candidate in candidates {
36         match mandatory_constraints.fitness_distance(candidate) {
37             Ok(fitness_distance) => {
38                 debug_assert!(fitness_distance.is_finite());
39 
40                 feasible_candidates.push((candidate, fitness_distance));
41             }
42             Err(error) => {
43                 for (property, setting_error) in error.setting_errors {
44                     let entry = failed_constraints
45                         .entry(property)
46                         .or_insert_with(Default::default);
47                     entry.failures += 1;
48                     entry.errors.insert(setting_error);
49                 }
50             }
51         }
52     }
53 
54     if feasible_candidates.is_empty() {
55         return Err(match exposure_mode {
56             DeviceInformationExposureMode::Exposed => {
57                 OverconstrainedError::exposing_device_information(failed_constraints)
58             }
59             DeviceInformationExposureMode::Protected => OverconstrainedError::default(),
60         });
61     }
62 
63     Ok(feasible_candidates)
64 }
65 
66 #[cfg(test)]
67 mod tests {
68     use std::iter::FromIterator;
69 
70     use crate::{
71         property::all::name::*, MediaTrackSupportedConstraints, ResizeMode,
72         ResolvedMandatoryMediaTrackConstraints, ResolvedValueConstraint,
73         ResolvedValueRangeConstraint,
74     };
75 
76     use super::*;
77 
78     // Advanced constraint sets that do not match any candidates should just get ignored:
79     #[test]
80     fn overconstrained() {
81         let supported_constraints =
82             MediaTrackSupportedConstraints::from_iter(vec![DEVICE_ID, HEIGHT, WIDTH, RESIZE_MODE]);
83 
84         let settings = vec![
85             MediaTrackSettings::from_iter([(DEVICE_ID, "foo".into())]),
86             MediaTrackSettings::from_iter([(DEVICE_ID, "bar".into())]),
87         ];
88 
89         let candidates: Vec<_> = settings.iter().collect();
90 
91         let constraints = ResolvedMandatoryMediaTrackConstraints::from_iter([(
92             DEVICE_ID,
93             ResolvedValueConstraint::default()
94                 .exact("mismatched-device".to_owned())
95                 .into(),
96         )]);
97 
98         let sanitized_constraints = constraints.to_sanitized(&supported_constraints);
99 
100         let error = apply_mandatory_constraints(
101             candidates,
102             &sanitized_constraints,
103             DeviceInformationExposureMode::Exposed,
104         )
105         .unwrap_err();
106 
107         let constraint = &error.constraint;
108         let err_message = error.message.as_ref().expect("Error message.");
109 
110         assert_eq!(constraint, DEVICE_ID);
111         assert_eq!(
112             err_message,
113             "Setting was a mismatch ([\"bar\", \"foo\"] do not satisfy (x == \"mismatched-device\"))."
114         );
115     }
116 
117     #[test]
118     fn constrained() {
119         let supported_constraints =
120             MediaTrackSupportedConstraints::from_iter(vec![DEVICE_ID, HEIGHT, WIDTH, RESIZE_MODE]);
121 
122         let settings = vec![
123             MediaTrackSettings::from_iter([
124                 (DEVICE_ID, "480p".into()),
125                 (HEIGHT, 480.into()),
126                 (WIDTH, 720.into()),
127                 (RESIZE_MODE, ResizeMode::crop_and_scale().into()),
128             ]),
129             MediaTrackSettings::from_iter([
130                 (DEVICE_ID, "720p".into()),
131                 (HEIGHT, 720.into()),
132                 (WIDTH, 1280.into()),
133                 (RESIZE_MODE, ResizeMode::crop_and_scale().into()),
134             ]),
135             MediaTrackSettings::from_iter([
136                 (DEVICE_ID, "1080p".into()),
137                 (HEIGHT, 1080.into()),
138                 (WIDTH, 1920.into()),
139                 (RESIZE_MODE, ResizeMode::none().into()),
140             ]),
141             MediaTrackSettings::from_iter([
142                 (DEVICE_ID, "1440p".into()),
143                 (HEIGHT, 1440.into()),
144                 (WIDTH, 2560.into()),
145                 (RESIZE_MODE, ResizeMode::none().into()),
146             ]),
147             MediaTrackSettings::from_iter([
148                 (DEVICE_ID, "2160p".into()),
149                 (HEIGHT, 2160.into()),
150                 (WIDTH, 3840.into()),
151                 (RESIZE_MODE, ResizeMode::none().into()),
152             ]),
153         ];
154 
155         let candidates: Vec<_> = settings.iter().collect();
156 
157         let constraints = ResolvedMandatoryMediaTrackConstraints::from_iter([
158             (
159                 RESIZE_MODE,
160                 ResolvedValueConstraint::default()
161                     .exact(ResizeMode::none())
162                     .into(),
163             ),
164             (
165                 HEIGHT,
166                 ResolvedValueRangeConstraint::default().min(1000).into(),
167             ),
168             (
169                 WIDTH,
170                 ResolvedValueRangeConstraint::default().max(2000).into(),
171             ),
172         ]);
173 
174         let sanitized_constraints = constraints.to_sanitized(&supported_constraints);
175 
176         let actual = apply_mandatory_constraints(
177             candidates,
178             &sanitized_constraints,
179             DeviceInformationExposureMode::Exposed,
180         )
181         .unwrap();
182 
183         let expected = vec![(&settings[2], 0.0)];
184 
185         assert_eq!(actual, expected);
186     }
187 }
188