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 //! 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 { //! let playlist = MediaPlaylist {
//! version: Some(6), //! version: Some(6),
@ -48,12 +48,12 @@
//! end_list: true, //! end_list: true,
//! playlist_type: Some(MediaPlaylistType::Vod), //! playlist_type: Some(MediaPlaylistType::Vod),
//! segments: vec![ //! segments: vec![
//! MediaSegment { //! MediaSegmentType::Full(MediaSegment {
//! uri: "20140311T113819-01-338559live.ts".into(), //! uri: "20140311T113819-01-338559live.ts".into(),
//! duration: 2.002, //! duration: 2.002,
//! title: Some("title".into()), //! title: Some("title".into()),
//! ..Default::default() //! ..Default::default()
//! }, //! }),
//! ], //! ],
//! ..Default::default() //! ..Default::default()
//! }; //! };
@ -69,18 +69,18 @@
//! //!
//! ``` //! ```
//! use std::sync::atomic::Ordering; //! 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); //! WRITE_OPT_FLOAT_PRECISION.store(5, Ordering::Relaxed);
//! //!
//! let playlist = MediaPlaylist { //! let playlist = MediaPlaylist {
//! target_duration: 3, //! target_duration: 3,
//! segments: vec![ //! segments: vec![
//! MediaSegment { //! MediaSegmentType::Full(MediaSegment {
//! duration: 2.9, //! duration: 2.9,
//! title: Some("title".into()), //! title: Some("title".into()),
//! ..Default::default() //! ..Default::default()
//! }, //! }),
//! ], //! ],
//! ..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.key = encryption_key.clone();
next_segment.map = map.clone(); next_segment.map = map.clone();
next_segment.uri = u; next_segment.uri = u;
media_playlist.segments.push(next_segment); media_playlist
.segments
.push(MediaSegmentType::Full(next_segment));
next_segment = MediaSegment::empty(); next_segment = MediaSegment::empty();
encryption_key = None; encryption_key = None;
map = None; map = None;
} }
SegmentTag::Part(p) => { SegmentTag::Part(p) => {
next_segment.parts.push(p); media_playlist.segments.push(MediaSegmentType::Partial(p));
} }
SegmentTag::Unknown(t) => { SegmentTag::Unknown(t) => {
next_segment.unknown_tags.push(t); next_segment.unknown_tags.push(t);

View File

@ -738,7 +738,7 @@ pub struct MediaPlaylist {
pub target_duration: u64, pub target_duration: u64,
/// `#EXT-X-MEDIA-SEQUENCE:<number>` /// `#EXT-X-MEDIA-SEQUENCE:<number>`
pub media_sequence: u64, pub media_sequence: u64,
pub segments: Vec<MediaSegment>, pub segments: Vec<MediaSegmentType>,
/// `#EXT-X-DISCONTINUITY-SEQUENCE:<number>` /// `#EXT-X-DISCONTINUITY-SEQUENCE:<number>`
pub discontinuity_sequence: u64, pub discontinuity_sequence: u64,
/// `#EXT-X-ENDLIST` /// `#EXT-X-ENDLIST`
@ -868,6 +868,23 @@ impl Default for MediaPlaylistType {
// Media Segment // 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) /// 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. /// is specified by a URI and optionally a byte range.
#[derive(Debug, Default, PartialEq, Clone)] #[derive(Debug, Default, PartialEq, Clone)]
@ -891,9 +908,6 @@ pub struct MediaSegment {
pub daterange: Option<DateRange>, pub daterange: Option<DateRange>,
/// `#EXT-` /// `#EXT-`
pub unknown_tags: Vec<ExtTag>, pub unknown_tags: Vec<ExtTag>,
// LL-HLS specific fields
pub parts: Vec<Part>,
} }
impl MediaSegment { impl MediaSegment {
@ -932,9 +946,6 @@ impl MediaSegment {
v.write_attributes_to(w)?; v.write_attributes_to(w)?;
writeln!(w)?; writeln!(w)?;
} }
for part in &self.parts {
part.write_to(w)?;
}
for unknown_tag in &self.unknown_tags { for unknown_tag in &self.unknown_tags {
writeln!(w, "{}", unknown_tag)?; writeln!(w, "{}", unknown_tag)?;
} }

View File

@ -208,12 +208,12 @@ fn create_segment_float_inf() {
discontinuity_sequence: 1234, discontinuity_sequence: 1234,
end_list: true, end_list: true,
playlist_type: Some(MediaPlaylistType::Vod), playlist_type: Some(MediaPlaylistType::Vod),
segments: vec![MediaSegment { segments: vec![MediaSegmentType::Full(MediaSegment {
uri: "20140311T113819-01-338559live.ts".into(), uri: "20140311T113819-01-338559live.ts".into(),
duration: 2.000f32, duration: 2.000f32,
title: Some("title".into()), title: Some("title".into()),
..Default::default() ..Default::default()
}], })],
..Default::default() ..Default::default()
}); });
@ -336,12 +336,12 @@ fn create_and_parse_media_playlist_empty() {
fn create_and_parse_media_playlist_single_segment() { fn create_and_parse_media_playlist_single_segment() {
let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist { let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist {
target_duration: 2, target_duration: 2,
segments: vec![MediaSegment { segments: vec![MediaSegmentType::Full(MediaSegment {
uri: "20140311T113819-01-338559live.ts".into(), uri: "20140311T113819-01-338559live.ts".into(),
duration: 2.002, duration: 2.002,
title: Some("hey".into()), title: Some("hey".into()),
..Default::default() ..Default::default()
}], })],
..Default::default() ..Default::default()
}); });
let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original); 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(), other_attributes: Default::default(),
}), }),
independent_segments: true, independent_segments: true,
segments: vec![MediaSegment { segments: vec![MediaSegmentType::Full(MediaSegment {
uri: "20140311T113819-01-338559live.ts".into(), uri: "20140311T113819-01-338559live.ts".into(),
duration: 2.002, duration: 2.002,
title: Some("338559".into()), title: Some("338559".into()),
@ -414,7 +414,7 @@ fn create_and_parse_media_playlist_full() {
rest: Some("DURATION=2.002".into()), rest: Some("DURATION=2.002".into()),
}], }],
..Default::default() ..Default::default()
}], })],
unknown_tags: vec![], unknown_tags: vec![],
server_control: Default::default(), server_control: Default::default(),
@ -492,7 +492,8 @@ fn create_and_parse_media_playlist_llhls() {
other_attributes: Default::default(), other_attributes: Default::default(),
}), }),
independent_segments: true, independent_segments: true,
segments: vec![MediaSegment { segments: vec![
MediaSegmentType::Full(MediaSegment {
uri: "20140311T113819-01-338559live.ts".into(), uri: "20140311T113819-01-338559live.ts".into(),
duration: 2.002, duration: 2.002,
title: Some("338559".into()), title: Some("338559".into()),
@ -538,8 +539,9 @@ fn create_and_parse_media_playlist_llhls() {
other_attributes: Default::default(), other_attributes: Default::default(),
}), }),
unknown_tags: vec![], unknown_tags: vec![],
parts: vec![ ..Default::default()
Part { }),
MediaSegmentType::Partial(Part {
uri: "part0.ts".into(), uri: "part0.ts".into(),
duration: 0.5, duration: 0.5,
independent: true, independent: true,
@ -548,8 +550,8 @@ fn create_and_parse_media_playlist_llhls() {
length: 50000, length: 50000,
offset: Some(0), offset: Some(0),
}), }),
}, }),
Part { MediaSegmentType::Partial(Part {
uri: "part1.ts".into(), uri: "part1.ts".into(),
duration: 0.5, duration: 0.5,
independent: false, independent: false,
@ -558,10 +560,8 @@ fn create_and_parse_media_playlist_llhls() {
length: 50000, length: 50000,
offset: Some(50000), offset: Some(50000),
}), }),
}, }),
], ],
..Default::default()
}],
unknown_tags: vec![], unknown_tags: vec![],
server_control: Some(ServerControl { server_control: Some(ServerControl {
can_skip_until: Some(12.0), can_skip_until: Some(12.0),