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