1 use std::collections::HashMap; 2 3 use crate::{ 4 algorithms::{ 5 select_settings::{ConstraintFailureInfo, DeviceInformationExposureMode}, 6 FitnessDistance, 7 }, 8 errors::OverconstrainedError, 9 MediaTrackProperty, 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<MediaTrackProperty, ConstraintFailureInfo> = 34 Default::default(); 35 36 for candidate in candidates { 37 match mandatory_constraints.fitness_distance(candidate) { 38 Ok(fitness_distance) => { 39 debug_assert!(fitness_distance.is_finite()); 40 41 feasible_candidates.push((candidate, fitness_distance)); 42 } 43 Err(error) => { 44 for (property, setting_error) in error.setting_errors { 45 let entry = failed_constraints 46 .entry(property) 47 .or_insert_with(Default::default); 48 entry.failures += 1; 49 entry.errors.insert(setting_error); 50 } 51 } 52 } 53 } 54 55 if feasible_candidates.is_empty() { 56 return Err(match exposure_mode { 57 DeviceInformationExposureMode::Exposed => { 58 OverconstrainedError::exposing_device_information(failed_constraints) 59 } 60 DeviceInformationExposureMode::Protected => OverconstrainedError::default(), 61 }); 62 } 63 64 Ok(feasible_candidates) 65 } 66 67 #[cfg(test)] 68 mod tests { 69 use std::iter::FromIterator; 70 71 use crate::{ 72 property::all::name::*, MediaTrackSupportedConstraints, ResizeMode, 73 ResolvedMandatoryMediaTrackConstraints, ResolvedValueConstraint, 74 ResolvedValueRangeConstraint, 75 }; 76 77 use super::*; 78 79 // Advanced constraint sets that do not match any candidates should just get ignored: 80 #[test] 81 fn overconstrained() { 82 let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ 83 &DEVICE_ID, 84 &HEIGHT, 85 &WIDTH, 86 &RESIZE_MODE, 87 ]); 88 89 let settings = vec![ 90 MediaTrackSettings::from_iter([(&DEVICE_ID, "foo".into())]), 91 MediaTrackSettings::from_iter([(&DEVICE_ID, "bar".into())]), 92 ]; 93 94 let candidates: Vec<_> = settings.iter().collect(); 95 96 let constraints = ResolvedMandatoryMediaTrackConstraints::from_iter([( 97 &DEVICE_ID, 98 ResolvedValueConstraint::default() 99 .exact("mismatched-device".to_owned()) 100 .into(), 101 )]); 102 103 let sanitized_constraints = constraints.to_sanitized(&supported_constraints); 104 105 // Exposed exposure mode: 106 107 let error = apply_mandatory_constraints( 108 candidates.clone(), 109 &sanitized_constraints, 110 DeviceInformationExposureMode::Exposed, 111 ) 112 .unwrap_err(); 113 114 let constraint = &error.constraint; 115 let err_message = error.message.as_ref().expect("Error message."); 116 117 assert_eq!(constraint, &DEVICE_ID); 118 assert_eq!( 119 err_message, 120 "Setting was a mismatch ([\"bar\", \"foo\"] do not satisfy (x == \"mismatched-device\"))." 121 ); 122 123 // Protected exposure mode: 124 125 let error = apply_mandatory_constraints( 126 candidates, 127 &sanitized_constraints, 128 DeviceInformationExposureMode::Protected, 129 ) 130 .unwrap_err(); 131 132 let constraint = &error.constraint; 133 let err_message = error.message; 134 135 assert_eq!( 136 constraint, 137 &MediaTrackProperty::from(""), 138 "Constraint should not have been exposed" 139 ); 140 assert!( 141 err_message.is_none(), 142 "Error message should not have been exposed" 143 ); 144 } 145 146 #[test] 147 fn constrained() { 148 let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ 149 &DEVICE_ID, 150 &HEIGHT, 151 &WIDTH, 152 &RESIZE_MODE, 153 ]); 154 155 let settings = vec![ 156 MediaTrackSettings::from_iter([ 157 (&DEVICE_ID, "480p".into()), 158 (&HEIGHT, 480.into()), 159 (&WIDTH, 720.into()), 160 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), 161 ]), 162 MediaTrackSettings::from_iter([ 163 (&DEVICE_ID, "720p".into()), 164 (&HEIGHT, 720.into()), 165 (&WIDTH, 1280.into()), 166 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), 167 ]), 168 MediaTrackSettings::from_iter([ 169 (&DEVICE_ID, "1080p".into()), 170 (&HEIGHT, 1080.into()), 171 (&WIDTH, 1920.into()), 172 (&RESIZE_MODE, ResizeMode::none().into()), 173 ]), 174 MediaTrackSettings::from_iter([ 175 (&DEVICE_ID, "1440p".into()), 176 (&HEIGHT, 1440.into()), 177 (&WIDTH, 2560.into()), 178 (&RESIZE_MODE, ResizeMode::none().into()), 179 ]), 180 MediaTrackSettings::from_iter([ 181 (&DEVICE_ID, "2160p".into()), 182 (&HEIGHT, 2160.into()), 183 (&WIDTH, 3840.into()), 184 (&RESIZE_MODE, ResizeMode::none().into()), 185 ]), 186 ]; 187 188 let candidates: Vec<_> = settings.iter().collect(); 189 190 let constraints = ResolvedMandatoryMediaTrackConstraints::from_iter([ 191 ( 192 &RESIZE_MODE, 193 ResolvedValueConstraint::default() 194 .exact(ResizeMode::none()) 195 .into(), 196 ), 197 ( 198 &HEIGHT, 199 ResolvedValueRangeConstraint::default().min(1000).into(), 200 ), 201 ( 202 &WIDTH, 203 ResolvedValueRangeConstraint::default().max(2000).into(), 204 ), 205 ]); 206 207 let sanitized_constraints = constraints.to_sanitized(&supported_constraints); 208 209 let actual = apply_mandatory_constraints( 210 candidates, 211 &sanitized_constraints, 212 DeviceInformationExposureMode::Exposed, 213 ) 214 .unwrap(); 215 216 let expected = vec![(&settings[2], 0.0)]; 217 218 assert_eq!(actual, expected); 219 } 220 } 221