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