1 use std::collections::{HashMap, HashSet}; 2 3 use ordered_float::NotNan; 4 5 use crate::{ 6 algorithms::{fitness_distance::SettingFitnessDistanceError, FitnessDistance, SettingFitnessDistanceErrorKind}, 7 constraints::SanitizedAdvancedMediaTrackConstraints, 8 errors::OverconstrainedError, 9 BareOrMandatoryMediaTrackConstraints, MediaTrackSettings, MediaTrackSupportedConstraints, 10 SanitizedMandatoryMediaTrackConstraints, SanitizedMediaTrackConstraintSet, 11 SanitizedMediaTrackConstraints, 12 }; 13 14 pub trait SelectSettingsPolicy { 15 /// Selects a preferred candidate from a non-empty selection of optimal candidates. 16 /// 17 /// As specified in step 5 of the `SelectSettings` algorithm: 18 /// https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings 19 /// 20 /// > Select one settings dictionary from candidates, and return it as the result 21 /// > of the SelectSettings algorithm. The User Agent MUST use one with the 22 /// > smallest fitness distance, as calculated in step 3. 23 /// > If more than one settings dictionary have the smallest fitness distance, 24 /// > the User Agent chooses one of them based on system default property values 25 /// > and User Agent default property values. 26 fn select_candidate<'a, I>(&self, candidates: I) -> &'a MediaTrackSettings 27 where 28 I: Iterator<Item = &'a MediaTrackSettings>; 29 } 30 31 /// A naïve settings selection policy that just picks the first item of the iterator. 32 pub struct SelectFirstSettingsPolicy; 33 34 impl SelectFirstSettingsPolicy { 35 pub fn new() -> Self { 36 Self 37 } 38 } 39 40 impl Default for SelectFirstSettingsPolicy { 41 fn default() -> Self { 42 Self::new() 43 } 44 } 45 46 impl SelectSettingsPolicy for SelectFirstSettingsPolicy { 47 fn select_candidate<'a, I>(&self, mut candidates: I) -> &'a MediaTrackSettings 48 where 49 I: Iterator<Item = &'a MediaTrackSettings>, 50 { 51 // Safety: We know that `candidates is non-empty: 52 candidates 53 .next() 54 .expect("The `candidates` iterator should have produced at least one item.") 55 } 56 } 57 58 /// A settings selection policy that picks the item that's closest to the ideal. 59 pub struct SelectIdealSettingsPolicy { 60 sanitized_constraints: SanitizedMandatoryMediaTrackConstraints, 61 } 62 63 impl SelectIdealSettingsPolicy { 64 pub fn new( 65 ideal_settings: MediaTrackSettings, 66 supported_constraints: &MediaTrackSupportedConstraints, 67 ) -> Self { 68 let sanitized_constraints = BareOrMandatoryMediaTrackConstraints::from_iter( 69 ideal_settings 70 .into_iter() 71 .map(|(property, setting)| (property, setting.into())), 72 ) 73 .into_resolved() 74 .into_sanitized(supported_constraints); 75 76 Self { 77 sanitized_constraints, 78 } 79 } 80 } 81 82 impl SelectSettingsPolicy for SelectIdealSettingsPolicy { 83 fn select_candidate<'b, I>(&self, candidates: I) -> &'b MediaTrackSettings 84 where 85 I: Iterator<Item = &'b MediaTrackSettings>, 86 { 87 candidates 88 .min_by_key(|settings| { 89 let fitness_distance = self 90 .sanitized_constraints 91 .fitness_distance(settings) 92 .expect("Fitness distance should be positive."); 93 NotNan::new(fitness_distance).expect("Expected non-NaN fitness distance.") 94 }) 95 .expect("The `candidates` iterator should have produced at least one item.") 96 } 97 } 98 99 #[derive(Clone, Eq, PartialEq, Debug)] 100 pub enum SelectSettingsError { 101 Overconstrained(OverconstrainedError), 102 } 103 104 impl From<OverconstrainedError> for SelectSettingsError { 105 fn from(error: OverconstrainedError) -> Self { 106 Self::Overconstrained(error) 107 } 108 } 109 110 pub fn select_settings<'a, I, P>( 111 possible_settings: I, 112 constraints: &SanitizedMediaTrackConstraints, 113 policy: &P, 114 ) -> Result<&'a MediaTrackSettings, SelectSettingsError> 115 where 116 I: Iterator<Item = &'a MediaTrackSettings>, 117 P: SelectSettingsPolicy, 118 { 119 // As specified in step 1 of the `SelectSettings` algorithm: 120 // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings 121 // 122 // > Each constraint specifies one or more values (or a range of values) for its property. 123 // > A property MAY appear more than once in the list of 'advanced' ConstraintSets. 124 // > If an empty list has been given as the value for a constraint, 125 // > it MUST be interpreted as if the constraint were not specified 126 // > (in other words, an empty constraint == no constraint). 127 // > 128 // > Note that unknown properties are discarded by WebIDL, 129 // > which means that unknown/unsupported required constraints will silently disappear. 130 // > To avoid this being a surprise, application authors are expected to first use 131 // > the `getSupportedConstraints()` method […]. 132 133 // We can expect "sanitized" constraints to not contain empty constraints: 134 debug_assert!(constraints 135 .mandatory 136 .iter() 137 .all(|(_, constraint)| !constraint.is_empty())); 138 139 // Obtain candidates by filtering possible settings, dropping those with infinite fitness distances: 140 // 141 // This function call corresponds to steps 3 & 4 of the `SelectSettings` algorithm: 142 // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings 143 144 let mut candidates = select_feasible_candidates(possible_settings, &constraints.mandatory)?; 145 146 // As specified in step 5 of the `SelectSettings` algorithm: 147 // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings 148 // 149 // > Iterate over the 'advanced' ConstraintSets in newConstraints in the order in which they were specified. 150 // > 151 // > For each ConstraintSet: 152 // > 153 // > 1. compute the fitness distance between it and each settings dictionary in candidates, 154 // > treating bare values of properties as exact. 155 // > 156 // > 2. If the fitness distance is finite for one or more settings dictionaries in candidates, 157 // > keep those settings dictionaries in candidates, discarding others. 158 // > 159 // > If the fitness distance is infinite for all settings dictionaries in candidates, 160 // > ignore this ConstraintSet. 161 sieve_by_advanced_constraints(&mut candidates, &constraints.advanced); 162 163 // Sort candidates by their fitness distance, as obtained in an earlier step: 164 candidates.sort_by(|lhs, rhs| { 165 // Safety: We know (since we check for it in `fitness_distance()`) that `candidates` does 166 // not contain candidates with non-finite distances, so we can safely unwrap the comparison: 167 lhs.1 168 .partial_cmp(&rhs.1) 169 .expect("Fitness distances should be finite at this point.") 170 }); 171 172 // Safety: 173 // - We know that `select_candidates()` returns a non-empty vec of candidates. 174 // - We know that applying advanced constraint-sets will always keep at least one candidate. 175 // As such we can safely unwrap the first element of the vec: 176 177 let best_fitness_distance = candidates[0].1; 178 179 let best_candidates = candidates 180 .into_iter() 181 .take_while(|(_, fitness_distance)| *fitness_distance == best_fitness_distance) 182 .map(|(candidate, _)| candidate); 183 184 // Select one settings value from candidates: 185 // 186 // This function call corresponds to step 5 of the `SelectSettings` algorithm: 187 // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings 188 let best_candidate = policy.select_candidate(best_candidates); 189 190 Ok(best_candidate) 191 } 192 193 #[derive(Default)] 194 struct ConstraintFailureInfo { 195 failures: usize, 196 errors: HashSet<SettingFitnessDistanceError>, 197 } 198 199 fn select_feasible_candidates<'a, I>( 200 possible_settings: I, 201 basic_or_required_constraints: &SanitizedMediaTrackConstraintSet, 202 ) -> Result<Vec<(&'a MediaTrackSettings, f64)>, OverconstrainedError> 203 where 204 I: Iterator<Item = &'a MediaTrackSettings>, 205 { 206 // As specified in step 3 of the `SelectSettings` algorithm: 207 // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings 208 // 209 // > For every possible settings dictionary of copy compute its fitness distance, 210 // > treating bare values of properties as ideal values. Let candidates be the 211 // > set of settings dictionaries for which the fitness distance is finite. 212 213 let mut settings_len = 0; 214 let mut candidates: Vec<(&'a MediaTrackSettings, f64)> = vec![]; 215 let mut failed_constraints: HashMap<String, ConstraintFailureInfo> = Default::default(); 216 217 for settings in possible_settings { 218 settings_len += 1; 219 match basic_or_required_constraints.fitness_distance(settings) { 220 Ok(fitness_distance) => { 221 candidates.push((settings, fitness_distance)); 222 } 223 Err(error) => { 224 for (property, setting_error) in error.setting_errors { 225 let entry = failed_constraints 226 .entry(property) 227 .or_insert_with(Default::default); 228 entry.failures += 1; 229 entry.errors.insert(setting_error); 230 } 231 } 232 } 233 } 234 235 if candidates.is_empty() { 236 let error = select_failed_constraint(settings_len, failed_constraints); 237 return Err(error); 238 } 239 240 Ok(candidates) 241 } 242 243 /// Implements step 5 of the `SelectSettings` algorithm: 244 /// https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings 245 /// 246 /// # Note: 247 /// This may change the order of items in `feasible_candidates`. 248 /// In practice however this is not a problem as we have to sort 249 /// it by fitness-distance eventually anyway. 250 fn sieve_by_advanced_constraints<'a>( 251 feasible_candidates: &mut Vec<(&'a MediaTrackSettings, f64)>, 252 advanced_constraints: &SanitizedAdvancedMediaTrackConstraints, 253 ) { 254 // As specified in step 5 of the `SelectSettings` algorithm: 255 // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings 256 // 257 // > Iterate over the 'advanced' ConstraintSets in newConstraints in the order in which they were specified. 258 // > 259 // > For each ConstraintSet: 260 // > 261 // > 1. compute the fitness distance between it and each settings dictionary in candidates, 262 // > treating bare values of properties as exact. 263 // > 264 // > 2. If the fitness distance is finite for one or more settings dictionaries in candidates, 265 // > keep those settings dictionaries in candidates, discarding others. 266 // > 267 // > If the fitness distance is infinite for all settings dictionaries in candidates, 268 // > ignore this ConstraintSet. 269 270 for advanced_constraint_set in advanced_constraints.iter() { 271 let results: Vec<bool> = feasible_candidates 272 .iter() 273 .map( 274 |(candidate, _)| match advanced_constraint_set.fitness_distance(candidate) { 275 Ok(fitness_distance) => { 276 debug_assert!(fitness_distance.is_finite()); 277 true 278 } 279 Err(_) => false, 280 }, 281 ) 282 .collect(); 283 284 if !results.iter().any(|is_finite| *is_finite) { 285 continue; 286 } 287 288 for (index, is_match) in results.iter().enumerate() { 289 if !is_match { 290 feasible_candidates.swap_remove(index); 291 } 292 } 293 } 294 } 295 296 fn select_failed_constraint( 297 settings_len: usize, 298 failed_constraints: HashMap<String, ConstraintFailureInfo>, 299 ) -> OverconstrainedError { 300 let failed_constraint = failed_constraints 301 .into_iter() 302 .max_by_key(|(_, failure_info)| failure_info.failures); 303 304 let (constraint, failure_info) = 305 failed_constraint.expect("Empty candidates implies non-empty failed constraints"); 306 307 if failure_info.failures == settings_len { 308 generate_overconstrained_error(constraint, failure_info.errors) 309 } else { 310 OverconstrainedError::default() 311 } 312 } 313 314 fn generate_overconstrained_error( 315 constraint: String, 316 errors: HashSet<SettingFitnessDistanceError>, 317 ) -> OverconstrainedError { 318 struct Violation { 319 constraint: String, 320 settings: Vec<String>, 321 } 322 let mut violators_by_kind: HashMap<SettingFitnessDistanceErrorKind, Violation> = 323 HashMap::default(); 324 325 for error in errors { 326 let violation = violators_by_kind.entry(error.kind).or_insert(Violation { 327 constraint: error.constraint.clone(), 328 settings: vec![], 329 }); 330 assert_eq!(violation.constraint, error.constraint); 331 if let Some(setting) = error.setting { 332 violation.settings.push(setting.clone()); 333 } 334 } 335 336 let formatted_reasons: Vec<_> = violators_by_kind 337 .into_iter() 338 .map(|(kind, violation)| { 339 let kind_str = match kind { 340 SettingFitnessDistanceErrorKind::Missing => "missing", 341 SettingFitnessDistanceErrorKind::Mismatch => "a mismatch", 342 SettingFitnessDistanceErrorKind::TooSmall => "too small", 343 SettingFitnessDistanceErrorKind::TooLarge => "too large", 344 SettingFitnessDistanceErrorKind::Invalid => "invalid", 345 }; 346 347 let mut settings = violation.settings; 348 349 if settings.is_empty() { 350 return format!("{} (does not satisfy {})", kind_str, violation.constraint); 351 } 352 353 settings.sort(); 354 355 format!( 356 "{} ([{}] do not satisfy {})", 357 kind_str, 358 settings.join(", "), 359 violation.constraint 360 ) 361 }) 362 .collect(); 363 364 let formatted_reason = match &formatted_reasons[..] { 365 [] => unreachable!(), 366 [reason] => reason.clone(), 367 [reasons @ .., reason] => { 368 let reasons = reasons.join(", "); 369 format!("either {}, or {}", reasons, reason) 370 } 371 }; 372 let message = Some(format!("Setting was {}.", formatted_reason)); 373 OverconstrainedError { 374 constraint, 375 message, 376 } 377 } 378