1 use std::iter::FromIterator; 2 3 use ordered_float::NotNan; 4 5 use crate::{ 6 algorithms::FitnessDistance, MandatoryMediaTrackConstraints, MediaTrackSettings, 7 MediaTrackSupportedConstraints, SanitizedMandatoryMediaTrackConstraints, 8 }; 9 10 /// A tie-breaking policy used for selecting a single preferred candidate 11 /// from a set list of equally optimal setting candidates. 12 pub trait TieBreakingPolicy { 13 /// Selects a preferred candidate from a non-empty selection of optimal candidates. 14 /// 15 /// As specified in step 6 of the `SelectSettings` algorithm: 16 /// <https://www.w3.org/TR/mediacapture-streams/#dfn-selectsettings> 17 /// 18 /// > Select one settings dictionary from candidates, and return it as the result 19 /// > of the SelectSettings algorithm. The User Agent MUST use one with the 20 /// > smallest fitness distance, as calculated in step 3. 21 /// > If more than one settings dictionary have the smallest fitness distance, 22 /// > the User Agent chooses one of them based on system default property values 23 /// > and User Agent default property values. select_candidate<'a, I>(&self, candidates: I) -> &'a MediaTrackSettings where I: IntoIterator<Item = &'a MediaTrackSettings>24 fn select_candidate<'a, I>(&self, candidates: I) -> &'a MediaTrackSettings 25 where 26 I: IntoIterator<Item = &'a MediaTrackSettings>; 27 } 28 29 /// A naïve tie-breaking policy that just picks the first settings item it encounters. 30 pub struct FirstPolicy; 31 32 impl FirstPolicy { 33 /// Creates a new policy. new() -> Self34 pub fn new() -> Self { 35 Self 36 } 37 } 38 39 impl Default for FirstPolicy { default() -> Self40 fn default() -> Self { 41 Self::new() 42 } 43 } 44 45 impl TieBreakingPolicy for FirstPolicy { select_candidate<'a, I>(&self, candidates: I) -> &'a MediaTrackSettings where I: IntoIterator<Item = &'a MediaTrackSettings>,46 fn select_candidate<'a, I>(&self, candidates: I) -> &'a MediaTrackSettings 47 where 48 I: IntoIterator<Item = &'a MediaTrackSettings>, 49 { 50 // Safety: We know that `candidates is non-empty: 51 candidates 52 .into_iter() 53 .next() 54 .expect("The `candidates` iterator should have produced at least one item.") 55 } 56 } 57 58 /// A tie-breaking policy that picks the settings item that's closest to the specified ideal settings. 59 pub struct ClosestToIdealPolicy { 60 sanitized_constraints: SanitizedMandatoryMediaTrackConstraints, 61 } 62 63 impl ClosestToIdealPolicy { 64 /// Creates a new policy from the given ideal settings and supported constraints. new( ideal_settings: MediaTrackSettings, supported_constraints: &MediaTrackSupportedConstraints, ) -> Self65 pub fn new( 66 ideal_settings: MediaTrackSettings, 67 supported_constraints: &MediaTrackSupportedConstraints, 68 ) -> Self { 69 let sanitized_constraints = MandatoryMediaTrackConstraints::from_iter( 70 ideal_settings 71 .into_iter() 72 .map(|(property, setting)| (property, setting.into())), 73 ) 74 .into_resolved() 75 .into_sanitized(supported_constraints); 76 77 Self { 78 sanitized_constraints, 79 } 80 } 81 } 82 83 impl TieBreakingPolicy for ClosestToIdealPolicy { select_candidate<'b, I>(&self, candidates: I) -> &'b MediaTrackSettings where I: IntoIterator<Item = &'b MediaTrackSettings>,84 fn select_candidate<'b, I>(&self, candidates: I) -> &'b MediaTrackSettings 85 where 86 I: IntoIterator<Item = &'b MediaTrackSettings>, 87 { 88 candidates 89 .into_iter() 90 .min_by_key(|settings| { 91 let fitness_distance = self 92 .sanitized_constraints 93 .fitness_distance(settings) 94 .expect("Fitness distance should be positive."); 95 NotNan::new(fitness_distance).expect("Expected non-NaN fitness distance.") 96 }) 97 .expect("The `candidates` iterator should have produced at least one item.") 98 } 99 } 100 101 #[cfg(test)] 102 mod tests { 103 use super::*; 104 105 use std::iter::FromIterator; 106 107 use crate::{ 108 property::all::name::*, MediaTrackSettings, MediaTrackSupportedConstraints, ResizeMode, 109 }; 110 111 #[test] first()112 fn first() { 113 let settings = vec![ 114 MediaTrackSettings::from_iter([(&DEVICE_ID, "device-id-0".into())]), 115 MediaTrackSettings::from_iter([(&DEVICE_ID, "device-id-1".into())]), 116 MediaTrackSettings::from_iter([(&DEVICE_ID, "device-id-2".into())]), 117 ]; 118 119 let policy = FirstPolicy::default(); 120 121 let actual = policy.select_candidate(&settings); 122 123 let expected = &settings[0]; 124 125 assert_eq!(actual, expected); 126 } 127 128 #[test] closest_to_ideal()129 fn closest_to_ideal() { 130 let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ 131 &DEVICE_ID, 132 &HEIGHT, 133 &WIDTH, 134 &RESIZE_MODE, 135 ]); 136 137 let settings = vec![ 138 MediaTrackSettings::from_iter([ 139 (&DEVICE_ID, "480p".into()), 140 (&HEIGHT, 480.into()), 141 (&WIDTH, 720.into()), 142 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), 143 ]), 144 MediaTrackSettings::from_iter([ 145 (&DEVICE_ID, "720p".into()), 146 (&HEIGHT, 720.into()), 147 (&WIDTH, 1280.into()), 148 (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), 149 ]), 150 MediaTrackSettings::from_iter([ 151 (&DEVICE_ID, "1080p".into()), 152 (&HEIGHT, 1080.into()), 153 (&WIDTH, 1920.into()), 154 (&RESIZE_MODE, ResizeMode::none().into()), 155 ]), 156 MediaTrackSettings::from_iter([ 157 (&DEVICE_ID, "1440p".into()), 158 (&HEIGHT, 1440.into()), 159 (&WIDTH, 2560.into()), 160 (&RESIZE_MODE, ResizeMode::none().into()), 161 ]), 162 MediaTrackSettings::from_iter([ 163 (&DEVICE_ID, "2160p".into()), 164 (&HEIGHT, 2160.into()), 165 (&WIDTH, 3840.into()), 166 (&RESIZE_MODE, ResizeMode::none().into()), 167 ]), 168 ]; 169 170 let ideal_settings = vec![ 171 MediaTrackSettings::from_iter([(&HEIGHT, 450.into()), (&WIDTH, 700.into())]), 172 MediaTrackSettings::from_iter([(&HEIGHT, 700.into()), (&WIDTH, 1250.into())]), 173 MediaTrackSettings::from_iter([(&HEIGHT, 1000.into()), (&WIDTH, 2000.into())]), 174 MediaTrackSettings::from_iter([(&HEIGHT, 1500.into()), (&WIDTH, 2500.into())]), 175 MediaTrackSettings::from_iter([(&HEIGHT, 2000.into()), (&WIDTH, 3750.into())]), 176 ]; 177 178 for (index, ideal) in ideal_settings.iter().enumerate() { 179 let policy = ClosestToIdealPolicy::new(ideal.clone(), &supported_constraints); 180 181 let actual = policy.select_candidate(&settings); 182 183 let expected = &settings[index]; 184 185 assert_eq!(actual, expected); 186 } 187 } 188 } 189