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