Skip to main content

smooth_runtime/client/
config.rs

1//! Tunables: [`SmoothConfig`] and the video-quality choice [`QualitySelector`].
2
3use std::fmt;
4use std::sync::Arc;
5
6use super::output::QualityInfo;
7
8/// Fragments before the live edge a live join starts at.
9pub const DEFAULT_LIVE_JOIN_FRAGMENTS: usize = 2;
10/// Fragment fetch failures tolerated before a fragment is skipped (live) or the
11/// session ends (static).
12pub const DEFAULT_MAX_FRAGMENT_FAILURES: u32 = 5;
13/// Manifest failures tolerated before the session ends.
14pub const DEFAULT_MAX_MANIFEST_FAILURES: u32 = 5;
15/// Consecutive refreshes lacking a stream before that stream is abandoned.
16pub const DEFAULT_MAX_REFRESH_MISSES: u32 = 5;
17/// Backoff after the first failures, in milliseconds.
18pub const DEFAULT_BACKOFF_MS: [u64; 4] = [250, 500, 1_000, 2_000];
19/// Backoff once the list is exhausted, in milliseconds.
20pub const DEFAULT_BACKOFF_CAP_MS: u64 = 4_000;
21/// Default [`SmoothConfig::jitter_seed`]; a host that runs more than one client sets its own.
22pub const DEFAULT_JITTER_SEED: u64 = 0;
23/// Shortest live manifest refresh wait, in microseconds.
24pub const DEFAULT_REFRESH_MIN_US: u64 = 500_000;
25/// Longest live manifest refresh wait, in microseconds.
26pub const DEFAULT_REFRESH_MAX_US: u64 = 5_000_000;
27/// `mvhd` timescale of each synthesized init segment (ISO/IEC 14496-12:2026 ยง8.2.2).
28pub const SYNTHETIC_MOVIE_TIMESCALE: u32 = 90_000;
29
30type SelectFn = dyn Fn(&[QualityInfo]) -> usize + Send + Sync;
31
32/// Chooses which `QualityLevel` of the first video stream to play.
33#[derive(Clone)]
34pub struct QualitySelector(Arc<SelectFn>);
35
36impl QualitySelector {
37    /// A selector backed by `f`, which returns an index into the offered list.
38    pub fn new(f: impl Fn(&[QualityInfo]) -> usize + Send + Sync + 'static) -> Self {
39        Self(Arc::new(f))
40    }
41
42    /// Always the first `QualityLevel`.
43    pub fn first() -> Self {
44        Self::new(|_| 0)
45    }
46
47    pub(crate) fn choose(&self, qualities: &[QualityInfo]) -> usize {
48        (self.0)(qualities).min(qualities.len().saturating_sub(1))
49    }
50}
51
52impl fmt::Debug for QualitySelector {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        f.write_str("QualitySelector(..)")
55    }
56}
57
58/// Engine tunables: start from [`SmoothConfig::default`] and set what differs
59/// with the `with_*` setters.
60#[derive(Debug, Clone)]
61#[non_exhaustive]
62pub struct SmoothConfig {
63    /// Live join point, in fragments behind the newest.
64    pub live_join_fragments: usize,
65    /// Consecutive failures of one fragment before it is skipped (live) or the session ends (static); default 5.
66    pub max_fragment_failures: u32,
67    /// Consecutive manifest failures before the session ends; default 5.
68    pub max_manifest_failures: u32,
69    /// Consecutive refreshes lacking a stream before that stream is abandoned; default 5.
70    pub max_refresh_misses: u32,
71    /// Retry delays by attempt, in milliseconds.
72    pub backoff_ms: Vec<u64>,
73    /// Retry delay past the end of [`Self::backoff_ms`], in milliseconds.
74    pub backoff_cap_ms: u64,
75    /// Seed of the retry jitter ([`broadcast_common::backoff::RetrySchedule`]): the first retry of
76    /// a fragment or manifest waits exactly its step, every later one a term of `[5/6, 1.0)` of its
77    /// step drawn from this seed, so a fleet of clients that fail together does not retry in
78    /// lockstep. The engine reads no clock and no RNG, so a host gives each client its own entropy;
79    /// two clients with the same seed retry identically (which is what makes a replay reproducible).
80    pub jitter_seed: u64,
81    /// Lower clamp of the live refresh wait, in microseconds.
82    pub refresh_min_us: u64,
83    /// Upper clamp of the live refresh wait, in microseconds.
84    pub refresh_max_us: u64,
85    /// Video quality choice.
86    pub video_quality: QualitySelector,
87}
88
89impl Default for SmoothConfig {
90    fn default() -> Self {
91        Self {
92            live_join_fragments: DEFAULT_LIVE_JOIN_FRAGMENTS,
93            max_fragment_failures: DEFAULT_MAX_FRAGMENT_FAILURES,
94            max_manifest_failures: DEFAULT_MAX_MANIFEST_FAILURES,
95            max_refresh_misses: DEFAULT_MAX_REFRESH_MISSES,
96            backoff_ms: DEFAULT_BACKOFF_MS.to_vec(),
97            backoff_cap_ms: DEFAULT_BACKOFF_CAP_MS,
98            jitter_seed: DEFAULT_JITTER_SEED,
99            refresh_min_us: DEFAULT_REFRESH_MIN_US,
100            refresh_max_us: DEFAULT_REFRESH_MAX_US,
101            video_quality: QualitySelector::first(),
102        }
103    }
104}
105
106impl SmoothConfig {
107    /// Set [`Self::live_join_fragments`].
108    #[must_use]
109    pub fn with_live_join_fragments(mut self, live_join_fragments: usize) -> Self {
110        self.live_join_fragments = live_join_fragments;
111        self
112    }
113
114    /// Set [`Self::max_fragment_failures`].
115    #[must_use]
116    pub fn with_max_fragment_failures(mut self, max_fragment_failures: u32) -> Self {
117        self.max_fragment_failures = max_fragment_failures;
118        self
119    }
120
121    /// Set [`Self::max_manifest_failures`].
122    #[must_use]
123    pub fn with_max_manifest_failures(mut self, max_manifest_failures: u32) -> Self {
124        self.max_manifest_failures = max_manifest_failures;
125        self
126    }
127
128    /// Set [`Self::max_refresh_misses`].
129    #[must_use]
130    pub fn with_max_refresh_misses(mut self, max_refresh_misses: u32) -> Self {
131        self.max_refresh_misses = max_refresh_misses;
132        self
133    }
134
135    /// Set [`Self::backoff_ms`].
136    #[must_use]
137    pub fn with_backoff_ms(mut self, backoff_ms: Vec<u64>) -> Self {
138        self.backoff_ms = backoff_ms;
139        self
140    }
141
142    /// Set [`Self::backoff_cap_ms`].
143    #[must_use]
144    pub fn with_backoff_cap_ms(mut self, backoff_cap_ms: u64) -> Self {
145        self.backoff_cap_ms = backoff_cap_ms;
146        self
147    }
148
149    /// Set [`Self::jitter_seed`].
150    #[must_use]
151    pub fn with_jitter_seed(mut self, jitter_seed: u64) -> Self {
152        self.jitter_seed = jitter_seed;
153        self
154    }
155
156    /// Set [`Self::refresh_min_us`].
157    #[must_use]
158    pub fn with_refresh_min_us(mut self, refresh_min_us: u64) -> Self {
159        self.refresh_min_us = refresh_min_us;
160        self
161    }
162
163    /// Set [`Self::refresh_max_us`].
164    #[must_use]
165    pub fn with_refresh_max_us(mut self, refresh_max_us: u64) -> Self {
166        self.refresh_max_us = refresh_max_us;
167        self
168    }
169
170    /// Set [`Self::video_quality`].
171    #[must_use]
172    pub fn with_video_quality(mut self, video_quality: QualitySelector) -> Self {
173        self.video_quality = video_quality;
174        self
175    }
176}