1 use crate::{
2     algorithms::FitnessDistance, constraints::SanitizedAdvancedMediaTrackConstraints,
3     MediaTrackSettings,
4 };
5 
6 /// Returns the set of settings for which all non-overconstraining advanced constraints'
7 /// fitness distance is finite.
8 ///
9 /// Implements step 5 of the `SelectSettings` algorithm:
10 /// https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
11 ///
12 /// # Note:
13 /// This may change the order of items in `feasible_candidates`.
14 /// In practice however this is not a problem as we have to sort
15 /// it by fitness-distance eventually anyway.
16 pub(super) fn apply_advanced_constraints<'a>(
17     mut candidates: Vec<(&'a MediaTrackSettings, f64)>,
18     advanced_constraints: &SanitizedAdvancedMediaTrackConstraints,
19 ) -> Vec<(&'a MediaTrackSettings, f64)> {
20     // As specified in step 5 of the `SelectSettings` algorithm:
21     // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings
22     //
23     // > Iterate over the 'advanced' ConstraintSets in newConstraints in the order in which they were specified.
24     // >
25     // > For each ConstraintSet:
26     // >
27     // > 1. compute the fitness distance between it and each settings dictionary in candidates,
28     // >    treating bare values of properties as exact.
29     // >
30     // > 2. If the fitness distance is finite for one or more settings dictionaries in candidates,
31     // >    keep those settings dictionaries in candidates, discarding others.
32     // >
33     // >    If the fitness distance is infinite for all settings dictionaries in candidates,
34     // >    ignore this ConstraintSet.
35 
36     let mut selected_candidates = Vec::with_capacity(candidates.len());
37 
38     // Double-buffered sieving to avoid excessive vec allocations:
39     for advanced_constraint_set in advanced_constraints.iter() {
40         for (candidate, fitness_distance) in candidates.iter() {
41             if advanced_constraint_set.fitness_distance(candidate).is_ok() {
42                 selected_candidates.push((*candidate, *fitness_distance));
43             }
44         }
45 
46         if !selected_candidates.is_empty() {
47             candidates.clear();
48             std::mem::swap(&mut candidates, &mut selected_candidates);
49         }
50     }
51 
52     candidates
53 }
54 
55 #[cfg(test)]
56 mod tests {
57     use std::iter::FromIterator;
58 
59     use crate::{
60         property::all::name::*, MediaTrackSupportedConstraints, ResizeMode,
61         ResolvedAdvancedMediaTrackConstraints, ResolvedMediaTrackConstraintSet,
62         ResolvedValueConstraint, ResolvedValueRangeConstraint,
63     };
64 
65     use super::*;
66 
67     // Advanced constraint sets that doe not match any
68     // candidates should just get ignored:
69     #[test]
70     fn overconstrained() {
71         let supported_constraints =
72             MediaTrackSupportedConstraints::from_iter(vec![DEVICE_ID, HEIGHT, WIDTH, RESIZE_MODE]);
73 
74         let settings = vec![
75             MediaTrackSettings::from_iter([(DEVICE_ID, "foo".into())]),
76             MediaTrackSettings::from_iter([(DEVICE_ID, "bar".into())]),
77         ];
78 
79         let candidates: Vec<_> = settings
80             .iter()
81             // attach a dummy fitness function:
82             .map(|settings| (settings, 42.0))
83             .collect();
84 
85         let constraints = ResolvedAdvancedMediaTrackConstraints::from_iter([
86             ResolvedMediaTrackConstraintSet::from_iter([(
87                 DEVICE_ID,
88                 ResolvedValueConstraint::default()
89                     .exact("bazblee".to_owned())
90                     .into(),
91             )]),
92         ]);
93 
94         let sanitized_constraints = constraints.to_sanitized(&supported_constraints);
95 
96         let actual: Vec<_> = apply_advanced_constraints(candidates, &sanitized_constraints)
97             .into_iter()
98             // drop the dummy fitness distance:
99             .map(|(settings, _)| settings)
100             .collect();
101 
102         let expected: Vec<_> = settings.iter().collect();
103 
104         assert_eq!(actual, expected);
105     }
106 
107     #[test]
108     fn constrained() {
109         let supported_constraints =
110             MediaTrackSupportedConstraints::from_iter(vec![DEVICE_ID, HEIGHT, WIDTH, RESIZE_MODE]);
111 
112         let settings = vec![
113             MediaTrackSettings::from_iter([
114                 (DEVICE_ID, "480p".into()),
115                 (HEIGHT, 480.into()),
116                 (WIDTH, 720.into()),
117                 (RESIZE_MODE, ResizeMode::crop_and_scale().into()),
118             ]),
119             MediaTrackSettings::from_iter([
120                 (DEVICE_ID, "720p".into()),
121                 (HEIGHT, 720.into()),
122                 (WIDTH, 1280.into()),
123                 (RESIZE_MODE, ResizeMode::crop_and_scale().into()),
124             ]),
125             MediaTrackSettings::from_iter([
126                 (DEVICE_ID, "1080p".into()),
127                 (HEIGHT, 1080.into()),
128                 (WIDTH, 1920.into()),
129                 (RESIZE_MODE, ResizeMode::none().into()),
130             ]),
131             MediaTrackSettings::from_iter([
132                 (DEVICE_ID, "1440p".into()),
133                 (HEIGHT, 1440.into()),
134                 (WIDTH, 2560.into()),
135                 (RESIZE_MODE, ResizeMode::none().into()),
136             ]),
137             MediaTrackSettings::from_iter([
138                 (DEVICE_ID, "2160p".into()),
139                 (HEIGHT, 2160.into()),
140                 (WIDTH, 3840.into()),
141                 (RESIZE_MODE, ResizeMode::none().into()),
142             ]),
143         ];
144 
145         let candidates: Vec<_> = settings.iter().map(|settings| (settings, 42.0)).collect();
146 
147         let constraints = ResolvedAdvancedMediaTrackConstraints::from_iter([
148             // The first advanced constraint set of "exact 800p" does not match
149             // any candidate and should thus get ignored by the algorithm:
150             ResolvedMediaTrackConstraintSet::from_iter([(
151                 HEIGHT,
152                 ResolvedValueRangeConstraint::default().exact(800).into(),
153             )]),
154             // The second advanced constraint set of "no resizing" does match
155             // candidates and should thus be applied by the algorithm:
156             ResolvedMediaTrackConstraintSet::from_iter([(
157                 RESIZE_MODE,
158                 ResolvedValueConstraint::default()
159                     .exact(ResizeMode::none())
160                     .into(),
161             )]),
162             // The second advanced constraint set of "max 1440p" does match
163             // candidates and should thus be applied by the algorithm:
164             ResolvedMediaTrackConstraintSet::from_iter([(
165                 HEIGHT,
166                 ResolvedValueRangeConstraint::default().max(1440).into(),
167             )]),
168         ]);
169 
170         let sanitized_constraints = constraints.to_sanitized(&supported_constraints);
171 
172         let actual: Vec<_> = apply_advanced_constraints(candidates, &sanitized_constraints)
173             .into_iter()
174             // drop the dummy fitness distance:
175             .map(|(settings, _)| settings)
176             .collect();
177 
178         let expected = vec![&settings[2], &settings[3]];
179 
180         assert_eq!(actual, expected);
181     }
182 }
183