1 use crate::{MediaTrackSetting, ResolvedMediaTrackConstraint}; 2 3 use super::FitnessDistance; 4 5 /// An error indicating a rejected fitness distance computation, 6 /// likely caused by a mismatched yet required constraint. 7 #[derive(Debug, Clone, Eq, PartialEq, Hash)] 8 pub struct SettingFitnessDistanceError { 9 /// The kind of the error (e.g. missing value, mismatching value, …). 10 pub kind: SettingFitnessDistanceErrorKind, 11 /// The required constraint value. 12 pub constraint: String, 13 /// The offending setting value. 14 pub setting: Option<String>, 15 } 16 17 /// The kind of the error (e.g. missing value, mismatching value, …). 18 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 19 pub enum SettingFitnessDistanceErrorKind { 20 /// Settings value is missing. 21 Missing, 22 /// Settings value is a mismatch. 23 Mismatch, 24 /// Settings value is too small. 25 TooSmall, 26 /// Settings value is too large. 27 TooLarge, 28 } 29 30 impl<'a> FitnessDistance<Option<&'a MediaTrackSetting>> for ResolvedMediaTrackConstraint { 31 type Error = SettingFitnessDistanceError; 32 fitness_distance(&self, setting: Option<&'a MediaTrackSetting>) -> Result<f64, Self::Error>33 fn fitness_distance(&self, setting: Option<&'a MediaTrackSetting>) -> Result<f64, Self::Error> { 34 type Setting = MediaTrackSetting; 35 type Constraint = ResolvedMediaTrackConstraint; 36 37 let setting = match setting { 38 Some(setting) => setting, 39 None => { 40 return if self.is_required() { 41 Err(Self::Error { 42 kind: SettingFitnessDistanceErrorKind::Missing, 43 constraint: format!("{}", self.to_required_only()), 44 setting: None, 45 }) 46 } else { 47 Ok(1.0) 48 } 49 } 50 }; 51 52 let result = match (self, setting) { 53 // Empty constraint: 54 (ResolvedMediaTrackConstraint::Empty(constraint), setting) => { 55 constraint.fitness_distance(Some(setting)) 56 } 57 58 // Boolean constraint: 59 (Constraint::Bool(constraint), Setting::Bool(setting)) => { 60 constraint.fitness_distance(Some(setting)) 61 } 62 (Constraint::Bool(constraint), Setting::Integer(setting)) => { 63 constraint.fitness_distance(Some(setting)) 64 } 65 (Constraint::Bool(constraint), Setting::Float(setting)) => { 66 constraint.fitness_distance(Some(setting)) 67 } 68 (Constraint::Bool(constraint), Setting::String(setting)) => { 69 constraint.fitness_distance(Some(setting)) 70 } 71 72 // Integer constraint: 73 (Constraint::IntegerRange(_constraint), Setting::Bool(_setting)) => Ok(0.0), 74 (Constraint::IntegerRange(constraint), Setting::Integer(setting)) => { 75 constraint.fitness_distance(Some(setting)) 76 } 77 (Constraint::IntegerRange(constraint), Setting::Float(setting)) => { 78 constraint.fitness_distance(Some(setting)) 79 } 80 (Constraint::IntegerRange(_constraint), Setting::String(_setting)) => Ok(0.0), 81 82 // Float constraint: 83 (Constraint::FloatRange(_constraint), Setting::Bool(_setting)) => Ok(0.0), 84 (Constraint::FloatRange(constraint), Setting::Integer(setting)) => { 85 constraint.fitness_distance(Some(setting)) 86 } 87 (Constraint::FloatRange(constraint), Setting::Float(setting)) => { 88 constraint.fitness_distance(Some(setting)) 89 } 90 (Constraint::FloatRange(_constraint), Setting::String(_setting)) => Ok(0.0), 91 92 // String constraint: 93 (Constraint::String(_constraint), Setting::Bool(_setting)) => Ok(0.0), 94 (Constraint::String(_constraint), Setting::Integer(_setting)) => Ok(0.0), 95 (Constraint::String(_constraint), Setting::Float(_setting)) => Ok(0.0), 96 (Constraint::String(constraint), Setting::String(setting)) => { 97 constraint.fitness_distance(Some(setting)) 98 } 99 100 // String sequence constraint: 101 (Constraint::StringSequence(_constraint), Setting::Bool(_setting)) => Ok(0.0), 102 (Constraint::StringSequence(_constraint), Setting::Integer(_setting)) => Ok(0.0), 103 (Constraint::StringSequence(_constraint), Setting::Float(_setting)) => Ok(0.0), 104 (Constraint::StringSequence(constraint), Setting::String(setting)) => { 105 constraint.fitness_distance(Some(setting)) 106 } 107 }; 108 109 #[cfg(debug_assertions)] 110 if let Ok(fitness_distance) = result { 111 debug_assert!({ fitness_distance.is_finite() }); 112 } 113 114 result 115 } 116 } 117 118 #[cfg(test)] 119 mod tests { 120 use crate::{constraint::EmptyConstraint, MediaTrackSetting, ResolvedMediaTrackConstraint}; 121 122 use super::*; 123 124 #[test] empty_constraint()125 fn empty_constraint() { 126 // As per step 1 of the `SelectSettings` algorithm from the W3C spec: 127 // <https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings> 128 // 129 // > Each constraint specifies one or more values (or a range of values) for its property. 130 // > A property MAY appear more than once in the list of 'advanced' ConstraintSets. 131 // > If an empty list has been given as the value for a constraint, 132 // > it MUST be interpreted as if the constraint were not specified 133 // > (in other words, an empty constraint == no constraint). 134 let constraint = ResolvedMediaTrackConstraint::Empty(EmptyConstraint {}); 135 136 let settings = [ 137 MediaTrackSetting::Bool(true), 138 MediaTrackSetting::Integer(42), 139 MediaTrackSetting::Float(4.2), 140 MediaTrackSetting::String("string".to_owned()), 141 ]; 142 143 let expected = 0.0; 144 145 for setting in settings { 146 let actual = constraint.fitness_distance(Some(&setting)).unwrap(); 147 148 assert_eq!(actual, expected); 149 } 150 } 151 152 mod bool_constraint { 153 use crate::ResolvedValueConstraint; 154 155 use super::*; 156 157 #[test] bool_setting()158 fn bool_setting() { 159 // As per step 8 of the `fitness distance` function from the W3C spec: 160 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 161 // 162 // > For all string, enum and boolean constraints 163 // > (e.g. deviceId, groupId, facingMode, resizeMode, echoCancellation), 164 // > the fitness distance is the result of the formula: 165 // > 166 // > ``` 167 // > (actual == ideal) ? 0 : 1 168 // > ``` 169 170 let scenarios = [(false, false), (false, true), (true, false), (true, true)]; 171 172 for (constraint_value, setting_value) in scenarios { 173 let constraint = ResolvedMediaTrackConstraint::Bool(ResolvedValueConstraint { 174 exact: None, 175 ideal: Some(constraint_value), 176 }); 177 178 let setting = MediaTrackSetting::Bool(setting_value); 179 180 let actual = constraint.fitness_distance(Some(&setting)).unwrap(); 181 182 let expected = if constraint_value == setting_value { 183 0.0 184 } else { 185 1.0 186 }; 187 188 assert_eq!(actual, expected); 189 } 190 } 191 192 #[test] non_bool_settings()193 fn non_bool_settings() { 194 // As per step 4 of the `fitness distance` function from the W3C spec: 195 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 196 // 197 // > If constraintValue is a boolean, but the constrainable property is not, 198 // > then the fitness distance is based on whether the settings dictionary's 199 // > constraintName member exists or not, from the formula: 200 // > 201 // > ``` 202 // > (constraintValue == exists) ? 0 : 1 203 // > ``` 204 205 let settings = [ 206 MediaTrackSetting::Integer(42), 207 MediaTrackSetting::Float(4.2), 208 MediaTrackSetting::String("string".to_owned()), 209 ]; 210 211 let scenarios = [(false, false), (false, true), (true, false), (true, true)]; 212 213 for (constraint_value, setting_value) in scenarios { 214 let constraint = ResolvedMediaTrackConstraint::Bool(ResolvedValueConstraint { 215 exact: None, 216 ideal: Some(constraint_value), 217 }); 218 219 for setting in settings.iter() { 220 // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)` 221 // once MSRV has passed 1.62.0: 222 let setting = if setting_value { Some(setting) } else { None }; 223 let actual = constraint.fitness_distance(setting).unwrap(); 224 225 let expected = if setting_value { 0.0 } else { 1.0 }; 226 227 assert_eq!(actual, expected); 228 } 229 } 230 } 231 } 232 233 mod numeric_constraint { 234 use crate::ResolvedValueRangeConstraint; 235 236 use super::*; 237 238 #[test] missing_settings()239 fn missing_settings() { 240 // As per step 5 of the `fitness distance` function from the W3C spec: 241 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 242 // 243 // > If the settings dictionary's constraintName member does not exist, 244 // > the fitness distance is 1. 245 246 let constraints = [ 247 ResolvedMediaTrackConstraint::IntegerRange(ResolvedValueRangeConstraint { 248 exact: None, 249 ideal: Some(42), 250 min: None, 251 max: None, 252 }), 253 ResolvedMediaTrackConstraint::FloatRange(ResolvedValueRangeConstraint { 254 exact: None, 255 ideal: Some(42.0), 256 min: None, 257 max: None, 258 }), 259 ]; 260 261 for constraint in constraints { 262 let actual = constraint.fitness_distance(None).unwrap(); 263 264 let expected = 1.0; 265 266 assert_eq!(actual, expected); 267 } 268 } 269 270 #[test] compatible_settings()271 fn compatible_settings() { 272 // As per step 7 of the `fitness distance` function from the W3C spec: 273 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 274 // 275 // > For all positive numeric constraints 276 // > (such as height, width, frameRate, aspectRatio, sampleRate and sampleSize), 277 // > the fitness distance is the result of the formula 278 // > 279 // > ``` 280 // > (actual == ideal) ? 0 : |actual - ideal| / max(|actual|, |ideal|) 281 // > ``` 282 283 let settings = [ 284 MediaTrackSetting::Integer(21), 285 MediaTrackSetting::Float(21.0), 286 ]; 287 288 let constraints = [ 289 ResolvedMediaTrackConstraint::IntegerRange(ResolvedValueRangeConstraint { 290 exact: None, 291 ideal: Some(42), 292 min: None, 293 max: None, 294 }), 295 ResolvedMediaTrackConstraint::FloatRange(ResolvedValueRangeConstraint { 296 exact: None, 297 ideal: Some(42.0), 298 min: None, 299 max: None, 300 }), 301 ]; 302 303 for constraint in constraints { 304 for setting in settings.iter() { 305 let actual = constraint.fitness_distance(Some(setting)).unwrap(); 306 307 let expected = 0.5; 308 309 assert_eq!(actual, expected); 310 } 311 } 312 } 313 314 #[test] incompatible_settings()315 fn incompatible_settings() { 316 // As per step 3 of the `fitness distance` function from the W3C spec: 317 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 318 // 319 // > If the constraint does not apply for this type of object, the fitness distance is 0 320 // > (that is, the constraint does not influence the fitness distance). 321 322 let settings = [ 323 MediaTrackSetting::Bool(true), 324 MediaTrackSetting::String("string".to_owned()), 325 ]; 326 327 let constraints = [ 328 ResolvedMediaTrackConstraint::IntegerRange(ResolvedValueRangeConstraint { 329 exact: None, 330 ideal: Some(42), 331 min: None, 332 max: None, 333 }), 334 ResolvedMediaTrackConstraint::FloatRange(ResolvedValueRangeConstraint { 335 exact: None, 336 ideal: Some(42.0), 337 min: None, 338 max: None, 339 }), 340 ]; 341 342 for constraint in constraints { 343 for setting in settings.iter() { 344 let actual = constraint.fitness_distance(Some(setting)).unwrap(); 345 346 let expected = 0.0; 347 348 println!("constraint: {constraint:?}"); 349 println!("setting: {setting:?}"); 350 println!("actual: {actual:?}"); 351 println!("expected: {expected:?}"); 352 353 assert_eq!(actual, expected); 354 } 355 } 356 } 357 } 358 359 mod string_constraint { 360 use crate::ResolvedValueConstraint; 361 362 use super::*; 363 364 #[test] missing_settings()365 fn missing_settings() { 366 // As per step 5 of the `fitness distance` function from the W3C spec: 367 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 368 // 369 // > If the settings dictionary's constraintName member does not exist, 370 // > the fitness distance is 1. 371 372 let constraint = ResolvedMediaTrackConstraint::String(ResolvedValueConstraint { 373 exact: None, 374 ideal: Some("constraint".to_owned()), 375 }); 376 377 let actual = constraint.fitness_distance(None).unwrap(); 378 379 let expected = 1.0; 380 381 assert_eq!(actual, expected); 382 } 383 384 #[test] compatible_settings()385 fn compatible_settings() { 386 // As per step 8 of the `fitness distance` function from the W3C spec: 387 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 388 // 389 // > For all string, enum and boolean constraints 390 // > (e.g. deviceId, groupId, facingMode, resizeMode, echoCancellation), 391 // > the fitness distance is the result of the formula: 392 // > 393 // > ``` 394 // > (actual == ideal) ? 0 : 1 395 // > ``` 396 397 let constraint = ResolvedMediaTrackConstraint::String(ResolvedValueConstraint { 398 exact: None, 399 ideal: Some("constraint".to_owned()), 400 }); 401 402 let settings = [MediaTrackSetting::String("setting".to_owned())]; 403 404 for setting in settings { 405 let actual = constraint.fitness_distance(Some(&setting)).unwrap(); 406 407 let expected = 1.0; 408 409 assert_eq!(actual, expected); 410 } 411 } 412 413 #[test] incompatible_settings()414 fn incompatible_settings() { 415 // As per step 3 of the `fitness distance` function from the W3C spec: 416 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 417 // 418 // > If the constraint does not apply for this type of object, the fitness distance is 0 419 // > (that is, the constraint does not influence the fitness distance). 420 421 let constraint = ResolvedMediaTrackConstraint::String(ResolvedValueConstraint { 422 exact: None, 423 ideal: Some("string".to_owned()), 424 }); 425 426 let settings = [ 427 MediaTrackSetting::Bool(true), 428 MediaTrackSetting::Integer(42), 429 MediaTrackSetting::Float(4.2), 430 ]; 431 432 for setting in settings { 433 let actual = constraint.fitness_distance(Some(&setting)).unwrap(); 434 435 let expected = 0.0; 436 437 println!("constraint: {constraint:?}"); 438 println!("setting: {setting:?}"); 439 println!("actual: {actual:?}"); 440 println!("expected: {expected:?}"); 441 442 assert_eq!(actual, expected); 443 } 444 } 445 } 446 447 mod string_sequence_constraint { 448 use crate::ResolvedValueSequenceConstraint; 449 450 use super::*; 451 452 #[test] missing_settings()453 fn missing_settings() { 454 // As per step 5 of the `fitness distance` function from the W3C spec: 455 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 456 // 457 // > If the settings dictionary's constraintName member does not exist, 458 // > the fitness distance is 1. 459 460 let constraint = 461 ResolvedMediaTrackConstraint::StringSequence(ResolvedValueSequenceConstraint { 462 exact: None, 463 ideal: Some(vec!["constraint".to_owned()]), 464 }); 465 466 let actual = constraint.fitness_distance(None).unwrap(); 467 468 let expected = 1.0; 469 470 assert_eq!(actual, expected); 471 } 472 473 #[test] compatible_settings()474 fn compatible_settings() { 475 // As per step 8 of the `fitness distance` function from the W3C spec: 476 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 477 // 478 // > For all string, enum and boolean constraints 479 // > (e.g. deviceId, groupId, facingMode, resizeMode, echoCancellation), 480 // > the fitness distance is the result of the formula: 481 // > 482 // > ``` 483 // > (actual == ideal) ? 0 : 1 484 // > ``` 485 // 486 // As well as the preliminary definition: 487 // 488 // > For string valued constraints, we define "==" below to be true if one of the 489 // > values in the sequence is exactly the same as the value being compared against. 490 491 let constraint = 492 ResolvedMediaTrackConstraint::StringSequence(ResolvedValueSequenceConstraint { 493 exact: None, 494 ideal: Some(vec!["constraint".to_owned()]), 495 }); 496 497 let settings = [MediaTrackSetting::String("setting".to_owned())]; 498 499 for setting in settings { 500 let actual = constraint.fitness_distance(Some(&setting)).unwrap(); 501 502 let expected = 1.0; 503 504 assert_eq!(actual, expected); 505 } 506 } 507 508 #[test] incompatible_settings()509 fn incompatible_settings() { 510 // As per step 3 of the `fitness distance` function from the W3C spec: 511 // <https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance> 512 // 513 // > If the constraint does not apply for this type of object, the fitness distance is 0 514 // > (that is, the constraint does not influence the fitness distance). 515 516 let constraint = 517 ResolvedMediaTrackConstraint::StringSequence(ResolvedValueSequenceConstraint { 518 exact: None, 519 ideal: Some(vec!["constraint".to_owned()]), 520 }); 521 522 let settings = [ 523 MediaTrackSetting::Bool(true), 524 MediaTrackSetting::Integer(42), 525 MediaTrackSetting::Float(4.2), 526 ]; 527 528 for setting in settings { 529 let actual = constraint.fitness_distance(Some(&setting)).unwrap(); 530 531 let expected = 0.0; 532 533 assert_eq!(actual, expected); 534 } 535 } 536 } 537 } 538