Merge tag 'epoch-1.8.0'
Epoch 1.8.0 # Conflicts: # Cargo.lock # src/video.rs
This commit is contained in:
commit
45c9b1aa19
6 changed files with 261 additions and 63 deletions
69
src/video.rs
69
src/video.rs
|
|
@ -4,6 +4,40 @@ use iced_video_player::{Video, gst, gst_app, gst_pbutils};
|
|||
use cosmic::action;
|
||||
use cosmic::app::Task;
|
||||
|
||||
const SUSPICIOUS_AUDIO_GAP_DURATION: gst::ClockTime = gst::ClockTime::SECOND;
|
||||
|
||||
fn is_suspicious_audio_gap(duration: Option<gst::ClockTime>) -> bool {
|
||||
duration.is_some_and(|duration| duration >= SUSPICIOUS_AUDIO_GAP_DURATION)
|
||||
}
|
||||
|
||||
fn suppress_suspicious_audio_gaps(audio_filter: &gst::Element) {
|
||||
let Some(src_pad) = audio_filter.static_pad("src") else {
|
||||
log::warn!("audio filter has no source pad; malformed audio gaps will not be suppressed");
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = src_pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, |_, info| {
|
||||
let Some(event) = info.event() else {
|
||||
return gst::PadProbeReturn::Ok;
|
||||
};
|
||||
let gst::EventView::Gap(gap) = event.view() else {
|
||||
return gst::PadProbeReturn::Ok;
|
||||
};
|
||||
let (timestamp, duration) = gap.get();
|
||||
if !is_suspicious_audio_gap(duration) {
|
||||
return gst::PadProbeReturn::Ok;
|
||||
}
|
||||
|
||||
// Some malformed MP4 edit lists cause streamsynchronizer to emit a
|
||||
// long GAP before almost every audio buffer. The next buffer's PTS is
|
||||
// still continuous, so forwarding those events makes audio sinks
|
||||
// repeatedly wait or resynchronise and produces choppy playback.
|
||||
log::debug!("suppressing audio GAP at {timestamp} with duration {duration:?}");
|
||||
drop(info.take_event());
|
||||
gst::PadProbeReturn::Handled
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct VideoSettings {
|
||||
pub mute: bool,
|
||||
|
|
@ -33,11 +67,14 @@ pub fn new_video(
|
|||
.downcast::<gst::Pipeline>()
|
||||
.map_err(|_| iced_video_player::Error::Cast)
|
||||
.unwrap();
|
||||
if let Ok(scaletempo) = gst::ElementFactory::make("scaletempo").build() {
|
||||
pipeline.set_property("audio-filter", &scaletempo);
|
||||
} else {
|
||||
log::warn!("scaletempo element not available; speed changes will affect pitch");
|
||||
}
|
||||
let audio_filter = gst::ElementFactory::make("scaletempo")
|
||||
.build()
|
||||
.unwrap_or_else(|_| {
|
||||
log::warn!("scaletempo element not available; speed changes will affect pitch");
|
||||
gst::ElementFactory::make("identity").build().unwrap()
|
||||
});
|
||||
suppress_suspicious_audio_gaps(&audio_filter);
|
||||
pipeline.set_property("audio-filter", &audio_filter);
|
||||
pipeline.connect("element-setup", false, |vals| {
|
||||
let Ok(elem) = vals[1].get::<gst::Element>() else {
|
||||
return None;
|
||||
|
|
@ -111,3 +148,25 @@ fn disable_broken_hardware_decoders() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::is_suspicious_audio_gap;
|
||||
use iced_video_player::gst;
|
||||
|
||||
#[test]
|
||||
fn long_audio_gap_is_suspicious() {
|
||||
assert!(is_suspicious_audio_gap(Some(gst::ClockTime::SECOND)));
|
||||
assert!(is_suspicious_audio_gap(Some(
|
||||
gst::ClockTime::from_seconds(3)
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_or_unknown_audio_gap_is_preserved() {
|
||||
assert!(!is_suspicious_audio_gap(Some(
|
||||
gst::ClockTime::from_mseconds(999)
|
||||
)));
|
||||
assert!(!is_suspicious_audio_gap(None));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue