From a8684853d0285169b4f90a85926a1fca7363abd4 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Mon, 11 Aug 2025 17:06:15 +0530 Subject: [PATCH] hlssink3/hlscmafsink: Add byte range tag information to hls-segment-added message While at it, for tests, replace unreachable!() with panic!() and print the error message contents. Part-of: --- net/hlssink3/src/hlsbasesink.rs | 37 ++++++++++++++++++ net/hlssink3/tests/common/mod.rs | 63 +++++++++---------------------- net/hlssink3/tests/hlscmafsink.rs | 16 ++++---- net/hlssink3/tests/hlssink3.rs | 14 ++++--- 4 files changed, 70 insertions(+), 60 deletions(-) diff --git a/net/hlssink3/src/hlsbasesink.rs b/net/hlssink3/src/hlsbasesink.rs index 857860122..ef2abb49b 100644 --- a/net/hlssink3/src/hlsbasesink.rs +++ b/net/hlssink3/src/hlsbasesink.rs @@ -545,6 +545,8 @@ impl HlsBaseSink { } } + let (init_segment_br, segment_br) = self.byte_ranges(context, &segment); + context.playlist.add_segment(segment); if context.playlist.is_type_undefined() { @@ -559,6 +561,12 @@ impl HlsBaseSink { if let Some(ts) = timestamp { s = s.field("timestamp", ts.timestamp()); }; + if let Some(br) = init_segment_br { + s = s.field("initialization-segment-byte-range", br); + } + if let Some(br) = segment_br { + s = s.field("segment-byte-range", br); + } self.post_message( gst::message::Element::builder(s.build()) .src(&*self.obj()) @@ -690,4 +698,33 @@ impl HlsBaseSink { let settings = self.settings.lock().unwrap(); settings.single_media_file.is_some() } + + fn byte_ranges( + &self, + context: &PlaylistContext, + segment: &MediaSegment, + ) -> (Option, Option) { + let mut init_segment_br = None; + let mut segment_br = None; + + if context.single_media_file { + if let Some(ref map) = segment.map { + if let Some(br) = &map.byte_range { + let mut s = gst::Structure::new_empty("byte-range"); + s.set("length", br.length); + s.set("offset", br.offset.unwrap()); + init_segment_br = Some(s); + } + } + + if let Some(ref br) = segment.byte_range { + let mut s = gst::Structure::new_empty("byte-range"); + s.set("length", br.length); + s.set("offset", br.offset.unwrap()); + segment_br = Some(s) + } + } + + (init_segment_br, segment_br) + } } diff --git a/net/hlssink3/tests/common/mod.rs b/net/hlssink3/tests/common/mod.rs index eaf8ca24e..61f55e49f 100644 --- a/net/hlssink3/tests/common/mod.rs +++ b/net/hlssink3/tests/common/mod.rs @@ -6,57 +6,30 @@ // // SPDX-License-Identifier: MPL-2.0 -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct ByteRange { length: u64, offset: u64, } -#[allow(dead_code)] -pub fn extract_map_byterange(input: &str) -> Option { - input - .lines() - .find(|line| line.starts_with("#EXT-X-MAP:")) - .and_then(|line| { - line.find("BYTERANGE=\"") - .and_then(|start| { - let content_start = start + "BYTERANGE=\"".len(); - line[content_start..] - .find('"') - .map(|end| &line[content_start..content_start + end]) - }) - .and_then(|byterange_str| { - let mut parts = byterange_str.split('@'); - match (parts.next(), parts.next()) { - (Some(len_str), Some(off_str)) => { - let length = len_str.parse::().ok()?; - let offset = off_str.parse::().ok()?; - Some(ByteRange { length, offset }) - } - _ => None, - } - }) - }) -} +pub fn get_byte_ranges(s: &gst::StructureRef) -> Vec { + let mut ranges = Vec::new(); -pub fn extract_byteranges(input: &str) -> Vec { - input - .split('\n') - .filter(|line| line.starts_with("#EXT-X-BYTERANGE:")) - .filter_map(|line| { - line.strip_prefix("#EXT-X-BYTERANGE:").and_then(|content| { - let mut parts = content.split('@'); - match (parts.next(), parts.next()) { - (Some(len_str), Some(off_str)) => { - let length = len_str.parse::().ok()?; - let offset = off_str.parse::().ok()?; - Some(ByteRange { length, offset }) - } - _ => None, - } - }) - }) - .collect() + if let Ok(br) = s.get::("initialization-segment-byte-range") { + let length = br.get::("length").unwrap(); + let offset = br.get::("offset").unwrap(); + assert!(length != 0); + ranges.push(ByteRange { length, offset }) + } + + if let Ok(br) = s.get::("segment-byte-range") { + let length = br.get::("length").unwrap(); + let offset = br.get::("offset").unwrap(); + assert!(length != 0); + ranges.push(ByteRange { length, offset }) + } + + ranges } pub fn validate_byterange_sequence(ranges: &[ByteRange]) -> bool { diff --git a/net/hlssink3/tests/hlscmafsink.rs b/net/hlssink3/tests/hlscmafsink.rs index e87caecdd..e0554a7e4 100644 --- a/net/hlssink3/tests/hlscmafsink.rs +++ b/net/hlssink3/tests/hlscmafsink.rs @@ -159,6 +159,7 @@ fn test_hlscmafsink_video_with_single_media_file() -> Result<(), ()> { pipeline.set_state(gst::State::Playing).unwrap(); + let mut byte_ranges: Vec = Vec::new(); let mut eos = false; let bus = pipeline.bus().unwrap(); while let Some(msg) = bus.timed_pop(gst::ClockTime::NONE) { @@ -172,13 +173,16 @@ fn test_hlscmafsink_video_with_single_media_file() -> Result<(), ()> { if let Some(structure) = msg.structure() { if structure.has_name("hls-segment-added") { let location = structure.get::("location").unwrap(); + let byte_range = get_byte_ranges(structure); + byte_ranges.extend(byte_range); + hls_messages_sender .try_send(HlsSinkEvent::SegmentAddedMessage(location)) .expect("Send segment added event"); } } } - MessageView::Error(..) => unreachable!(), + MessageView::Error(err) => panic!("{err}"), _ => (), } } @@ -196,17 +200,11 @@ fn test_hlscmafsink_video_with_single_media_file() -> Result<(), ()> { actual_messages.push(event); } - let contents = playlist_content.lock().unwrap(); - - let mut map_byte_range = Vec::new(); - map_byte_range.push(extract_map_byterange(contents.as_str()).unwrap()); - let byte_ranges = extract_byteranges(contents.as_str()); - map_byte_range.extend(byte_ranges); - + eprintln!("Playlist byte ranges: {byte_ranges:?}"); // We only validate the byte range and map tag. The actual value of // byte range can differ from each run and hence we do not validate // the entire playlist. - assert!(validate_byterange_sequence(&map_byte_range)); + assert!(validate_byterange_sequence(&byte_ranges)); let expected_messages = { use self::HlsSinkEvent::*; diff --git a/net/hlssink3/tests/hlssink3.rs b/net/hlssink3/tests/hlssink3.rs index 4e88f1703..b2f83815f 100644 --- a/net/hlssink3/tests/hlssink3.rs +++ b/net/hlssink3/tests/hlssink3.rs @@ -14,7 +14,7 @@ use std::sync::LazyLock; use std::sync::{mpsc, Arc, Mutex}; mod common; -use crate::common::{extract_byteranges, validate_byterange_sequence}; +use crate::common::{get_byte_ranges, validate_byterange_sequence, ByteRange}; static CAT: LazyLock = LazyLock::new(|| { gst::DebugCategory::new( @@ -213,7 +213,7 @@ fn test_hlssink3_element_with_video_content() -> Result<(), ()> { } } } - MessageView::Error(..) => unreachable!(), + MessageView::Error(err) => panic!("{err}"), _ => (), } } @@ -611,6 +611,7 @@ fn test_hlssink3_video_with_single_media_file() -> Result<(), ()> { BUFFER_NB ); + let mut byte_ranges: Vec = Vec::new(); let mut eos = false; let bus = pipeline.bus().unwrap(); while let Some(msg) = bus.timed_pop(gst::ClockTime::NONE) { @@ -624,13 +625,16 @@ fn test_hlssink3_video_with_single_media_file() -> Result<(), ()> { if let Some(structure) = msg.structure() { if structure.has_name("hls-segment-added") { let location = structure.get::("location").unwrap(); + let byte_range = get_byte_ranges(structure); + byte_ranges.extend(byte_range); + hls_messages_sender .try_send(HlsSinkEvent::SegmentAddedMessage(location)) .expect("Send segment added event"); } } } - MessageView::Error(..) => unreachable!(), + MessageView::Error(err) => panic!("{err}"), _ => (), } } @@ -648,9 +652,7 @@ fn test_hlssink3_video_with_single_media_file() -> Result<(), ()> { actual_messages.push(event); } - let contents = playlist_content.lock().unwrap(); - - let byte_ranges = extract_byteranges(contents.as_str()); + eprintln!("Playlist byte ranges: {byte_ranges:?}"); // We only validate the byte range tag. The actual value of byte // range can differ from each run and hence we do not validate // the entire playlist.