feat: create enum to represent partial/full segments

This commit is contained in:
2025-06-12 21:59:33 +01:00
parent f937160344
commit d76ff96326
4 changed files with 98 additions and 85 deletions

View File

@ -38,7 +38,7 @@
//! Creating a playlist and writing it back to a vec/file
//!
//! ```
//! use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment};
//! use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, MediaSegmentType};
//!
//! let playlist = MediaPlaylist {
//! version: Some(6),
@ -48,12 +48,12 @@
//! end_list: true,
//! playlist_type: Some(MediaPlaylistType::Vod),
//! segments: vec![
//! MediaSegment {
//! MediaSegmentType::Full(MediaSegment {
//! uri: "20140311T113819-01-338559live.ts".into(),
//! duration: 2.002,
//! title: Some("title".into()),
//! ..Default::default()
//! },
//! }),
//! ],
//! ..Default::default()
//! };
@ -69,18 +69,18 @@
//!
//! ```
//! use std::sync::atomic::Ordering;
//! use m3u8_rs::{WRITE_OPT_FLOAT_PRECISION, MediaPlaylist, MediaSegment};
//! use m3u8_rs::{WRITE_OPT_FLOAT_PRECISION, MediaPlaylist, MediaSegment, MediaSegmentType};
//!
//! WRITE_OPT_FLOAT_PRECISION.store(5, Ordering::Relaxed);
//!
//! let playlist = MediaPlaylist {
//! target_duration: 3,
//! segments: vec![
//! MediaSegment {
//! MediaSegmentType::Full(MediaSegment {
//! duration: 2.9,
//! title: Some("title".into()),
//! ..Default::default()
//! },
//! }),
//! ],
//! ..Default::default()
//! };

View File

@ -476,13 +476,15 @@ fn media_playlist_from_tags(mut tags: Vec<MediaPlaylistTag>) -> MediaPlaylist {
next_segment.key = encryption_key.clone();
next_segment.map = map.clone();
next_segment.uri = u;
media_playlist.segments.push(next_segment);
media_playlist
.segments
.push(MediaSegmentType::Full(next_segment));
next_segment = MediaSegment::empty();
encryption_key = None;
map = None;
}
SegmentTag::Part(p) => {
next_segment.parts.push(p);
media_playlist.segments.push(MediaSegmentType::Partial(p));
}
SegmentTag::Unknown(t) => {
next_segment.unknown_tags.push(t);

View File

@ -738,7 +738,7 @@ pub struct MediaPlaylist {
pub target_duration: u64,
/// `#EXT-X-MEDIA-SEQUENCE:<number>`
pub media_sequence: u64,
pub segments: Vec<MediaSegment>,
pub segments: Vec<MediaSegmentType>,
/// `#EXT-X-DISCONTINUITY-SEQUENCE:<number>`
pub discontinuity_sequence: u64,
/// `#EXT-X-ENDLIST`
@ -868,6 +868,23 @@ impl Default for MediaPlaylistType {
// Media Segment
// -----------------------------------------------------------------------------------------------
#[derive(Debug, PartialEq, Clone)]
pub enum MediaSegmentType {
/// Regular HLS segment (#EXTINF)
Full(MediaSegment),
/// HLS-LL: Partial segment (#EXT-X-PART)
Partial(Part),
}
impl MediaSegmentType {
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
match self {
MediaSegmentType::Full(s) => s.write_to(w),
MediaSegmentType::Partial(s) => s.write_to(w),
}
}
}
/// A [Media Segment](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-3)
/// is specified by a URI and optionally a byte range.
#[derive(Debug, Default, PartialEq, Clone)]
@ -891,9 +908,6 @@ pub struct MediaSegment {
pub daterange: Option<DateRange>,
/// `#EXT-`
pub unknown_tags: Vec<ExtTag>,
// LL-HLS specific fields
pub parts: Vec<Part>,
}
impl MediaSegment {
@ -932,9 +946,6 @@ impl MediaSegment {
v.write_attributes_to(w)?;
writeln!(w)?;
}
for part in &self.parts {
part.write_to(w)?;
}
for unknown_tag in &self.unknown_tags {
writeln!(w, "{}", unknown_tag)?;
}

View File

@ -208,12 +208,12 @@ fn create_segment_float_inf() {
discontinuity_sequence: 1234,
end_list: true,
playlist_type: Some(MediaPlaylistType::Vod),
segments: vec![MediaSegment {
segments: vec![MediaSegmentType::Full(MediaSegment {
uri: "20140311T113819-01-338559live.ts".into(),
duration: 2.000f32,
title: Some("title".into()),
..Default::default()
}],
})],
..Default::default()
});
@ -336,12 +336,12 @@ fn create_and_parse_media_playlist_empty() {
fn create_and_parse_media_playlist_single_segment() {
let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist {
target_duration: 2,
segments: vec![MediaSegment {
segments: vec![MediaSegmentType::Full(MediaSegment {
uri: "20140311T113819-01-338559live.ts".into(),
duration: 2.002,
title: Some("hey".into()),
..Default::default()
}],
})],
..Default::default()
});
let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original);
@ -364,7 +364,7 @@ fn create_and_parse_media_playlist_full() {
other_attributes: Default::default(),
}),
independent_segments: true,
segments: vec![MediaSegment {
segments: vec![MediaSegmentType::Full(MediaSegment {
uri: "20140311T113819-01-338559live.ts".into(),
duration: 2.002,
title: Some("338559".into()),
@ -414,7 +414,7 @@ fn create_and_parse_media_playlist_full() {
rest: Some("DURATION=2.002".into()),
}],
..Default::default()
}],
})],
unknown_tags: vec![],
server_control: Default::default(),
@ -492,76 +492,76 @@ fn create_and_parse_media_playlist_llhls() {
other_attributes: Default::default(),
}),
independent_segments: true,
segments: vec![MediaSegment {
uri: "20140311T113819-01-338559live.ts".into(),
duration: 2.002,
title: Some("338559".into()),
byte_range: Some(ByteRange {
length: 137116,
offset: Some(4559),
}),
discontinuity: true,
key: Some(Key {
method: KeyMethod::None,
uri: Some("https://secure.domain.com".into()),
iv: Some("0xb059217aa2649ce170b734".into()),
keyformat: Some("xXkeyformatXx".into()),
keyformatversions: Some("xXFormatVers".into()),
}),
map: Some(Map {
uri: "www.map-uri.com".into(),
segments: vec![
MediaSegmentType::Full(MediaSegment {
uri: "20140311T113819-01-338559live.ts".into(),
duration: 2.002,
title: Some("338559".into()),
byte_range: Some(ByteRange {
length: 137116,
offset: Some(4559),
}),
other_attributes: Default::default(),
}),
program_date_time: Some(
chrono::FixedOffset::east(8 * 3600)
.ymd(2010, 2, 19)
.and_hms_milli(14, 54, 23, 31),
),
daterange: Some(DateRange {
id: "9999".into(),
class: Some("class".into()),
start_date: chrono::FixedOffset::east(8 * 3600)
.ymd(2010, 2, 19)
.and_hms_milli(14, 54, 23, 31),
end_date: None,
duration: None,
planned_duration: Some("40.000".parse().unwrap()),
x_prefixed: Some(HashMap::from([(
"X-client-attribute".into(),
"whatever".into(),
)])),
end_on_next: false,
other_attributes: Default::default(),
}),
unknown_tags: vec![],
parts: vec![
Part {
uri: "part0.ts".into(),
duration: 0.5,
independent: true,
gap: false,
discontinuity: true,
key: Some(Key {
method: KeyMethod::None,
uri: Some("https://secure.domain.com".into()),
iv: Some("0xb059217aa2649ce170b734".into()),
keyformat: Some("xXkeyformatXx".into()),
keyformatversions: Some("xXFormatVers".into()),
}),
map: Some(Map {
uri: "www.map-uri.com".into(),
byte_range: Some(ByteRange {
length: 50000,
offset: Some(0),
length: 137116,
offset: Some(4559),
}),
},
Part {
uri: "part1.ts".into(),
duration: 0.5,
independent: false,
gap: false,
byte_range: Some(ByteRange {
length: 50000,
offset: Some(50000),
}),
},
],
..Default::default()
}],
other_attributes: Default::default(),
}),
program_date_time: Some(
chrono::FixedOffset::east(8 * 3600)
.ymd(2010, 2, 19)
.and_hms_milli(14, 54, 23, 31),
),
daterange: Some(DateRange {
id: "9999".into(),
class: Some("class".into()),
start_date: chrono::FixedOffset::east(8 * 3600)
.ymd(2010, 2, 19)
.and_hms_milli(14, 54, 23, 31),
end_date: None,
duration: None,
planned_duration: Some("40.000".parse().unwrap()),
x_prefixed: Some(HashMap::from([(
"X-client-attribute".into(),
"whatever".into(),
)])),
end_on_next: false,
other_attributes: Default::default(),
}),
unknown_tags: vec![],
..Default::default()
}),
MediaSegmentType::Partial(Part {
uri: "part0.ts".into(),
duration: 0.5,
independent: true,
gap: false,
byte_range: Some(ByteRange {
length: 50000,
offset: Some(0),
}),
}),
MediaSegmentType::Partial(Part {
uri: "part1.ts".into(),
duration: 0.5,
independent: false,
gap: false,
byte_range: Some(ByteRange {
length: 50000,
offset: Some(50000),
}),
}),
],
unknown_tags: vec![],
server_control: Some(ServerControl {
can_skip_until: Some(12.0),