webrtc_constraints/algorithms/select_settings/
tie_breaking.rs

1use std::iter::FromIterator;
2
3use ordered_float::NotNan;
4
5use 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.
12pub 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.
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.
30pub struct FirstPolicy;
31
32impl FirstPolicy {
33    /// Creates a new policy.
34    pub fn new() -> Self {
35        Self
36    }
37}
38
39impl Default for FirstPolicy {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45impl TieBreakingPolicy for FirstPolicy {
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.
59pub struct ClosestToIdealPolicy {
60    sanitized_constraints: SanitizedMandatoryMediaTrackConstraints,
61}
62
63impl ClosestToIdealPolicy {
64    /// Creates a new policy from the given ideal settings and supported constraints.
65    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
83impl TieBreakingPolicy for ClosestToIdealPolicy {
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)]
102mod 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]
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]
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}