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);
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<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
#[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<ByteRange> {
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 get_byte_ranges(s: &gst::StructureRef) -> Vec<ByteRange> {
let mut ranges = Vec::new();
pub fn extract_byteranges(input: &str) -> Vec<ByteRange> {
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::<u64>().ok()?;
let offset = off_str.parse::<u64>().ok()?;
Some(ByteRange { length, offset })
}
_ => None,
}
})
})
.collect()
if let Ok(br) = s.get::<gst::Structure>("initialization-segment-byte-range") {
let length = br.get::<u64>("length").unwrap();
let offset = br.get::<u64>("offset").unwrap();
assert!(length != 0);
ranges.push(ByteRange { length, offset })
}
if let Ok(br) = s.get::<gst::Structure>("segment-byte-range") {
let length = br.get::<u64>("length").unwrap();
let offset = br.get::<u64>("offset").unwrap();
assert!(length != 0);
ranges.push(ByteRange { length, offset })
}
ranges
}
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();
let mut byte_ranges: Vec<ByteRange> = 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::<String>("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::*;

View file

@ -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<gst::DebugCategory> = 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<ByteRange> = 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::<String>("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.