diff --git a/src/lib.rs b/src/lib.rs index ec65acf..0362181 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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() //! }; diff --git a/src/parser.rs b/src/parser.rs index 67c639f..a698e22 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -476,13 +476,15 @@ fn media_playlist_from_tags(mut tags: Vec) -> 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); diff --git a/src/playlist.rs b/src/playlist.rs index 6ff1193..6cd24ab 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -738,7 +738,7 @@ pub struct MediaPlaylist { pub target_duration: u64, /// `#EXT-X-MEDIA-SEQUENCE:` pub media_sequence: u64, - pub segments: Vec, + pub segments: Vec, /// `#EXT-X-DISCONTINUITY-SEQUENCE:` 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(&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, /// `#EXT-` pub unknown_tags: Vec, - - // LL-HLS specific fields - pub parts: Vec, } 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)?; } diff --git a/tests/lib.rs b/tests/lib.rs index f15649c..5dd161c 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -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),