Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions crates/iori-hls/src/m3u8_rs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,37 @@ impl From<m3u8_rs::Resolution> for Resolution {

impl From<m3u8_rs::MediaPlaylist> for MediaPlaylist {
fn from(value: m3u8_rs::MediaPlaylist) -> Self {
let mut current_part_index = value.discontinuity_sequence;
let mut segments = Vec::with_capacity(value.segments.len());

for segment in value.segments {
if segment.discontinuity {
current_part_index += 1;
}

let segment: crate::models::MediaSegment = (segment, current_part_index).into();
segments.push(segment);
}

MediaPlaylist {
media_sequence: value.media_sequence,
segments: value.segments.into_iter().map(MediaSegment::from).collect(),
segments,
end_list: value.end_list,
discontinuity_sequence: value.discontinuity_sequence,
}
}
}

impl From<m3u8_rs::MediaSegment> for MediaSegment {
fn from(value: m3u8_rs::MediaSegment) -> Self {
impl From<(m3u8_rs::MediaSegment, u64)> for MediaSegment {
fn from((value, part_index): (m3u8_rs::MediaSegment, u64)) -> Self {
MediaSegment {
uri: value.uri,
duration: (value.duration as f64).into(),
title: value.title,
byte_range: value.byte_range.map(ByteRange::from),
key: value.key.map(Key::from),
map: value.map.map(Map::from),
part_index,
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion crates/iori-hls/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ impl From<hls::MediaType> for AlternativeMediaType {
#[derive(Debug, Clone, PartialEq, Comparable)]
pub struct MediaPlaylist {
pub media_sequence: u64,
pub discontinuity_sequence: u64,
pub segments: Vec<MediaSegment>,
pub end_list: bool,
}
Expand All @@ -116,6 +117,7 @@ pub struct MediaSegment {
pub byte_range: Option<ByteRange>,
pub key: Option<Key>,
pub map: Option<Map>,
pub part_index: u64,
}

impl<'a>
Expand All @@ -125,15 +127,17 @@ impl<'a>
Option<ByteRange>,
Option<Key>,
Option<Map>,
u64,
)> for MediaSegment
{
fn from(
(inf, uri, byte_range, key, map): (
(inf, uri, byte_range, key, map, part_index): (
hls::Inf<'a>,
Cow<'a, str>,
Option<ByteRange>,
Option<Key>,
Option<Map>,
u64,
),
) -> Self {
Self {
Expand All @@ -146,6 +150,7 @@ impl<'a>
byte_range,
key,
map,
part_index,
}
}
}
Expand Down
23 changes: 21 additions & 2 deletions crates/iori-hls/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,28 @@ pub fn parse_playlist_res(input: &[u8]) -> Result<Playlist, M3u8ParseError> {

let mut is_master = false;

// master playlist
// <FOR MASTER PLAYLIST>
let mut variants = Vec::new();
let mut alternatives = Vec::new();

// media playlist
// <FOR MEDIA PLAYLIST>
// [RFC8216 Section 4.3.3.2](https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.2)
// > If the Media Playlist file does not contain an EXT-X-MEDIA-SEQUENCE
// > tag, then the Media Sequence Number of the first Media Segment in the
// > Media Playlist SHALL be considered to be 0.
let mut media_sequence = 0;
// [RFC8216 Section 4.3.3.3](https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.3)
// > If the Media Playlist does not contain an EXT-X-DISCONTINUITY-
// > SEQUENCE tag, then the Discontinuity Sequence Number of the first
// > Media Segment in the Playlist SHALL be considered to be 0.
let mut discontinuity_sequence = 0;
let mut segments: Vec<MediaSegment> = Vec::new();
let mut end_list = false;

// Maps(initial segment information) and keys(encryption information)
let mut current_key: Option<Key> = None;
let mut current_map: Option<Map> = None;
let mut current_part_index = 0;

// Pending tags, which should be cleared after the URI line is processed
let mut pending_inf: Option<hls::Inf> = None;
Expand All @@ -35,6 +45,13 @@ pub fn parse_playlist_res(input: &[u8]) -> Result<Playlist, M3u8ParseError> {
match line {
HlsLine::KnownTag(KnownTag::Hls(tag)) => match tag {
hls::Tag::MediaSequence(seq) => media_sequence = seq.media_sequence(),
hls::Tag::DiscontinuitySequence(seq) => {
discontinuity_sequence = seq.discontinuity_sequence();
current_part_index = discontinuity_sequence;
}
hls::Tag::Discontinuity(_) => {
current_part_index += 1;
}
hls::Tag::Inf(inf) => pending_inf = Some(inf),
hls::Tag::Byterange(range) => pending_byterange = Some(range.into()),
hls::Tag::Key(key) => current_key = Some(key.into()),
Expand Down Expand Up @@ -63,6 +80,7 @@ pub fn parse_playlist_res(input: &[u8]) -> Result<Playlist, M3u8ParseError> {
pending_byterange.take(),
current_key.clone(),
current_map.clone(),
current_part_index,
)
.into(),
);
Expand All @@ -84,6 +102,7 @@ pub fn parse_playlist_res(input: &[u8]) -> Result<Playlist, M3u8ParseError> {
} else {
Playlist::MediaPlaylist(MediaPlaylist {
media_sequence,
discontinuity_sequence,
segments,
end_list,
})
Expand Down
11 changes: 0 additions & 11 deletions crates/iori-hls/tests/cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,6 @@ fn test_accuracy_archive_01() {
assert_changes!(old_result, new_result, Changed::Unchanged);
}

#[test]
fn test_accuracy_archive_02() {
let data = include_bytes!("./fixtures/archive_02.m3u8");
let old_result = iori_hls::m3u8_rs::parse_playlist_res(data);
let new_result = iori_hls::parse::parse_playlist_res(data);

let old_result = old_result.expect("Old parse engine should not error");
let new_result = new_result.expect("New parse engine should not error");
assert_changes!(old_result, new_result, Changed::Unchanged);
}

#[test]
fn test_accuracy_archive_03() {
let data = include_bytes!("./fixtures/archive_03.m3u8");
Expand Down
57 changes: 57 additions & 0 deletions crates/iori-hls/tests/discontinuity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use comparable::{Changed, assert_changes};

#[test]
fn test_accuracy_discontinuity() {
let data = include_bytes!("./fixtures/discontinuity/playlist.m3u8");
let old_result = iori_hls::m3u8_rs::parse_playlist_res(data);
let new_result = iori_hls::parse::parse_playlist_res(data);

let old_result = old_result.expect("Old parse engine should not error");
let new_result = new_result.expect("New parse engine should not error");
assert_changes!(old_result, new_result, Changed::Unchanged);

let media_playlist = match new_result {
iori_hls::Playlist::MediaPlaylist(media_playlist) => media_playlist,
_ => panic!("Expected media playlist"),
};
assert_eq!(media_playlist.discontinuity_sequence, 5);

let segments = media_playlist.segments;
assert_eq!(segments.len(), 3);
assert_eq!(segments[0].part_index, 5);
assert_eq!(segments[1].part_index, 6);
assert_eq!(segments[2].part_index, 7);
}

#[test]
fn test_accuracy_discontinuity_02() {
let data = include_bytes!("./fixtures/discontinuity/playlist2.m3u8");
let old_result = iori_hls::m3u8_rs::parse_playlist_res(data);
let new_result = iori_hls::parse::parse_playlist_res(data);

let old_result = old_result.expect("Old parse engine should not error");
let new_result = new_result.expect("New parse engine should not error");
assert_changes!(old_result, new_result, Changed::Unchanged);
}

#[test]
fn test_accuracy_discontinuity_no_seq() {
let data = include_bytes!("./fixtures/discontinuity/no_seq.m3u8");
let old_result = iori_hls::m3u8_rs::parse_playlist_res(data);
let new_result = iori_hls::parse::parse_playlist_res(data);

let old_result = old_result.expect("Old parse engine should not error");
let new_result = new_result.expect("New parse engine should not error");
assert_changes!(old_result, new_result, Changed::Unchanged);

let media_playlist = match new_result {
iori_hls::Playlist::MediaPlaylist(media_playlist) => media_playlist,
_ => panic!("Expected media playlist"),
};
assert_eq!(media_playlist.discontinuity_sequence, 0);

let segments = media_playlist.segments;
assert_eq!(segments.len(), 2);
assert_eq!(segments[0].part_index, 0);
assert_eq!(segments[1].part_index, 1);
}
11 changes: 11 additions & 0 deletions crates/iori-hls/tests/fixtures/discontinuity/no_seq.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
segment1.ts
#EXT-X-DISCONTINUITY
#EXTINF:10.0,
segment2.ts
#EXT-X-ENDLIST

14 changes: 14 additions & 0 deletions crates/iori-hls/tests/fixtures/discontinuity/playlist.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-DISCONTINUITY-SEQUENCE:5
#EXTINF:10.0,
segment1.ts
#EXT-X-DISCONTINUITY
#EXTINF:10.0,
segment2.ts
#EXT-X-DISCONTINUITY
#EXTINF:10.0,
segment3.ts
#EXT-X-ENDLIST
6 changes: 6 additions & 0 deletions crates/iori/src/hls/segment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub struct M3u8Segment {
/// Media sequence id from the m3u8 file
pub media_sequence: u64,

pub part_index: u64,

pub duration: f64,
pub format: SegmentFormat,
}
Expand Down Expand Up @@ -59,6 +61,10 @@ impl StreamingSegment for M3u8Segment {
fn format(&self) -> SegmentFormat {
self.format.clone()
}

fn part_index(&self) -> u64 {
self.part_index
}
}

impl RemoteStreamingSegment for M3u8Segment {
Expand Down
1 change: 1 addition & 0 deletions crates/iori/src/hls/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ impl HlsMediaPlaylistSource {
initial_segment: initial_segment.clone(),
sequence: self.sequence.fetch_add(1, Ordering::Relaxed),
media_sequence,
part_index: segment.part_index,
byte_range: segment.byte_range.as_ref().map(|r| crate::ByteRange {
offset: r.offset.unwrap_or(next_range_start),
length: Some(r.length),
Expand Down
7 changes: 7 additions & 0 deletions crates/iori/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub trait StreamingSource {
) -> impl Future<Output = IoriResult<impl Stream<Item = IoriResult<Vec<Self::Segment>>>>>;
}

/// A segment of a streaming source
pub trait StreamingSegment {
/// Stream id
fn stream_id(&self) -> u64;
Expand All @@ -68,6 +69,12 @@ pub trait StreamingSegment {
/// Sequence ID of the segment, starts from 0
fn sequence(&self) -> u64;

/// Part index of the segment. For segments without sub-parts or discontinuities,
/// this defaults to 0.
fn part_index(&self) -> u64 {
0
}

/// File name of the segment
fn file_name(&self) -> &str;

Expand Down
Loading
Loading