1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct AbrRung {
8 pub width: u32,
10 pub height: u32,
12 pub video_bitrate: u64,
14 pub audio_bitrate: u64,
16 pub frame_rate: (u32, u32),
18 pub codec: String,
20 pub profile_name: String,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum AbrStrategy {
27 AppleHls,
29 YouTube,
31 Netflix,
33 Conservative,
35 Aggressive,
37 Custom,
39}
40
41#[derive(Debug, Clone)]
43pub struct AbrLadder {
44 pub rungs: Vec<AbrRung>,
46 pub strategy: AbrStrategy,
48 pub max_resolution: (u32, u32),
50 pub min_resolution: (u32, u32),
52}
53
54impl AbrRung {
55 #[must_use]
57 pub fn new(
58 width: u32,
59 height: u32,
60 video_bitrate: u64,
61 audio_bitrate: u64,
62 codec: impl Into<String>,
63 profile_name: impl Into<String>,
64 ) -> Self {
65 Self {
66 width,
67 height,
68 video_bitrate,
69 audio_bitrate,
70 frame_rate: (30, 1),
71 codec: codec.into(),
72 profile_name: profile_name.into(),
73 }
74 }
75
76 #[must_use]
78 pub fn with_frame_rate(mut self, num: u32, den: u32) -> Self {
79 self.frame_rate = (num, den);
80 self
81 }
82
83 #[must_use]
85 pub fn total_bitrate(&self) -> u64 {
86 self.video_bitrate + self.audio_bitrate
87 }
88
89 #[must_use]
91 pub fn resolution_string(&self) -> String {
92 format!("{}x{}", self.width, self.height)
93 }
94
95 #[must_use]
97 pub fn is_hd(&self) -> bool {
98 self.height >= 720
99 }
100
101 #[must_use]
103 pub fn is_full_hd(&self) -> bool {
104 self.height >= 1080
105 }
106
107 #[must_use]
109 pub fn is_4k(&self) -> bool {
110 self.height >= 2160
111 }
112}
113
114impl AbrLadder {
115 #[must_use]
117 pub fn new(strategy: AbrStrategy) -> Self {
118 Self {
119 rungs: Vec::new(),
120 strategy,
121 max_resolution: (3840, 2160), min_resolution: (426, 240), }
124 }
125
126 pub fn add_rung(&mut self, rung: AbrRung) {
128 self.rungs.push(rung);
129 self.rungs.sort_by_key(AbrRung::total_bitrate);
131 }
132
133 #[must_use]
135 pub fn with_max_resolution(mut self, width: u32, height: u32) -> Self {
136 self.max_resolution = (width, height);
137 self
138 }
139
140 #[must_use]
142 pub fn with_min_resolution(mut self, width: u32, height: u32) -> Self {
143 self.min_resolution = (width, height);
144 self
145 }
146
147 #[must_use]
149 pub fn hls_standard() -> Self {
150 let mut ladder = Self::new(AbrStrategy::AppleHls);
151
152 ladder.add_rung(AbrRung::new(426, 240, 400_000, 64_000, "h264", "240p"));
154 ladder.add_rung(AbrRung::new(640, 360, 800_000, 96_000, "h264", "360p"));
155 ladder.add_rung(AbrRung::new(854, 480, 1_400_000, 128_000, "h264", "480p"));
156 ladder.add_rung(AbrRung::new(1280, 720, 2_800_000, 128_000, "h264", "720p"));
157 ladder.add_rung(AbrRung::new(
158 1920, 1080, 5_000_000, 192_000, "h264", "1080p",
159 ));
160
161 ladder
162 }
163
164 #[must_use]
166 pub fn youtube_standard() -> Self {
167 let mut ladder = Self::new(AbrStrategy::YouTube);
168
169 ladder.add_rung(AbrRung::new(426, 240, 300_000, 64_000, "vp9", "240p"));
171 ladder.add_rung(AbrRung::new(640, 360, 700_000, 96_000, "vp9", "360p"));
172 ladder.add_rung(AbrRung::new(854, 480, 1_000_000, 128_000, "vp9", "480p"));
173 ladder.add_rung(AbrRung::new(1280, 720, 2_500_000, 128_000, "vp9", "720p"));
174 ladder.add_rung(AbrRung::new(1920, 1080, 4_500_000, 192_000, "vp9", "1080p"));
175 ladder.add_rung(AbrRung::new(2560, 1440, 9_000_000, 192_000, "vp9", "1440p"));
176
177 ladder
178 }
179
180 #[must_use]
182 pub fn conservative() -> Self {
183 let mut ladder = Self::new(AbrStrategy::Conservative);
184
185 ladder.add_rung(AbrRung::new(640, 360, 600_000, 96_000, "h264", "360p"));
186 ladder.add_rung(AbrRung::new(1280, 720, 2_000_000, 128_000, "h264", "720p"));
187 ladder.add_rung(AbrRung::new(
188 1920, 1080, 4_000_000, 192_000, "h264", "1080p",
189 ));
190
191 ladder
192 }
193
194 #[must_use]
196 pub fn aggressive() -> Self {
197 let mut ladder = Self::new(AbrStrategy::Aggressive);
198
199 ladder.add_rung(AbrRung::new(426, 240, 400_000, 64_000, "h264", "240p"));
200 ladder.add_rung(AbrRung::new(640, 360, 800_000, 96_000, "h264", "360p"));
201 ladder.add_rung(AbrRung::new(854, 480, 1_400_000, 128_000, "h264", "480p"));
202 ladder.add_rung(AbrRung::new(960, 540, 2_000_000, 128_000, "h264", "540p"));
203 ladder.add_rung(AbrRung::new(1280, 720, 3_000_000, 128_000, "h264", "720p"));
204 ladder.add_rung(AbrRung::new(
205 1920, 1080, 5_500_000, 192_000, "h264", "1080p",
206 ));
207 ladder.add_rung(AbrRung::new(
208 2560, 1440, 10_000_000, 192_000, "h264", "1440p",
209 ));
210 ladder.add_rung(AbrRung::new(
211 3840, 2160, 20_000_000, 256_000, "h264", "2160p",
212 ));
213
214 ladder
215 }
216
217 #[must_use]
221 pub fn filter_by_source(mut self, source_width: u32, source_height: u32) -> Self {
222 self.rungs
223 .retain(|rung| rung.width <= source_width && rung.height <= source_height);
224 self
225 }
226
227 #[must_use]
229 pub fn rung_count(&self) -> usize {
230 self.rungs.len()
231 }
232
233 #[must_use]
235 pub fn get_rung(&self, index: usize) -> Option<&AbrRung> {
236 self.rungs.get(index)
237 }
238
239 #[must_use]
241 pub fn highest_quality(&self) -> Option<&AbrRung> {
242 self.rungs.last()
243 }
244
245 #[must_use]
247 pub fn lowest_quality(&self) -> Option<&AbrRung> {
248 self.rungs.first()
249 }
250}
251
252pub struct AbrLadderBuilder {
254 ladder: AbrLadder,
255}
256
257impl AbrLadderBuilder {
258 #[must_use]
260 pub fn new(strategy: AbrStrategy) -> Self {
261 Self {
262 ladder: AbrLadder::new(strategy),
263 }
264 }
265
266 #[must_use]
268 pub fn add_rung(mut self, rung: AbrRung) -> Self {
269 self.ladder.add_rung(rung);
270 self
271 }
272
273 #[must_use]
275 pub fn add(
276 mut self,
277 width: u32,
278 height: u32,
279 video_bitrate: u64,
280 audio_bitrate: u64,
281 codec: impl Into<String>,
282 profile_name: impl Into<String>,
283 ) -> Self {
284 let rung = AbrRung::new(
285 width,
286 height,
287 video_bitrate,
288 audio_bitrate,
289 codec,
290 profile_name,
291 );
292 self.ladder.add_rung(rung);
293 self
294 }
295
296 #[must_use]
298 pub fn max_resolution(mut self, width: u32, height: u32) -> Self {
299 self.ladder.max_resolution = (width, height);
300 self
301 }
302
303 #[must_use]
305 pub fn min_resolution(mut self, width: u32, height: u32) -> Self {
306 self.ladder.min_resolution = (width, height);
307 self
308 }
309
310 #[must_use]
312 pub fn build(self) -> AbrLadder {
313 self.ladder
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 #[test]
322 fn test_abr_rung_creation() {
323 let rung = AbrRung::new(1920, 1080, 5_000_000, 192_000, "h264", "1080p");
324
325 assert_eq!(rung.width, 1920);
326 assert_eq!(rung.height, 1080);
327 assert_eq!(rung.video_bitrate, 5_000_000);
328 assert_eq!(rung.audio_bitrate, 192_000);
329 assert_eq!(rung.total_bitrate(), 5_192_000);
330 assert_eq!(rung.codec, "h264");
331 assert_eq!(rung.profile_name, "1080p");
332 }
333
334 #[test]
335 fn test_abr_rung_quality_checks() {
336 let rung_240p = AbrRung::new(426, 240, 400_000, 64_000, "h264", "240p");
337 assert!(!rung_240p.is_hd());
338 assert!(!rung_240p.is_full_hd());
339 assert!(!rung_240p.is_4k());
340
341 let rung_720p = AbrRung::new(1280, 720, 2_800_000, 128_000, "h264", "720p");
342 assert!(rung_720p.is_hd());
343 assert!(!rung_720p.is_full_hd());
344 assert!(!rung_720p.is_4k());
345
346 let rung_1080p = AbrRung::new(1920, 1080, 5_000_000, 192_000, "h264", "1080p");
347 assert!(rung_1080p.is_hd());
348 assert!(rung_1080p.is_full_hd());
349 assert!(!rung_1080p.is_4k());
350
351 let rung_4k = AbrRung::new(3840, 2160, 20_000_000, 256_000, "h264", "2160p");
352 assert!(rung_4k.is_hd());
353 assert!(rung_4k.is_full_hd());
354 assert!(rung_4k.is_4k());
355 }
356
357 #[test]
358 fn test_abr_rung_resolution_string() {
359 let rung = AbrRung::new(1920, 1080, 5_000_000, 192_000, "h264", "1080p");
360 assert_eq!(rung.resolution_string(), "1920x1080");
361 }
362
363 #[test]
364 fn test_hls_standard_ladder() {
365 let ladder = AbrLadder::hls_standard();
366 assert_eq!(ladder.rung_count(), 5);
367 assert_eq!(ladder.strategy, AbrStrategy::AppleHls);
368
369 let lowest = ladder.lowest_quality().expect("should succeed in test");
370 assert_eq!(lowest.profile_name, "240p");
371
372 let highest = ladder.highest_quality().expect("should succeed in test");
373 assert_eq!(highest.profile_name, "1080p");
374 }
375
376 #[test]
377 fn test_youtube_standard_ladder() {
378 let ladder = AbrLadder::youtube_standard();
379 assert_eq!(ladder.rung_count(), 6);
380 assert_eq!(ladder.strategy, AbrStrategy::YouTube);
381
382 let highest = ladder.highest_quality().expect("should succeed in test");
383 assert_eq!(highest.profile_name, "1440p");
384 }
385
386 #[test]
387 fn test_conservative_ladder() {
388 let ladder = AbrLadder::conservative();
389 assert_eq!(ladder.rung_count(), 3);
390 assert_eq!(ladder.strategy, AbrStrategy::Conservative);
391 }
392
393 #[test]
394 fn test_aggressive_ladder() {
395 let ladder = AbrLadder::aggressive();
396 assert_eq!(ladder.rung_count(), 8);
397 assert_eq!(ladder.strategy, AbrStrategy::Aggressive);
398 }
399
400 #[test]
401 fn test_ladder_filtering() {
402 let ladder = AbrLadder::hls_standard();
403 let filtered = ladder.filter_by_source(1280, 720);
404
405 assert_eq!(filtered.rung_count(), 4); let highest = filtered.highest_quality().expect("should succeed in test");
407 assert_eq!(highest.profile_name, "720p");
408 }
409
410 #[test]
411 fn test_ladder_builder() {
412 let ladder = AbrLadderBuilder::new(AbrStrategy::Custom)
413 .add(640, 360, 800_000, 96_000, "h264", "360p")
414 .add(1280, 720, 2_800_000, 128_000, "h264", "720p")
415 .add(1920, 1080, 5_000_000, 192_000, "h264", "1080p")
416 .max_resolution(1920, 1080)
417 .min_resolution(640, 360)
418 .build();
419
420 assert_eq!(ladder.rung_count(), 3);
421 assert_eq!(ladder.strategy, AbrStrategy::Custom);
422 }
423
424 #[test]
425 fn test_ladder_sorting() {
426 let mut ladder = AbrLadder::new(AbrStrategy::Custom);
427
428 ladder.add_rung(AbrRung::new(
430 1920, 1080, 5_000_000, 192_000, "h264", "1080p",
431 ));
432 ladder.add_rung(AbrRung::new(640, 360, 800_000, 96_000, "h264", "360p"));
433 ladder.add_rung(AbrRung::new(1280, 720, 2_800_000, 128_000, "h264", "720p"));
434
435 assert_eq!(ladder.rungs[0].profile_name, "360p");
437 assert_eq!(ladder.rungs[1].profile_name, "720p");
438 assert_eq!(ladder.rungs[2].profile_name, "1080p");
439 }
440}