use std::sync::Arc;
use crate::pp_log::{PpLog, pp_info};
use crate::{
buffer::MediaBuffer,
contract::InputContract,
control::ControlMsg,
element::{Element, ElementType, Sink, element_pp_log},
error::Result,
};
pub struct AppSink<F, C> {
pp_log: PpLog,
name: Arc<str>,
consume: F,
control: C,
}
impl<F> AppSink<F, fn(ControlMsg) -> Result<()>>
where
F: FnMut(MediaBuffer) -> Result<()> + Send + 'static,
{
pub fn new(name: impl Into<String>, consume: F) -> Self {
Self::with_control(name, consume, |_| Ok(()))
}
}
impl<F, C> AppSink<F, C>
where
F: FnMut(MediaBuffer) -> Result<()> + Send + 'static,
C: FnMut(ControlMsg) -> Result<()> + Send + 'static,
{
pub fn with_control(name: impl Into<String>, consume: F, control: C) -> Self {
let name: Arc<str> = name.into().into();
let pp_log = element_pp_log(ElementType::AppSink, &name, None);
pp_info!(pp_log: &pp_log, "created");
Self {
name,
pp_log,
consume,
control,
}
}
}
impl<F, C> Element for AppSink<F, C>
where
F: FnMut(MediaBuffer) -> Result<()> + Send + 'static,
C: FnMut(ControlMsg) -> Result<()> + Send + 'static,
{
fn name(&self) -> Arc<str> {
self.name.clone()
}
fn element_type(&self) -> ElementType {
ElementType::AppSink
}
fn pp_log(&self) -> &PpLog {
&self.pp_log
}
fn pp_log_mut(&mut self) -> &mut PpLog {
&mut self.pp_log
}
}
impl<F, C> Sink for AppSink<F, C>
where
F: FnMut(MediaBuffer) -> Result<()> + Send + 'static,
C: FnMut(ControlMsg) -> Result<()> + Send + 'static,
{
fn input_contract(&self) -> InputContract {
InputContract::Any
}
fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
(self.consume)(buf)
}
fn control(&mut self, msg: ControlMsg) -> Result<()> {
(self.control)(msg)
}
}
#[cfg(test)]
mod tests {
use std::{
sync::{Arc, Mutex},
time::Duration,
};
use ffmpeg_next as ffmpeg;
use super::*;
fn control_messages() -> [ControlMsg; 4] {
[
ControlMsg::Pause,
ControlMsg::Resume,
ControlMsg::Stop,
ControlMsg::Seek(Duration::from_secs(1)),
]
}
#[test]
fn new_accepts_and_ignores_every_control_message() {
let mut sink = AppSink::new("counter", |_buf| Ok(()));
for msg in control_messages() {
sink.control(msg).unwrap();
}
}
#[test]
fn with_control_forwards_every_control_message() {
let seen = Arc::new(Mutex::new(Vec::new()));
let recorded = seen.clone();
let mut sink = AppSink::with_control(
"detector",
|_buf| Ok(()),
move |msg| {
recorded.lock().unwrap().push(msg);
Ok(())
},
);
for msg in control_messages() {
sink.control(msg).unwrap();
}
assert_eq!(&*seen.lock().unwrap(), &control_messages());
}
#[test]
fn consume_error_propagates_to_the_caller() {
let mut sink = AppSink::new("failing", |_buf| {
Err(crate::error::Error::Other("closure failed".into()))
});
let error = sink.consume(MediaBuffer::Eos).unwrap_err();
assert!(error.to_string().contains("closure failed"));
}
#[test]
fn every_buffer_including_eos_reaches_the_closure() {
let seen = Arc::new(Mutex::new(Vec::new()));
let recorded = seen.clone();
let mut sink = AppSink::new("recorder", move |buf| {
recorded
.lock()
.unwrap()
.push(matches!(buf, MediaBuffer::Eos));
Ok(())
});
sink.consume(MediaBuffer::Audio(Arc::new(ffmpeg::frame::Audio::empty())))
.unwrap();
sink.consume(MediaBuffer::Eos).unwrap();
assert_eq!(&*seen.lock().unwrap(), &[false, true]);
}
}