Skip to main content

ez_ffmpeg/core/frame_export/
audio_iter.rs

1//! [`SampleIter`]: a fused iterator over exported audio chunks with the same
2//! load-bearing teardown ordering as [`FrameIter`](super::FrameIter) (S6).
3
4use super::chunk::AudioChunk;
5use crate::core::scheduler::ffmpeg_scheduler::{FfmpegScheduler, Running};
6use crate::error::Error;
7use crossbeam_channel::Receiver;
8
9/// An iterator over exported [`AudioChunk`]s.
10///
11/// Yields `Ok(chunk)` per exported chunk. When the pipeline finishes (or dies),
12/// the iterator joins the scheduler once: a worker error surfaces as a single
13/// terminal `Err`, after which the iterator is fused and yields `None` forever.
14///
15/// Dropping the iterator early tears the run down cleanly. Teardown drops the
16/// receiver *before* aborting the scheduler (S6): the sink can be parked in a
17/// blocking `send()`, and only dropping the receiver unblocks it so the abort
18/// can join the workers. `Drop` may block until in-flight FFmpeg calls return
19/// (no fixed bound on stalled network IO).
20pub struct SampleIter {
21    rx: Option<Receiver<AudioChunk>>,
22    scheduler: Option<FfmpegScheduler<Running>>,
23    terminated: bool,
24}
25
26impl SampleIter {
27    pub(crate) fn new(rx: Receiver<AudioChunk>, scheduler: FfmpegScheduler<Running>) -> Self {
28        Self {
29            rx: Some(rx),
30            scheduler: Some(scheduler),
31            terminated: false,
32        }
33    }
34
35    /// Joins the scheduler exactly once and maps its result to at most one
36    /// terminal error. Called when the channel disconnects.
37    fn finish(&mut self) -> Option<Result<AudioChunk, Error>> {
38        self.terminated = true;
39        // Drop the receiver before joining so a parked sender is released.
40        self.rx = None;
41        match self.scheduler.take() {
42            Some(scheduler) => match scheduler.wait() {
43                Ok(()) => None,
44                Err(e) => Some(Err(e)),
45            },
46            None => None,
47        }
48    }
49}
50
51impl Iterator for SampleIter {
52    type Item = Result<AudioChunk, Error>;
53
54    fn next(&mut self) -> Option<Self::Item> {
55        if self.terminated {
56            return None;
57        }
58        let recv = match self.rx.as_ref() {
59            Some(rx) => rx.recv(),
60            None => return self.finish(),
61        };
62        match recv {
63            Ok(chunk) => Some(Ok(chunk)),
64            // Channel closed: the pipeline finished or died. Join once.
65            Err(_) => self.finish(),
66        }
67    }
68}
69
70impl std::iter::FusedIterator for SampleIter {}
71
72impl Drop for SampleIter {
73    fn drop(&mut self) {
74        // S6: drop the receiver FIRST (unblocks a sink parked in send()), THEN
75        // abort the scheduler (which joins the workers). Reversed order can
76        // deadlock — see FrameIter::drop for the full rationale.
77        self.rx = None;
78        if let Some(scheduler) = self.scheduler.take() {
79            scheduler.abort();
80        }
81    }
82}