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: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/2439>
This commit is contained in:
Sanchayan Maity 2025-08-11 17:06:15 +05:30
parent efbfab76bc
commit a8684853d0
4 changed files with 70 additions and 60 deletions

View file

@ -545,6 +545,8 @@ impl HlsBaseSink {
} }
} }
let (init_segment_br, segment_br) = self.byte_ranges(context, &segment);
context.playlist.add_segment(segment); context.playlist.add_segment(segment);
if context.playlist.is_type_undefined() { if context.playlist.is_type_undefined() {
@ -559,6 +561,12 @@ impl HlsBaseSink {
if let Some(ts) = timestamp { if let Some(ts) = timestamp {
s = s.field("timestamp", 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( self.post_message(
gst::message::Element::builder(s.build()) gst::message::Element::builder(s.build())
.src(&*self.obj()) .src(&*self.obj())
@ -690,4 +698,33 @@ impl HlsBaseSink {
let settings = self.settings.lock().unwrap(); let settings = self.settings.lock().unwrap();
settings.single_media_file.is_some() settings.single_media_file.is_some()
} }
fn byte_ranges(
&self,
context: &PlaylistContext,
segment: &MediaSegment,
) -> (Option<gst::Structure>, Option<gst::Structure>) {
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)
}
} }

View file

@ -6,57 +6,30 @@
// //
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
#[derive(Debug, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct ByteRange { pub struct ByteRange {
length: u64, length: u64,
offset: u64, offset: u64,
} }
#[allow(dead_code)] pub fn get_byte_ranges(s: &gst::StructureRef) -> Vec<ByteRange> {
pub fn extract_map_byterange(input: &str) -> Option<ByteRange> { let mut ranges = Vec::new();
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::<u64>().ok()?;
let offset = off_str.parse::<u64>().ok()?;
Some(ByteRange { length, offset })
}
_ => None,
}
})
})
}
pub fn extract_byteranges(input: &str) -> Vec<ByteRange> { if let Ok(br) = s.get::<gst::Structure>("initialization-segment-byte-range") {
input let length = br.get::<u64>("length").unwrap();
.split('\n') let offset = br.get::<u64>("offset").unwrap();
.filter(|line| line.starts_with("#EXT-X-BYTERANGE:")) assert!(length != 0);
.filter_map(|line| { ranges.push(ByteRange { length, offset })
line.strip_prefix("#EXT-X-BYTERANGE:").and_then(|content| { }
let mut parts = content.split('@');
match (parts.next(), parts.next()) { if let Ok(br) = s.get::<gst::Structure>("segment-byte-range") {
(Some(len_str), Some(off_str)) => { let length = br.get::<u64>("length").unwrap();
let length = len_str.parse::<u64>().ok()?; let offset = br.get::<u64>("offset").unwrap();
let offset = off_str.parse::<u64>().ok()?; assert!(length != 0);
Some(ByteRange { length, offset }) ranges.push(ByteRange { length, offset })
} }
_ => None,
} ranges
})
})
.collect()
} }
pub fn validate_byterange_sequence(ranges: &[ByteRange]) -> bool { pub fn validate_byterange_sequence(ranges: &[ByteRange]) -> bool {

View file

@ -159,6 +159,7 @@ fn test_hlscmafsink_video_with_single_media_file() -> Result<(), ()> {
pipeline.set_state(gst::State::Playing).unwrap(); pipeline.set_state(gst::State::Playing).unwrap();
let mut byte_ranges: Vec<ByteRange> = Vec::new();
let mut eos = false; let mut eos = false;
let bus = pipeline.bus().unwrap(); let bus = pipeline.bus().unwrap();
while let Some(msg) = bus.timed_pop(gst::ClockTime::NONE) { 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 let Some(structure) = msg.structure() {
if structure.has_name("hls-segment-added") { if structure.has_name("hls-segment-added") {
let location = structure.get::<String>("location").unwrap(); let location = structure.get::<String>("location").unwrap();
let byte_range = get_byte_ranges(structure);
byte_ranges.extend(byte_range);
hls_messages_sender hls_messages_sender
.try_send(HlsSinkEvent::SegmentAddedMessage(location)) .try_send(HlsSinkEvent::SegmentAddedMessage(location))
.expect("Send segment added event"); .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); actual_messages.push(event);
} }
let contents = playlist_content.lock().unwrap(); eprintln!("Playlist byte ranges: {byte_ranges:?}");
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);
// We only validate the byte range and map tag. The actual value of // 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 // byte range can differ from each run and hence we do not validate
// the entire playlist. // the entire playlist.
assert!(validate_byterange_sequence(&map_byte_range)); assert!(validate_byterange_sequence(&byte_ranges));
let expected_messages = { let expected_messages = {
use self::HlsSinkEvent::*; use self::HlsSinkEvent::*;

View file

@ -14,7 +14,7 @@ use std::sync::LazyLock;
use std::sync::{mpsc, Arc, Mutex}; use std::sync::{mpsc, Arc, Mutex};
mod common; mod common;
use crate::common::{extract_byteranges, validate_byterange_sequence}; use crate::common::{get_byte_ranges, validate_byterange_sequence, ByteRange};
static CAT: LazyLock<gst::DebugCategory> = LazyLock::new(|| { static CAT: LazyLock<gst::DebugCategory> = LazyLock::new(|| {
gst::DebugCategory::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 BUFFER_NB
); );
let mut byte_ranges: Vec<ByteRange> = Vec::new();
let mut eos = false; let mut eos = false;
let bus = pipeline.bus().unwrap(); let bus = pipeline.bus().unwrap();
while let Some(msg) = bus.timed_pop(gst::ClockTime::NONE) { 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 let Some(structure) = msg.structure() {
if structure.has_name("hls-segment-added") { if structure.has_name("hls-segment-added") {
let location = structure.get::<String>("location").unwrap(); let location = structure.get::<String>("location").unwrap();
let byte_range = get_byte_ranges(structure);
byte_ranges.extend(byte_range);
hls_messages_sender hls_messages_sender
.try_send(HlsSinkEvent::SegmentAddedMessage(location)) .try_send(HlsSinkEvent::SegmentAddedMessage(location))
.expect("Send segment added event"); .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); actual_messages.push(event);
} }
let contents = playlist_content.lock().unwrap(); eprintln!("Playlist byte ranges: {byte_ranges:?}");
let byte_ranges = extract_byteranges(contents.as_str());
// We only validate the byte range tag. The actual value of byte // We only validate the byte range tag. The actual value of byte
// range can differ from each run and hence we do not validate // range can differ from each run and hence we do not validate
// the entire playlist. // the entire playlist.