1 use std::collections::HashMap; 2 3 use crate::{ 4 algorithms::{ 5 select_settings::{ConstraintFailureInfo, DeviceInformationExposureMode}, 6 FitnessDistance, 7 }, 8 errors::OverconstrainedError, 9 MediaTrackSettings, SanitizedMediaTrackConstraintSet, 10 }; 11 12 /// Returns the set of settings for which all mandatory constraints' 13 /// fitness distance is finite. 14 /// 15 /// Implements step 5 of the `SelectSettings` algorithm: 16 /// https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings 17 pub(super) fn apply_mandatory_constraints<'a, I>( 18 candidates: I, 19 mandatory_constraints: &SanitizedMediaTrackConstraintSet, 20 exposure_mode: DeviceInformationExposureMode, 21 ) -> Result<Vec<(&'a MediaTrackSettings, f64)>, OverconstrainedError> 22 where 23 I: IntoIterator<Item = &'a MediaTrackSettings>, 24 { 25 // As specified in step 3 of the `SelectSettings` algorithm: 26 // https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings 27 // 28 // > For every possible settings dictionary of copy compute its fitness distance, 29 // > treating bare values of properties as ideal values. Let candidates be the 30 // > set of settings dictionaries for which the fitness distance is finite. 31 32 let mut feasible_candidates: Vec<(&'a MediaTrackSettings, f64)> = vec![]; 33 let mut failed_constraints: HashMap<String, ConstraintFailureInfo> = Default::default(); 34 35 for candidate in candidates { 36 match mandatory_constraints.fitness_distance(candidate) { 37 Ok(fitness_distance) => { 38 debug_assert!(fitness_distance.is_finite()); 39 40 feasible_candidates.push((candidate, fitness_distance)); 41 } 42 Err(error) => { 43 for (property, setting_error) in error.setting_errors { 44 let entry = failed_constraints 45 .entry(property) 46 .or_insert_with(Default::default); 47 entry.failures += 1; 48 entry.errors.insert(setting_error); 49 } 50 } 51 } 52 } 53 54 if feasible_candidates.is_empty() { 55 return Err(match exposure_mode { 56 DeviceInformationExposureMode::Exposed => { 57 OverconstrainedError::exposing_device_information(failed_constraints) 58 } 59 DeviceInformationExposureMode::Protected => OverconstrainedError::default(), 60 }); 61 } 62 63 Ok(feasible_candidates) 64 } 65 66 #[cfg(test)] 67 mod tests { 68 use std::iter::FromIterator; 69 70 use crate::{ 71 property::all::name::*, MediaTrackSupportedConstraints, ResizeMode, 72 ResolvedMandatoryMediaTrackConstraints, ResolvedValueConstraint, 73 ResolvedValueRangeConstraint, 74 }; 75 76 use super::*; 77 78 // Advanced constraint sets that do not match any candidates should just get ignored: 79 #[test] 80 fn overconstrained() { 81 let supported_constraints = 82 MediaTrackSupportedConstraints::from_iter(vec![DEVICE_ID, HEIGHT, WIDTH, RESIZE_MODE]); 83 84 let settings = vec![ 85 MediaTrackSettings::from_iter([(DEVICE_ID, "foo".into())]), 86 MediaTrackSettings::from_iter([(DEVICE_ID, "bar".into())]), 87 ]; 88 89 let candidates: Vec<_> = settings.iter().collect(); 90 91 let constraints = ResolvedMandatoryMediaTrackConstraints::from_iter([( 92 DEVICE_ID, 93 ResolvedValueConstraint::default() 94 .exact("mismatched-device".to_owned()) 95 .into(), 96 )]); 97 98 let sanitized_constraints = constraints.to_sanitized(&supported_constraints); 99 100 let error = apply_mandatory_constraints( 101 candidates, 102 &sanitized_constraints, 103 DeviceInformationExposureMode::Exposed, 104 ) 105 .unwrap_err(); 106 107 let constraint = &error.constraint; 108 let err_message = error.message.as_ref().expect("Error message."); 109 110 assert_eq!(constraint, DEVICE_ID); 111 assert_eq!( 112 err_message, 113 "Setting was a mismatch ([\"bar\", \"foo\"] do not satisfy (x == \"mismatched-device\"))." 114 ); 115 } 116 117 #[test] 118 fn constrained() { 119 let supported_constraints = 120 MediaTrackSupportedConstraints::from_iter(vec![DEVICE_ID, HEIGHT, WIDTH, RESIZE_MODE]); 121 122 let settings = vec![ 123 MediaTrackSettings::from_iter([ 124 (DEVICE_ID, "480p".into()), 125 (HEIGHT, 480.into()), 126 (WIDTH, 720.into()), 127 (RESIZE_MODE, ResizeMode::crop_and_scale().into()), 128 ]), 129 MediaTrackSettings::from_iter([ 130 (DEVICE_ID, "720p".into()), 131 (HEIGHT, 720.into()), 132 (WIDTH, 1280.into()), 133 (RESIZE_MODE, ResizeMode::crop_and_scale().into()), 134 ]), 135 MediaTrackSettings::from_iter([ 136 (DEVICE_ID, "1080p".into()), 137 (HEIGHT, 1080.into()), 138 (WIDTH, 1920.into()), 139 (RESIZE_MODE, ResizeMode::none().into()), 140 ]), 141 MediaTrackSettings::from_iter([ 142 (DEVICE_ID, "1440p".into()), 143 (HEIGHT, 1440.into()), 144 (WIDTH, 2560.into()), 145 (RESIZE_MODE, ResizeMode::none().into()), 146 ]), 147 MediaTrackSettings::from_iter([ 148 (DEVICE_ID, "2160p".into()), 149 (HEIGHT, 2160.into()), 150 (WIDTH, 3840.into()), 151 (RESIZE_MODE, ResizeMode::none().into()), 152 ]), 153 ]; 154 155 let candidates: Vec<_> = settings.iter().collect(); 156 157 let constraints = ResolvedMandatoryMediaTrackConstraints::from_iter([ 158 ( 159 RESIZE_MODE, 160 ResolvedValueConstraint::default() 161 .exact(ResizeMode::none()) 162 .into(), 163 ), 164 ( 165 HEIGHT, 166 ResolvedValueRangeConstraint::default().min(1000).into(), 167 ), 168 ( 169 WIDTH, 170 ResolvedValueRangeConstraint::default().max(2000).into(), 171 ), 172 ]); 173 174 let sanitized_constraints = constraints.to_sanitized(&supported_constraints); 175 176 let actual = apply_mandatory_constraints( 177 candidates, 178 &sanitized_constraints, 179 DeviceInformationExposureMode::Exposed, 180 ) 181 .unwrap(); 182 183 let expected = vec![(&settings[2], 0.0)]; 184 185 assert_eq!(actual, expected); 186 } 187 } 188