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.
apply_advanced_constraints<'a>( mut candidates: Vec<(&'a MediaTrackSettings, f64)>, advanced_constraints: &SanitizedAdvancedMediaTrackConstraints, ) -> Vec<(&'a MediaTrackSettings, f64)>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]
overconstrained()70     fn overconstrained() {
71         let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![
72             &DEVICE_ID,
73             &HEIGHT,
74             &WIDTH,
75             &RESIZE_MODE,
76         ]);
77 
78         let settings = vec![
79             MediaTrackSettings::from_iter([(&DEVICE_ID, "foo".into())]),
80             MediaTrackSettings::from_iter([(&DEVICE_ID, "bar".into())]),
81         ];
82 
83         let candidates: Vec<_> = settings
84             .iter()
85             // attach a dummy fitness function:
86             .map(|settings| (settings, 42.0))
87             .collect();
88 
89         let constraints = ResolvedAdvancedMediaTrackConstraints::from_iter([
90             ResolvedMediaTrackConstraintSet::from_iter([(
91                 &DEVICE_ID,
92                 ResolvedValueConstraint::default()
93                     .exact("bazblee".to_owned())
94                     .into(),
95             )]),
96         ]);
97 
98         let sanitized_constraints = constraints.to_sanitized(&supported_constraints);
99 
100         let actual: Vec<_> = apply_advanced_constraints(candidates, &sanitized_constraints)
101             .into_iter()
102             // drop the dummy fitness distance:
103             .map(|(settings, _)| settings)
104             .collect();
105 
106         let expected: Vec<_> = settings.iter().collect();
107 
108         assert_eq!(actual, expected);
109     }
110 
111     #[test]
constrained()112     fn constrained() {
113         let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![
114             &DEVICE_ID,
115             &HEIGHT,
116             &WIDTH,
117             &RESIZE_MODE,
118         ]);
119 
120         let settings = vec![
121             MediaTrackSettings::from_iter([
122                 (&DEVICE_ID, "480p".into()),
123                 (&HEIGHT, 480.into()),
124                 (&WIDTH, 720.into()),
125                 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()),
126             ]),
127             MediaTrackSettings::from_iter([
128                 (&DEVICE_ID, "720p".into()),
129                 (&HEIGHT, 720.into()),
130                 (&WIDTH, 1280.into()),
131                 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()),
132             ]),
133             MediaTrackSettings::from_iter([
134                 (&DEVICE_ID, "1080p".into()),
135                 (&HEIGHT, 1080.into()),
136                 (&WIDTH, 1920.into()),
137                 (&RESIZE_MODE, ResizeMode::none().into()),
138             ]),
139             MediaTrackSettings::from_iter([
140                 (&DEVICE_ID, "1440p".into()),
141                 (&HEIGHT, 1440.into()),
142                 (&WIDTH, 2560.into()),
143                 (&RESIZE_MODE, ResizeMode::none().into()),
144             ]),
145             MediaTrackSettings::from_iter([
146                 (&DEVICE_ID, "2160p".into()),
147                 (&HEIGHT, 2160.into()),
148                 (&WIDTH, 3840.into()),
149                 (&RESIZE_MODE, ResizeMode::none().into()),
150             ]),
151         ];
152 
153         let candidates: Vec<_> = settings.iter().map(|settings| (settings, 42.0)).collect();
154 
155         let constraints = ResolvedAdvancedMediaTrackConstraints::from_iter([
156             // The first advanced constraint set of "exact 800p" does not match
157             // any candidate and should thus get ignored by the algorithm:
158             ResolvedMediaTrackConstraintSet::from_iter([(
159                 &HEIGHT,
160                 ResolvedValueRangeConstraint::default().exact(800).into(),
161             )]),
162             // The second advanced constraint set of "no resizing" does match
163             // candidates and should thus be applied by the algorithm:
164             ResolvedMediaTrackConstraintSet::from_iter([(
165                 &RESIZE_MODE,
166                 ResolvedValueConstraint::default()
167                     .exact(ResizeMode::none())
168                     .into(),
169             )]),
170             // The second advanced constraint set of "max 1440p" does match
171             // candidates and should thus be applied by the algorithm:
172             ResolvedMediaTrackConstraintSet::from_iter([(
173                 &HEIGHT,
174                 ResolvedValueRangeConstraint::default().max(1440).into(),
175             )]),
176         ]);
177 
178         let sanitized_constraints = constraints.to_sanitized(&supported_constraints);
179 
180         let actual: Vec<_> = apply_advanced_constraints(candidates, &sanitized_constraints)
181             .into_iter()
182             // drop the dummy fitness distance:
183             .map(|(settings, _)| settings)
184             .collect();
185 
186         let expected = vec![&settings[2], &settings[3]];
187 
188         assert_eq!(actual, expected);
189     }
190 }
191