hls_audio_server/
m3u8.rs

1//! Manages the playlist.
2
3use std::collections::VecDeque;
4
5use chrono::Utc;
6use m3u8_rs::{MediaPlaylist, MediaSegment};
7
8/// HLS Configuration.
9pub struct HLSConfig {
10    /// The amount of segments that should be kept in the playlist.
11    pub segments_to_keep: usize,
12    /// Segment duration in seconds.
13    pub segment_duration: f32,
14    /// Public URI of such form: `https://example.com:6969/` where the port is optional and the
15    /// trailing slash is not.
16    pub uri: String,
17    /// The file extension for files served over HLS. For example: `.aac`
18    pub file_extension: String,
19}
20
21/// Stores the state of the playlist.
22pub struct Playlist {
23    /// EXT-X-MEDIA-SEQUENCE
24    pub media_sequence: u64,
25    pub segments: VecDeque<MediaSegment>,
26    pub config: HLSConfig,
27}
28
29impl Playlist {
30    /// Create a new playlist with a given config.
31    pub fn new(config: HLSConfig) -> Self {
32        Self {
33            media_sequence: 0,
34            segments: VecDeque::new(),
35            config,
36        }
37    }
38
39    /// Add a new segment to the playlist and remove an old one if necessary.
40    ///
41    /// Returns the name of the just added segment and possibly removed segments.
42    pub fn add_segment(&mut self) -> (String, Option<String>) {
43        let removed_segment_name = if self.segments.len() >= self.config.segments_to_keep {
44            self.media_sequence += 1;
45            Some(
46                self.segments
47                    .pop_front()
48                    .expect("Could not remove a segment. Should never happen because if the previous if statement.")
49                    .uri
50                    .split('/')
51                    .nth(3)
52                    .expect("Could not get the segment name from its URI")
53                    .to_string(),
54            )
55        } else {
56            None
57        };
58
59        let timestamp = Utc::now().timestamp_millis();
60
61        let added_segment_name =
62            String::from("segment-") + &timestamp.to_string() + &self.config.file_extension;
63
64        let uri = self.config.uri.to_owned() + &added_segment_name;
65
66        let segment = MediaSegment {
67            uri,
68            duration: self.config.segment_duration,
69            title: None,
70            byte_range: None,
71            discontinuity: false,
72            key: None,
73            map: None,
74            program_date_time: None,
75            daterange: None,
76            unknown_tags: Vec::new(),
77        };
78        self.segments.push_back(segment);
79
80        (added_segment_name, removed_segment_name)
81    }
82
83    /// Generate the playlist based on the current state.
84    pub fn generate_playlist(&mut self) -> Vec<u8> {
85        let playlist = MediaPlaylist {
86            version: Some(3),
87            target_duration: self.config.segment_duration.ceil(),
88            media_sequence: self.media_sequence,
89            segments: self.segments.clone().into(),
90            discontinuity_sequence: 0,
91            end_list: false,
92            playlist_type: None,
93            i_frames_only: false,
94            start: None,
95            independent_segments: false,
96            unknown_tags: Vec::new(),
97        };
98
99        let mut generated_playlist = Vec::new();
100
101        generated_playlist.clear();
102        playlist.write_to(&mut generated_playlist).unwrap();
103
104        generated_playlist
105    }
106}