mirror of
https://github.com/v0l/zap-stream-core.git
synced 2025-06-16 08:59:35 +00:00
fix: try improve hls playback
This commit is contained in:
@ -113,8 +113,12 @@ impl RtmpClient {
|
||||
}
|
||||
ServerSessionResult::RaisedEvent(ev) => self.handle_event(ev)?,
|
||||
ServerSessionResult::UnhandleableMessageReceived(m) => {
|
||||
// treat any non-flv streams as raw media stream in rtmp
|
||||
self.media_buf.extend(&m.data);
|
||||
// Log unhandleable messages for debugging
|
||||
error!("Received unhandleable message with {} bytes", m.data.len());
|
||||
// Only append data if it looks like valid media data
|
||||
if !m.data.is_empty() && m.data.len() > 4 {
|
||||
self.media_buf.extend(&m.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,10 +168,20 @@ impl RtmpClient {
|
||||
);
|
||||
}
|
||||
ServerSessionEvent::AudioDataReceived { data, .. } => {
|
||||
self.media_buf.extend(data);
|
||||
// Validate audio data before adding to buffer
|
||||
if !data.is_empty() {
|
||||
self.media_buf.extend(data);
|
||||
} else {
|
||||
error!("Received empty audio data");
|
||||
}
|
||||
}
|
||||
ServerSessionEvent::VideoDataReceived { data, .. } => {
|
||||
self.media_buf.extend(data);
|
||||
// Validate video data before adding to buffer
|
||||
if !data.is_empty() {
|
||||
self.media_buf.extend(data);
|
||||
} else {
|
||||
error!("Received empty video data");
|
||||
}
|
||||
}
|
||||
ServerSessionEvent::UnhandleableAmf0Command { .. } => {}
|
||||
ServerSessionEvent::PlayStreamRequested { request_id, .. } => {
|
||||
|
@ -186,11 +186,7 @@ impl HlsVariant {
|
||||
streams,
|
||||
idx: 1,
|
||||
pkt_start: 0.0,
|
||||
segments: Vec::from([SegmentInfo {
|
||||
index: 1,
|
||||
duration: segment_length,
|
||||
kind: segment_type,
|
||||
}]),
|
||||
segments: Vec::new(), // Start with empty segments list
|
||||
out_dir: out_dir.to_string(),
|
||||
segment_type,
|
||||
})
|
||||
@ -220,8 +216,9 @@ impl HlsVariant {
|
||||
let pkt_q = av_q2d((*pkt).time_base);
|
||||
// time of this packet in seconds
|
||||
let pkt_time = (*pkt).pts as f32 * pkt_q as f32;
|
||||
// what segment this pkt should be in (index)
|
||||
let pkt_seg = 1 + (pkt_time / self.segment_length).floor() as u64;
|
||||
// what segment this pkt should be in (index) - use relative time from start
|
||||
let relative_time = pkt_time - self.pkt_start;
|
||||
let pkt_seg = self.idx + (relative_time / self.segment_length).floor() as u64;
|
||||
|
||||
let mut result = EgressResult::None;
|
||||
let pkt_stream = *(*self.mux.context())
|
||||
@ -361,11 +358,18 @@ impl HlsVariant {
|
||||
}
|
||||
|
||||
fn write_playlist(&mut self) -> Result<()> {
|
||||
if self.segments.is_empty() {
|
||||
return Ok(()); // Don't write empty playlists
|
||||
}
|
||||
|
||||
let mut pl = m3u8_rs::MediaPlaylist::default();
|
||||
pl.target_duration = self.segment_length as u64;
|
||||
// Round up target duration to ensure compliance
|
||||
pl.target_duration = (self.segment_length.ceil() as u64).max(1);
|
||||
pl.segments = self.segments.iter().map(|s| s.to_media_segment()).collect();
|
||||
pl.version = Some(3);
|
||||
pl.media_sequence = self.segments.first().map(|s| s.index).unwrap_or(0);
|
||||
// For live streams, don't set end list
|
||||
pl.end_list = false;
|
||||
|
||||
let mut f_out = File::create(self.out_dir().join("live.m3u8"))?;
|
||||
pl.write_to(&mut f_out)?;
|
||||
|
@ -77,6 +77,9 @@ pub struct PipelineRunner {
|
||||
/// Total number of frames produced
|
||||
frame_ctr: u64,
|
||||
out_dir: String,
|
||||
|
||||
/// Thumbnail generation interval (0 = disabled)
|
||||
thumb_interval: u64,
|
||||
}
|
||||
|
||||
impl PipelineRunner {
|
||||
@ -104,6 +107,7 @@ impl PipelineRunner {
|
||||
frame_ctr: 0,
|
||||
fps_last_frame_ctr: 0,
|
||||
info: None,
|
||||
thumb_interval: 1800, // Disable thumbnails by default for performance
|
||||
})
|
||||
}
|
||||
|
||||
@ -165,7 +169,9 @@ impl PipelineRunner {
|
||||
|
||||
let p = (*stream).codecpar;
|
||||
if (*p).codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO {
|
||||
if (self.frame_ctr % 1800) == 0 {
|
||||
// Conditionally generate thumbnails based on interval (0 = disabled)
|
||||
if self.thumb_interval > 0 && (self.frame_ctr % self.thumb_interval) == 0 {
|
||||
let thumb_start = Instant::now();
|
||||
let dst_pic = PathBuf::from(&self.out_dir)
|
||||
.join(config.id.to_string())
|
||||
.join("thumb.webp");
|
||||
@ -182,11 +188,15 @@ impl PipelineRunner {
|
||||
.with_pix_fmt(transmute((*frame).format))
|
||||
.open(None)?
|
||||
.save_picture(frame, dst_pic.to_str().unwrap())?;
|
||||
info!("Saved thumb to: {}", dst_pic.display());
|
||||
let thumb_duration = thumb_start.elapsed();
|
||||
info!(
|
||||
"Saved thumb ({:.2}ms) to: {}",
|
||||
thumb_duration.as_millis() as f32 / 1000.0,
|
||||
dst_pic.display(),
|
||||
);
|
||||
av_frame_free(&mut frame);
|
||||
}
|
||||
|
||||
// TODO: fix this, multiple video streams in
|
||||
self.frame_ctr += 1;
|
||||
}
|
||||
|
||||
@ -224,7 +234,7 @@ impl PipelineRunner {
|
||||
{
|
||||
// Set correct timebase for audio (1/sample_rate)
|
||||
(*ret).time_base.num = 1;
|
||||
(*ret).time_base.den = a.sample_rate as i32;
|
||||
(*ret).time_base.den = a.sample_rate as i32;
|
||||
av_frame_free(&mut resampled_frame);
|
||||
ret
|
||||
} else {
|
||||
@ -271,21 +281,23 @@ impl PipelineRunner {
|
||||
|
||||
av_packet_free(&mut pkt);
|
||||
|
||||
// egress results
|
||||
self.handle.block_on(async {
|
||||
for er in egress_results {
|
||||
if let EgressResult::Segments { created, deleted } = er {
|
||||
if let Err(e) = self
|
||||
.overseer
|
||||
.on_segments(&config.id, &created, &deleted)
|
||||
.await
|
||||
{
|
||||
bail!("Failed to process segment {}", e.to_string());
|
||||
// egress results - process async operations without blocking if possible
|
||||
if !egress_results.is_empty() {
|
||||
self.handle.block_on(async {
|
||||
for er in egress_results {
|
||||
if let EgressResult::Segments { created, deleted } = er {
|
||||
if let Err(e) = self
|
||||
.overseer
|
||||
.on_segments(&config.id, &created, &deleted)
|
||||
.await
|
||||
{
|
||||
bail!("Failed to process segment {}", e.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
let elapsed = Instant::now().sub(self.fps_counter_start).as_secs_f32();
|
||||
if elapsed >= 2f32 {
|
||||
let n_frames = self.frame_ctr - self.fps_last_frame_ctr;
|
||||
|
Reference in New Issue
Block a user