1 use crate::{MediaTrackSetting, ResolvedMediaTrackConstraint};
2 
3 use super::FitnessDistance;
4 
5 /// An error indicating a rejected fitness distance computation,
6 /// likely caused by a mismatched yet required constraint.
7 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
8 pub struct SettingFitnessDistanceError {
9     /// The kind of the error (e.g. missing value, mismatching value, …).
10     pub kind: SettingFitnessDistanceErrorKind,
11     /// The required constraint value.
12     pub constraint: String,
13     /// The offending setting value.
14     pub setting: Option<String>,
15 }
16 
17 /// The kind of the error (e.g. missing value, mismatching value, …).
18 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
19 pub enum SettingFitnessDistanceErrorKind {
20     /// Settings value is missing.
21     Missing,
22     /// Settings value is a mismatch.
23     Mismatch,
24     /// Settings value is too small.
25     TooSmall,
26     /// Settings value is too large.
27     TooLarge,
28 }
29 
30 impl<'a> FitnessDistance<Option<&'a MediaTrackSetting>> for ResolvedMediaTrackConstraint {
31     type Error = SettingFitnessDistanceError;
32 
fitness_distance(&self, setting: Option<&'a MediaTrackSetting>) -> Result<f64, Self::Error>33     fn fitness_distance(&self, setting: Option<&'a MediaTrackSetting>) -> Result<f64, Self::Error> {
34         type Setting = MediaTrackSetting;
35         type Constraint = ResolvedMediaTrackConstraint;
36 
37         let setting = match setting {
38             Some(setting) => setting,
39             None => {
40                 return if self.is_required() {
41                     Err(Self::Error {
42                         kind: SettingFitnessDistanceErrorKind::Missing,
43                         constraint: format!("{}", self.to_required_only()),
44                         setting: None,
45                     })
46                 } else {
47                     Ok(1.0)
48                 }
49             }
50         };
51 
52         let result = match (self, setting) {
53             // Empty constraint:
54             (ResolvedMediaTrackConstraint::Empty(constraint), setting) => {
55                 constraint.fitness_distance(Some(setting))
56             }
57 
58             // Boolean constraint:
59             (Constraint::Bool(constraint), Setting::Bool(setting)) => {
60                 constraint.fitness_distance(Some(setting))
61             }
62             (Constraint::Bool(constraint), Setting::Integer(setting)) => {
63                 constraint.fitness_distance(Some(setting))
64             }
65             (Constraint::Bool(constraint), Setting::Float(setting)) => {
66                 constraint.fitness_distance(Some(setting))
67             }
68             (Constraint::Bool(constraint), Setting::String(setting)) => {
69                 constraint.fitness_distance(Some(setting))
70             }
71 
72             // Integer constraint:
73             (Constraint::IntegerRange(_constraint), Setting::Bool(_setting)) => Ok(0.0),
74             (Constraint::IntegerRange(constraint), Setting::Integer(setting)) => {
75                 constraint.fitness_distance(Some(setting))
76             }
77             (Constraint::IntegerRange(constraint), Setting::Float(setting)) => {
78                 constraint.fitness_distance(Some(setting))
79             }
80             (Constraint::IntegerRange(_constraint), Setting::String(_setting)) => Ok(0.0),
81 
82             // Float constraint:
83             (Constraint::FloatRange(_constraint), Setting::Bool(_setting)) => Ok(0.0),
84             (Constraint::FloatRange(constraint), Setting::Integer(setting)) => {
85                 constraint.fitness_distance(Some(setting))
86             }
87             (Constraint::FloatRange(constraint), Setting::Float(setting)) => {
88                 constraint.fitness_distance(Some(setting))
89             }
90             (Constraint::FloatRange(_constraint), Setting::String(_setting)) => Ok(0.0),
91 
92             // String constraint:
93             (Constraint::String(_constraint), Setting::Bool(_setting)) => Ok(0.0),
94             (Constraint::String(_constraint), Setting::Integer(_setting)) => Ok(0.0),
95             (Constraint::String(_constraint), Setting::Float(_setting)) => Ok(0.0),
96             (Constraint::String(constraint), Setting::String(setting)) => {
97                 constraint.fitness_distance(Some(setting))
98             }
99 
100             // String sequence constraint:
101             (Constraint::StringSequence(_constraint), Setting::Bool(_setting)) => Ok(0.0),
102             (Constraint::StringSequence(_constraint), Setting::Integer(_setting)) => Ok(0.0),
103             (Constraint::StringSequence(_constraint), Setting::Float(_setting)) => Ok(0.0),
104             (Constraint::StringSequence(constraint), Setting::String(setting)) => {
105                 constraint.fitness_distance(Some(setting))
106             }
107         };
108 
109         #[cfg(debug_assertions)]
110         if let Ok(fitness_distance) = result {
111             debug_assert!({ fitness_distance.is_finite() });
112         }
113 
114         result
115     }
116 }
117 
118 #[cfg(test)]
119 mod tests {
120     use crate::{constraint::EmptyConstraint, MediaTrackSetting, ResolvedMediaTrackConstraint};
121 
122     use super::*;
123 
124     #[test]
empty_constraint()125     fn empty_constraint() {
126         // As per step 1 of the `SelectSettings` algorithm from the W3C spec:
127         // <https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings>
128         //
129         // > Each constraint specifies one or more values (or a range of values) for its property.
130         // > A property MAY appear more than once in the list of 'advanced' ConstraintSets.
131         // > If an empty list has been given as the value for a constraint,
132         // > it MUST be interpreted as if the constraint were not specified
133         // > (in other words, an empty constraint == no constraint).
134         let constraint = ResolvedMediaTrackConstraint::Empty(EmptyConstraint {});
135 
136         let settings = [
137             MediaTrackSetting::Bool(true),
138             MediaTrackSetting::Integer(42),
139             MediaTrackSetting::Float(4.2),
140             MediaTrackSetting::String("string".to_owned()),
141         ];
142 
143         let expected = 0.0;
144 
145         for setting in settings {
146             let actual = constraint.fitness_distance(Some(&setting)).unwrap();
147 
148             assert_eq!(actual, expected);
149         }
150     }
151 
152     mod bool_constraint {
153         use crate::ResolvedValueConstraint;
154 
155         use super::*;
156 
157         #[test]
bool_setting()158         fn bool_setting() {
159             // As per step 8 of the `fitness distance` function from the W3C spec:
160             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
161             //
162             // > For all string, enum and boolean constraints
163             // > (e.g. deviceId, groupId, facingMode, resizeMode, echoCancellation),
164             // > the fitness distance is the result of the formula:
165             // >
166             // > ```
167             // > (actual == ideal) ? 0 : 1
168             // > ```
169 
170             let scenarios = [(false, false), (false, true), (true, false), (true, true)];
171 
172             for (constraint_value, setting_value) in scenarios {
173                 let constraint = ResolvedMediaTrackConstraint::Bool(ResolvedValueConstraint {
174                     exact: None,
175                     ideal: Some(constraint_value),
176                 });
177 
178                 let setting = MediaTrackSetting::Bool(setting_value);
179 
180                 let actual = constraint.fitness_distance(Some(&setting)).unwrap();
181 
182                 let expected = if constraint_value == setting_value {
183                     0.0
184                 } else {
185                     1.0
186                 };
187 
188                 assert_eq!(actual, expected);
189             }
190         }
191 
192         #[test]
non_bool_settings()193         fn non_bool_settings() {
194             // As per step 4 of the `fitness distance` function from the W3C spec:
195             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
196             //
197             // > If constraintValue is a boolean, but the constrainable property is not,
198             // > then the fitness distance is based on whether the settings dictionary's
199             // > constraintName member exists or not, from the formula:
200             // >
201             // > ```
202             // > (constraintValue == exists) ? 0 : 1
203             // > ```
204 
205             let settings = [
206                 MediaTrackSetting::Integer(42),
207                 MediaTrackSetting::Float(4.2),
208                 MediaTrackSetting::String("string".to_owned()),
209             ];
210 
211             let scenarios = [(false, false), (false, true), (true, false), (true, true)];
212 
213             for (constraint_value, setting_value) in scenarios {
214                 let constraint = ResolvedMediaTrackConstraint::Bool(ResolvedValueConstraint {
215                     exact: None,
216                     ideal: Some(constraint_value),
217                 });
218 
219                 for setting in settings.iter() {
220                     // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)`
221                     // once MSRV has passed 1.62.0:
222                     let setting = if setting_value { Some(setting) } else { None };
223                     let actual = constraint.fitness_distance(setting).unwrap();
224 
225                     let expected = if setting_value { 0.0 } else { 1.0 };
226 
227                     assert_eq!(actual, expected);
228                 }
229             }
230         }
231     }
232 
233     mod numeric_constraint {
234         use crate::ResolvedValueRangeConstraint;
235 
236         use super::*;
237 
238         #[test]
missing_settings()239         fn missing_settings() {
240             // As per step 5 of the `fitness distance` function from the W3C spec:
241             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
242             //
243             // > If the settings dictionary's constraintName member does not exist,
244             // > the fitness distance is 1.
245 
246             let constraints = [
247                 ResolvedMediaTrackConstraint::IntegerRange(ResolvedValueRangeConstraint {
248                     exact: None,
249                     ideal: Some(42),
250                     min: None,
251                     max: None,
252                 }),
253                 ResolvedMediaTrackConstraint::FloatRange(ResolvedValueRangeConstraint {
254                     exact: None,
255                     ideal: Some(42.0),
256                     min: None,
257                     max: None,
258                 }),
259             ];
260 
261             for constraint in constraints {
262                 let actual = constraint.fitness_distance(None).unwrap();
263 
264                 let expected = 1.0;
265 
266                 assert_eq!(actual, expected);
267             }
268         }
269 
270         #[test]
compatible_settings()271         fn compatible_settings() {
272             // As per step 7 of the `fitness distance` function from the W3C spec:
273             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
274             //
275             // > For all positive numeric constraints
276             // > (such as height, width, frameRate, aspectRatio, sampleRate and sampleSize),
277             // > the fitness distance is the result of the formula
278             // >
279             // > ```
280             // > (actual == ideal) ? 0 : |actual - ideal| / max(|actual|, |ideal|)
281             // > ```
282 
283             let settings = [
284                 MediaTrackSetting::Integer(21),
285                 MediaTrackSetting::Float(21.0),
286             ];
287 
288             let constraints = [
289                 ResolvedMediaTrackConstraint::IntegerRange(ResolvedValueRangeConstraint {
290                     exact: None,
291                     ideal: Some(42),
292                     min: None,
293                     max: None,
294                 }),
295                 ResolvedMediaTrackConstraint::FloatRange(ResolvedValueRangeConstraint {
296                     exact: None,
297                     ideal: Some(42.0),
298                     min: None,
299                     max: None,
300                 }),
301             ];
302 
303             for constraint in constraints {
304                 for setting in settings.iter() {
305                     let actual = constraint.fitness_distance(Some(setting)).unwrap();
306 
307                     let expected = 0.5;
308 
309                     assert_eq!(actual, expected);
310                 }
311             }
312         }
313 
314         #[test]
incompatible_settings()315         fn incompatible_settings() {
316             // As per step 3 of the `fitness distance` function from the W3C spec:
317             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
318             //
319             // > If the constraint does not apply for this type of object, the fitness distance is 0
320             // > (that is, the constraint does not influence the fitness distance).
321 
322             let settings = [
323                 MediaTrackSetting::Bool(true),
324                 MediaTrackSetting::String("string".to_owned()),
325             ];
326 
327             let constraints = [
328                 ResolvedMediaTrackConstraint::IntegerRange(ResolvedValueRangeConstraint {
329                     exact: None,
330                     ideal: Some(42),
331                     min: None,
332                     max: None,
333                 }),
334                 ResolvedMediaTrackConstraint::FloatRange(ResolvedValueRangeConstraint {
335                     exact: None,
336                     ideal: Some(42.0),
337                     min: None,
338                     max: None,
339                 }),
340             ];
341 
342             for constraint in constraints {
343                 for setting in settings.iter() {
344                     let actual = constraint.fitness_distance(Some(setting)).unwrap();
345 
346                     let expected = 0.0;
347 
348                     println!("constraint: {constraint:?}");
349                     println!("setting: {setting:?}");
350                     println!("actual: {actual:?}");
351                     println!("expected: {expected:?}");
352 
353                     assert_eq!(actual, expected);
354                 }
355             }
356         }
357     }
358 
359     mod string_constraint {
360         use crate::ResolvedValueConstraint;
361 
362         use super::*;
363 
364         #[test]
missing_settings()365         fn missing_settings() {
366             // As per step 5 of the `fitness distance` function from the W3C spec:
367             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
368             //
369             // > If the settings dictionary's constraintName member does not exist,
370             // > the fitness distance is 1.
371 
372             let constraint = ResolvedMediaTrackConstraint::String(ResolvedValueConstraint {
373                 exact: None,
374                 ideal: Some("constraint".to_owned()),
375             });
376 
377             let actual = constraint.fitness_distance(None).unwrap();
378 
379             let expected = 1.0;
380 
381             assert_eq!(actual, expected);
382         }
383 
384         #[test]
compatible_settings()385         fn compatible_settings() {
386             // As per step 8 of the `fitness distance` function from the W3C spec:
387             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
388             //
389             // > For all string, enum and boolean constraints
390             // > (e.g. deviceId, groupId, facingMode, resizeMode, echoCancellation),
391             // > the fitness distance is the result of the formula:
392             // >
393             // > ```
394             // > (actual == ideal) ? 0 : 1
395             // > ```
396 
397             let constraint = ResolvedMediaTrackConstraint::String(ResolvedValueConstraint {
398                 exact: None,
399                 ideal: Some("constraint".to_owned()),
400             });
401 
402             let settings = [MediaTrackSetting::String("setting".to_owned())];
403 
404             for setting in settings {
405                 let actual = constraint.fitness_distance(Some(&setting)).unwrap();
406 
407                 let expected = 1.0;
408 
409                 assert_eq!(actual, expected);
410             }
411         }
412 
413         #[test]
incompatible_settings()414         fn incompatible_settings() {
415             // As per step 3 of the `fitness distance` function from the W3C spec:
416             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
417             //
418             // > If the constraint does not apply for this type of object, the fitness distance is 0
419             // > (that is, the constraint does not influence the fitness distance).
420 
421             let constraint = ResolvedMediaTrackConstraint::String(ResolvedValueConstraint {
422                 exact: None,
423                 ideal: Some("string".to_owned()),
424             });
425 
426             let settings = [
427                 MediaTrackSetting::Bool(true),
428                 MediaTrackSetting::Integer(42),
429                 MediaTrackSetting::Float(4.2),
430             ];
431 
432             for setting in settings {
433                 let actual = constraint.fitness_distance(Some(&setting)).unwrap();
434 
435                 let expected = 0.0;
436 
437                 println!("constraint: {constraint:?}");
438                 println!("setting: {setting:?}");
439                 println!("actual: {actual:?}");
440                 println!("expected: {expected:?}");
441 
442                 assert_eq!(actual, expected);
443             }
444         }
445     }
446 
447     mod string_sequence_constraint {
448         use crate::ResolvedValueSequenceConstraint;
449 
450         use super::*;
451 
452         #[test]
missing_settings()453         fn missing_settings() {
454             // As per step 5 of the `fitness distance` function from the W3C spec:
455             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
456             //
457             // > If the settings dictionary's constraintName member does not exist,
458             // > the fitness distance is 1.
459 
460             let constraint =
461                 ResolvedMediaTrackConstraint::StringSequence(ResolvedValueSequenceConstraint {
462                     exact: None,
463                     ideal: Some(vec!["constraint".to_owned()]),
464                 });
465 
466             let actual = constraint.fitness_distance(None).unwrap();
467 
468             let expected = 1.0;
469 
470             assert_eq!(actual, expected);
471         }
472 
473         #[test]
compatible_settings()474         fn compatible_settings() {
475             // As per step 8 of the `fitness distance` function from the W3C spec:
476             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
477             //
478             // > For all string, enum and boolean constraints
479             // > (e.g. deviceId, groupId, facingMode, resizeMode, echoCancellation),
480             // > the fitness distance is the result of the formula:
481             // >
482             // > ```
483             // > (actual == ideal) ? 0 : 1
484             // > ```
485             //
486             // As well as the preliminary definition:
487             //
488             // > For string valued constraints, we define "==" below to be true if one of the
489             // > values in the sequence is exactly the same as the value being compared against.
490 
491             let constraint =
492                 ResolvedMediaTrackConstraint::StringSequence(ResolvedValueSequenceConstraint {
493                     exact: None,
494                     ideal: Some(vec!["constraint".to_owned()]),
495                 });
496 
497             let settings = [MediaTrackSetting::String("setting".to_owned())];
498 
499             for setting in settings {
500                 let actual = constraint.fitness_distance(Some(&setting)).unwrap();
501 
502                 let expected = 1.0;
503 
504                 assert_eq!(actual, expected);
505             }
506         }
507 
508         #[test]
incompatible_settings()509         fn incompatible_settings() {
510             // As per step 3 of the `fitness distance` function from the W3C spec:
511             // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance>
512             //
513             // > If the constraint does not apply for this type of object, the fitness distance is 0
514             // > (that is, the constraint does not influence the fitness distance).
515 
516             let constraint =
517                 ResolvedMediaTrackConstraint::StringSequence(ResolvedValueSequenceConstraint {
518                     exact: None,
519                     ideal: Some(vec!["constraint".to_owned()]),
520                 });
521 
522             let settings = [
523                 MediaTrackSetting::Bool(true),
524                 MediaTrackSetting::Integer(42),
525                 MediaTrackSetting::Float(4.2),
526             ];
527 
528             for setting in settings {
529                 let actual = constraint.fitness_distance(Some(&setting)).unwrap();
530 
531                 let expected = 0.0;
532 
533                 assert_eq!(actual, expected);
534             }
535         }
536     }
537 }
538