1 use std::collections::HashMap;
2 
3 use crate::{
4     algorithms::{
5         select_settings::{ConstraintFailureInfo, DeviceInformationExposureMode},
6         FitnessDistance,
7     },
8     errors::OverconstrainedError,
9     MediaTrackProperty, 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>
apply_mandatory_constraints<'a, I>( candidates: I, mandatory_constraints: &SanitizedMediaTrackConstraintSet, exposure_mode: DeviceInformationExposureMode, ) -> Result<Vec<(&'a MediaTrackSettings, f64)>, OverconstrainedError> where I: IntoIterator<Item = &'a MediaTrackSettings>,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<MediaTrackProperty, ConstraintFailureInfo> =
34         Default::default();
35 
36     for candidate in candidates {
37         match mandatory_constraints.fitness_distance(candidate) {
38             Ok(fitness_distance) => {
39                 debug_assert!(fitness_distance.is_finite());
40 
41                 feasible_candidates.push((candidate, fitness_distance));
42             }
43             Err(error) => {
44                 for (property, setting_error) in error.setting_errors {
45                     let entry = failed_constraints
46                         .entry(property)
47                         .or_insert_with(Default::default);
48                     entry.failures += 1;
49                     entry.errors.insert(setting_error);
50                 }
51             }
52         }
53     }
54 
55     if feasible_candidates.is_empty() {
56         return Err(match exposure_mode {
57             DeviceInformationExposureMode::Exposed => {
58                 OverconstrainedError::exposing_device_information(failed_constraints)
59             }
60             DeviceInformationExposureMode::Protected => OverconstrainedError::default(),
61         });
62     }
63 
64     Ok(feasible_candidates)
65 }
66 
67 #[cfg(test)]
68 mod tests {
69     use std::iter::FromIterator;
70 
71     use crate::{
72         property::all::name::*, MediaTrackSupportedConstraints, ResizeMode,
73         ResolvedMandatoryMediaTrackConstraints, ResolvedValueConstraint,
74         ResolvedValueRangeConstraint,
75     };
76 
77     use super::*;
78 
79     // Advanced constraint sets that do not match any candidates should just get ignored:
80     #[test]
overconstrained()81     fn overconstrained() {
82         let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![
83             &DEVICE_ID,
84             &HEIGHT,
85             &WIDTH,
86             &RESIZE_MODE,
87         ]);
88 
89         let settings = vec![
90             MediaTrackSettings::from_iter([(&DEVICE_ID, "foo".into())]),
91             MediaTrackSettings::from_iter([(&DEVICE_ID, "bar".into())]),
92         ];
93 
94         let candidates: Vec<_> = settings.iter().collect();
95 
96         let constraints = ResolvedMandatoryMediaTrackConstraints::from_iter([(
97             &DEVICE_ID,
98             ResolvedValueConstraint::default()
99                 .exact("mismatched-device".to_owned())
100                 .into(),
101         )]);
102 
103         let sanitized_constraints = constraints.to_sanitized(&supported_constraints);
104 
105         // Exposed exposure mode:
106 
107         let error = apply_mandatory_constraints(
108             candidates.clone(),
109             &sanitized_constraints,
110             DeviceInformationExposureMode::Exposed,
111         )
112         .unwrap_err();
113 
114         let constraint = &error.constraint;
115         let err_message = error.message.as_ref().expect("Error message.");
116 
117         assert_eq!(constraint, &DEVICE_ID);
118         assert_eq!(
119             err_message,
120             "Setting was a mismatch ([\"bar\", \"foo\"] do not satisfy (x == \"mismatched-device\"))."
121         );
122 
123         // Protected exposure mode:
124 
125         let error = apply_mandatory_constraints(
126             candidates,
127             &sanitized_constraints,
128             DeviceInformationExposureMode::Protected,
129         )
130         .unwrap_err();
131 
132         let constraint = &error.constraint;
133         let err_message = error.message;
134 
135         assert_eq!(
136             constraint,
137             &MediaTrackProperty::from(""),
138             "Constraint should not have been exposed"
139         );
140         assert!(
141             err_message.is_none(),
142             "Error message should not have been exposed"
143         );
144     }
145 
146     #[test]
constrained()147     fn constrained() {
148         let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![
149             &DEVICE_ID,
150             &HEIGHT,
151             &WIDTH,
152             &RESIZE_MODE,
153         ]);
154 
155         let settings = vec![
156             MediaTrackSettings::from_iter([
157                 (&DEVICE_ID, "480p".into()),
158                 (&HEIGHT, 480.into()),
159                 (&WIDTH, 720.into()),
160                 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()),
161             ]),
162             MediaTrackSettings::from_iter([
163                 (&DEVICE_ID, "720p".into()),
164                 (&HEIGHT, 720.into()),
165                 (&WIDTH, 1280.into()),
166                 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()),
167             ]),
168             MediaTrackSettings::from_iter([
169                 (&DEVICE_ID, "1080p".into()),
170                 (&HEIGHT, 1080.into()),
171                 (&WIDTH, 1920.into()),
172                 (&RESIZE_MODE, ResizeMode::none().into()),
173             ]),
174             MediaTrackSettings::from_iter([
175                 (&DEVICE_ID, "1440p".into()),
176                 (&HEIGHT, 1440.into()),
177                 (&WIDTH, 2560.into()),
178                 (&RESIZE_MODE, ResizeMode::none().into()),
179             ]),
180             MediaTrackSettings::from_iter([
181                 (&DEVICE_ID, "2160p".into()),
182                 (&HEIGHT, 2160.into()),
183                 (&WIDTH, 3840.into()),
184                 (&RESIZE_MODE, ResizeMode::none().into()),
185             ]),
186         ];
187 
188         let candidates: Vec<_> = settings.iter().collect();
189 
190         let constraints = ResolvedMandatoryMediaTrackConstraints::from_iter([
191             (
192                 &RESIZE_MODE,
193                 ResolvedValueConstraint::default()
194                     .exact(ResizeMode::none())
195                     .into(),
196             ),
197             (
198                 &HEIGHT,
199                 ResolvedValueRangeConstraint::default().min(1000).into(),
200             ),
201             (
202                 &WIDTH,
203                 ResolvedValueRangeConstraint::default().max(2000).into(),
204             ),
205         ]);
206 
207         let sanitized_constraints = constraints.to_sanitized(&supported_constraints);
208 
209         let actual = apply_mandatory_constraints(
210             candidates,
211             &sanitized_constraints,
212             DeviceInformationExposureMode::Exposed,
213         )
214         .unwrap();
215 
216         let expected = vec![(&settings[2], 0.0)];
217 
218         assert_eq!(actual, expected);
219     }
220 }
221