1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
use anyhow::Result;
use gst::prelude::*;
use gst::ElementFactory;
use gst_hlssink3::hlssink3::HlsSink3PlaylistType;
/// A builder for configuring and creating the `hlssink3` GStreamer element.
///
/// The builder provides an interface for setting properties like `playlist-location`,
/// `location`, `target-duration`, `playlist-length`, `max-files`, and `playlist-type`,
/// allowing for flexible configuration of the HLS encoder based on user requirements.
#[derive(Debug, Clone)]
pub struct HlsSink3Builder {
element: gst::Element,
}
impl HlsSink3Builder {
/// Creates a new `HlsSink3EncoderBuilder` instance with default properties.
///
/// The `hlssink3` element is created and initialized with default properties
/// suitable for general HLS encoding tasks.
pub fn new(location: &str, playlist_location: &str) -> Self {
let element = ElementFactory::make_with_name("hlssink", Some("hls_sink"))
.expect("Failed to create hlssink3 element");
// Set default values for HLS properties
element.set_property("target-duration", 5u32); // Default segment duration is 5 seconds
element.set_property("playlist-length", 0u32); // Default playlist length is 10 segments
element.set_property("max-files", 0u32); // Keep a maximum of 5 segments at a time
// element.set_property("playlist-type", HlsSink3PlaylistType::Vod); // Default playlist type is VoD
element.set_property("location", format!("{}/segment_%02d.ts", location));
element.set_property("playlist-location", format!("{}/playlist.m3u8", playlist_location));
Self { element }
}
/// Sets the `playlist-location` property of the `hlssink3` element.
///
/// # Arguments
///
/// * `playlist_location`: The location (file path) of the main HLS playlist file.
pub fn with_playlist_location(mut self, playlist_location: &str) -> Self {
self.element
.set_property("playlist-location", playlist_location);
self
}
/// Sets the `location` property of the `hlssink3` element.
///
/// # Arguments
///
/// * `segment_location`: The location (file path) pattern for the HLS segments.
///
/// This path should include a placeholder like `%05d.ts` to indicate the segment numbering.
pub fn with_segment_location(mut self, segment_location: &str) -> Self {
self.element.set_property("location", segment_location);
self
}
/// Sets the `target-duration` property of the `hlssink3` element.
///
/// # Arguments
///
/// * `duration`: The target duration for each HLS segment in seconds.
///
/// Specifies the approximate length of each HLS segment, typically in the range of 2-10 seconds.
pub fn with_target_duration(mut self, duration: u32) -> Self {
self.element.set_property("target-duration", duration);
self
}
/// Sets the `playlist-length` property of the `hlssink3` element.
///
/// # Arguments
///
/// * `length`: The number of segments to be maintained in the playlist.
///
/// Determines the number of media segments listed in the HLS playlist at any given time.
pub fn with_playlist_length(mut self, length: u32) -> Self {
self.element.set_property("playlist-length", length);
self
}
/// Sets the `max-files` property of the `hlssink3` element.
///
/// # Arguments
///
/// * `max_files`: The maximum number of segments to keep at a time.
///
/// Older segments will be deleted once this limit is reached, reducing storage space usage.
pub fn with_max_files(mut self, max_files: u32) -> Self {
self.element.set_property("max-files", max_files);
self
}
/// Sets the `playlist-type` property of the `hlssink3` element.
///
/// # Arguments
///
/// * `playlist_type`: The type of playlist, either "event", "vod", or "live".
///
/// The playlist type defines whether the playlist is for a live stream or video on demand.
pub fn with_playlist_type(mut self, playlist_type: HlsSink3PlaylistType) -> Result<Self> {
if playlist_type != HlsSink3PlaylistType::Unspecified {
self.element
.set_property("playlist-type", playlist_type);
Ok(self)
} else {
Err(anyhow::anyhow!("Invalid playlist type: {:?}", playlist_type))
}
}
/// Builds and returns the configured `hlssink3` instance.
///
/// # Returns
///
/// A `Result` containing the built `gst::Element` instance or an error if the element could not be created.
pub fn build(self) -> Result<gst::Element> {
Ok(self.element)
}
}
#[cfg(test)]
mod tests {
use super::*;
use gst::init;
#[test]
fn test_hlssink3_encoder_builder_new() {
init().unwrap();
let builder = HlsSink3Builder::new("output/segment_%05d.ts", "output/playlist.m3u8");
assert_eq!(builder.element.property::<u32>("target-duration"), 5);
assert_eq!(builder.element.property::<u32>("playlist-length"), 10);
assert_eq!(builder.element.property::<u32>("max-files"), 5);
assert_eq!(
builder
.element
.property::<gst::glib::Value>("playlist-type")
.serialize()
.unwrap(),
"vod"
);
}
#[test]
fn test_hlssink3_encoder_builder_with_playlist_location() {
init().unwrap();
let builder = HlsSink3Builder::new("output/segment_%05d.ts", "output/playlist.m3u8")
.with_playlist_location("output/custom_playlist.m3u8");
assert_eq!(
builder.element.property::<String>("playlist-location"),
"output/custom_playlist.m3u8"
);
}
#[test]
fn test_hlssink3_encoder_builder_with_segment_location() {
init().unwrap();
let builder = HlsSink3Builder::new("output/segment_%05d.ts", "output/playlist.m3u8")
.with_segment_location("output/custom_segment_%03d.ts");
assert_eq!(
builder.element.property::<String>("location"),
"output/custom_segment_%03d.ts"
);
}
#[test]
fn test_hlssink3_encoder_builder_with_target_duration() {
init().unwrap();
let builder = HlsSink3Builder::new("output/segment_%05d.ts", "output/playlist.m3u8")
.with_target_duration(8);
assert_eq!(builder.element.property::<u32>("target-duration"), 8);
}
#[test]
fn test_hlssink3_encoder_builder_with_playlist_length() {
init().unwrap();
let builder = HlsSink3Builder::new("output/segment_%05d.ts", "output/playlist.m3u8")
.with_playlist_length(20);
assert_eq!(builder.element.property::<u32>("playlist-length"), 20);
}
#[test]
fn test_hlssink3_encoder_builder_with_max_files() {
init().unwrap();
let builder = HlsSink3Builder::new("output/segment_%05d.ts", "output/playlist.m3u8")
.with_max_files(3);
assert_eq!(builder.element.property::<u32>("max-files"), 3);
}
#[test]
fn test_hlssink3_encoder_builder_with_playlist_type_valid() {
init().unwrap();
let builder = HlsSink3Builder::new("output/segment_%05d.ts", "output/playlist.m3u8")
.with_playlist_type(HlsSink3PlaylistType::Event)
.unwrap();
assert_eq!(
builder
.element
.property::<HlsSink3PlaylistType>("playlist-type"), HlsSink3PlaylistType::Event
);
}
#[test]
fn test_hlssink3_encoder_builder_with_playlist_type_invalid() {
init().unwrap();
let result = HlsSink3Builder::new("output/segment_%05d.ts", "output/playlist.m3u8")
.with_playlist_type(HlsSink3PlaylistType::Unspecified);
assert!(result.is_err());
}
#[test]
fn test_hlssink3_encoder_builder_build() {
init().unwrap();
let result =
HlsSink3Builder::new("output/segment_%05d.ts", "output/playlist.m3u8").build();
assert!(result.is_ok());
}
}