#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SegmentType {
InitSegment,
MediaSegment,
IndexSegment,
}
impl SegmentType {
#[must_use]
pub fn is_media(self) -> bool {
matches!(self, Self::MediaSegment)
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::InitSegment => "init",
Self::MediaSegment => "media",
Self::IndexSegment => "index",
}
}
}
impl std::fmt::Display for SegmentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.label())
}
}
#[derive(Debug, Clone)]
pub struct MediaSegment {
uri: String,
segment_type: SegmentType,
duration_ms: u64,
byte_offset: Option<u64>,
byte_length: Option<u64>,
sequence_number: Option<u64>,
}
impl MediaSegment {
pub fn new(uri: impl Into<String>, segment_type: SegmentType, duration_ms: u64) -> Self {
Self {
uri: uri.into(),
segment_type,
duration_ms,
byte_offset: None,
byte_length: None,
sequence_number: None,
}
}
#[must_use]
pub fn with_byte_range(mut self, offset: u64, length: u64) -> Self {
self.byte_offset = Some(offset);
self.byte_length = Some(length);
self
}
#[must_use]
pub fn with_sequence(mut self, seq: u64) -> Self {
self.sequence_number = Some(seq);
self
}
#[must_use]
pub fn uri(&self) -> &str {
&self.uri
}
#[must_use]
pub fn segment_type(&self) -> SegmentType {
self.segment_type
}
#[must_use]
pub fn duration_ms(&self) -> u64 {
self.duration_ms
}
#[must_use]
pub fn byte_offset(&self) -> Option<u64> {
self.byte_offset
}
#[must_use]
pub fn byte_length(&self) -> Option<u64> {
self.byte_length
}
#[must_use]
pub fn sequence_number(&self) -> Option<u64> {
self.sequence_number
}
}
#[derive(Debug, Default)]
pub struct SegmentList {
segments: Vec<MediaSegment>,
}
impl SegmentList {
#[must_use]
pub fn new() -> Self {
Self {
segments: Vec::new(),
}
}
pub fn add(&mut self, segment: MediaSegment) {
self.segments.push(segment);
}
#[must_use]
pub fn total_duration_ms(&self) -> u64 {
self.segments.iter().map(|s| s.duration_ms).sum()
}
#[must_use]
pub fn segment_count(&self) -> usize {
self.segments.len()
}
#[must_use]
pub fn segments(&self) -> &[MediaSegment] {
&self.segments
}
#[must_use]
pub fn segments_of_type(&self, t: SegmentType) -> Vec<&MediaSegment> {
self.segments
.iter()
.filter(|s| s.segment_type == t)
.collect()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}
#[must_use]
pub fn init_segment(&self) -> Option<&MediaSegment> {
self.segments
.iter()
.find(|s| s.segment_type == SegmentType::InitSegment)
}
pub fn prune_before_sequence(&mut self, min_seq: u64) {
self.segments
.retain(|s| s.sequence_number.map_or(true, |seq| seq >= min_seq));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_segment_type_is_media() {
assert!(SegmentType::MediaSegment.is_media());
assert!(!SegmentType::InitSegment.is_media());
assert!(!SegmentType::IndexSegment.is_media());
}
#[test]
fn test_segment_type_label() {
assert_eq!(SegmentType::InitSegment.label(), "init");
assert_eq!(SegmentType::MediaSegment.label(), "media");
assert_eq!(SegmentType::IndexSegment.label(), "index");
}
#[test]
fn test_segment_type_display() {
assert_eq!(SegmentType::MediaSegment.to_string(), "media");
}
#[test]
fn test_media_segment_duration() {
let seg = MediaSegment::new("seg0.m4s", SegmentType::MediaSegment, 4000);
assert_eq!(seg.duration_ms(), 4000);
}
#[test]
fn test_media_segment_uri() {
let seg = MediaSegment::new("init.mp4", SegmentType::InitSegment, 0);
assert_eq!(seg.uri(), "init.mp4");
}
#[test]
fn test_media_segment_byte_range() {
let seg = MediaSegment::new("stream.mp4", SegmentType::MediaSegment, 2000)
.with_byte_range(0, 1024);
assert_eq!(seg.byte_offset(), Some(0));
assert_eq!(seg.byte_length(), Some(1024));
}
#[test]
fn test_media_segment_sequence() {
let seg = MediaSegment::new("s1.m4s", SegmentType::MediaSegment, 2000).with_sequence(5);
assert_eq!(seg.sequence_number(), Some(5));
}
#[test]
fn test_segment_list_empty() {
let list = SegmentList::new();
assert!(list.is_empty());
assert_eq!(list.segment_count(), 0);
assert_eq!(list.total_duration_ms(), 0);
}
#[test]
fn test_segment_list_add_and_count() {
let mut list = SegmentList::new();
list.add(MediaSegment::new("init.mp4", SegmentType::InitSegment, 0));
list.add(MediaSegment::new("s1.m4s", SegmentType::MediaSegment, 4000));
list.add(MediaSegment::new("s2.m4s", SegmentType::MediaSegment, 4000));
assert_eq!(list.segment_count(), 3);
}
#[test]
fn test_segment_list_total_duration() {
let mut list = SegmentList::new();
list.add(MediaSegment::new("s1.m4s", SegmentType::MediaSegment, 3000));
list.add(MediaSegment::new("s2.m4s", SegmentType::MediaSegment, 3500));
assert_eq!(list.total_duration_ms(), 6500);
}
#[test]
fn test_segment_list_of_type() {
let mut list = SegmentList::new();
list.add(MediaSegment::new("init.mp4", SegmentType::InitSegment, 0));
list.add(MediaSegment::new("s1.m4s", SegmentType::MediaSegment, 2000));
let media = list.segments_of_type(SegmentType::MediaSegment);
assert_eq!(media.len(), 1);
}
#[test]
fn test_segment_list_init_segment() {
let mut list = SegmentList::new();
list.add(MediaSegment::new("init.mp4", SegmentType::InitSegment, 0));
assert!(list.init_segment().is_some());
assert_eq!(
list.init_segment().expect("should succeed in test").uri(),
"init.mp4"
);
}
#[test]
fn test_segment_list_prune() {
let mut list = SegmentList::new();
list.add(MediaSegment::new("s0.m4s", SegmentType::MediaSegment, 2000).with_sequence(0));
list.add(MediaSegment::new("s1.m4s", SegmentType::MediaSegment, 2000).with_sequence(1));
list.add(MediaSegment::new("s2.m4s", SegmentType::MediaSegment, 2000).with_sequence(2));
list.prune_before_sequence(1);
assert_eq!(list.segment_count(), 2);
}
}