Skip to main content

media_pp/elements/source/
pipeline_bridge.rs

1use std::{
2    sync::{
3        Arc, Mutex, Weak,
4        atomic::{AtomicU64, Ordering},
5    },
6    time::Duration,
7};
8
9use crate::pp_log::{PpLog, pp_info, pp_warn};
10use thiserror::Error as ThisError;
11
12use crate::{
13    buffer::MediaBuffer,
14    bus::{Bus, BusEvent},
15    contract::{InputContract, OutputContract},
16    control::{ControlMsg, ControlReceiver, drain_control},
17    element::{Element, ElementType, Sink, Source, SourceElement, element_pp_log},
18    error::Result,
19    pad::SrcPad,
20    queue::OverflowPolicy,
21};
22
23/// How long [`PipelineBridge::run`] waits for a buffer before looking at its
24/// own control channel again.
25///
26/// The reason this is a poll rather than a blocking receive: a bridge with no
27/// input is the ordinary state, not a fault, and a `Stop` sent to it has to
28/// arrive while it is in exactly that state. Short enough that stopping feels
29/// immediate, long enough that an idle bridge is not a spin.
30const CONTROL_POLL_INTERVAL: Duration = Duration::from_millis(10);
31
32/// Errors specific to [`PipelineBridge`].
33#[derive(Debug, ThisError)]
34pub enum PipelineBridgeError {
35    /// This sink was replaced by a later [`PipelineBridgeHandle::connect`],
36    /// or had already reported its own end.
37    ///
38    /// Reported rather than ignored so the pipeline still pushing into it
39    /// hears about it: a `Queue` posts it to its own bus and stops, which is
40    /// how a producer nobody is reading learns to end.
41    #[error("this bridge input was superseded by a later connection")]
42    Superseded,
43
44    /// The buffer could not be handed over before the configured
45    /// [`OverflowPolicy::Block`] timeout elapsed.
46    #[error("the bridge did not take a buffer within {0:?}")]
47    SendTimedOut(Duration),
48
49    /// Seeking was asked of a bridge. The timeline belongs to whatever feeds
50    /// it, in a pipeline this one has no authority over.
51    #[error("a PipelineBridge cannot seek what another pipeline is producing")]
52    SeekUnsupported,
53
54    /// The bridge's own pipeline has finished, so there is nothing on the
55    /// other side any more.
56    ///
57    /// Distinct from [`PipelineBridgeError::Superseded`] because the feeding
58    /// side answers them differently: a superseded input can connect again,
59    /// and this one has nowhere left to connect to.
60    #[error("the pipeline on the other side of this bridge has finished")]
61    Disconnected,
62}
63
64/// Construction-time options for [`PipelineBridge::new`].
65#[derive(Debug, Clone, Copy)]
66pub struct PipelineBridgeOptions {
67    /// How many buffers may sit between the two pipelines.
68    ///
69    /// The same trade a [`crate::queue::Queue`] makes: room to absorb one
70    /// side being briefly busy, at the cost of that many buffers' worth of
71    /// latency and of whatever they hold open.
72    pub depth: usize,
73    /// What happens when that room runs out — see [`OverflowPolicy`].
74    pub policy: OverflowPolicy,
75}
76
77impl Default for PipelineBridgeOptions {
78    fn default() -> Self {
79        Self {
80            depth: 8,
81            policy: OverflowPolicy::default(),
82        }
83    }
84}
85
86/// Carries buffers from one [`crate::pipeline::Pipeline`] into another, so
87/// the two can start, end and fail independently.
88///
89/// # What it is for
90///
91/// A pipeline is one-shot: a source that dies is not restarted, it is
92/// replaced, and replacing it means building a new pipeline. Everything
93/// downstream of it would go with it — unless the boundary falls between
94/// them. This is that boundary, in the general case.
95///
96/// The general case is what was missing. Crossing from one pipeline into
97/// another was already possible through [`crate::elements::AudioMixer`] or a
98/// video compositor, and an application whose graph meets at one of those
99/// needs nothing here. But both of them decide what they carry: a media
100/// kind, a format, and a rate of their own. Packets, or frames that are not
101/// to be composited, or anything else that only needs to *cross*, had
102/// nowhere to do it.
103///
104/// # Shape
105///
106/// ```text
107///  pipeline "up"                        pipeline "down"
108///  source ─ … ─ [ PipelineBridgeSink ]  [ PipelineBridge ] ─ … ─ sink
109///                        └──────── one bounded queue ───────┘
110/// ```
111///
112/// The downstream half is this element, driven as its pipeline's own
113/// `SourceElement`. The upstream half is a [`Sink`] from
114/// [`PipelineBridgeHandle::connect`], which whichever pipeline is feeding it
115/// terminates at.
116///
117/// # One input at a time
118///
119/// Deliberately, and unlike a mixer: with no way to combine buffers, several
120/// inputs would only interleave in whatever order they arrived. A second
121/// [`PipelineBridgeHandle::connect`] *replaces* the first, which is the
122/// reconnection path — the old [`Sink`] then refuses with
123/// [`PipelineBridgeError::Superseded`] rather than feeding its own
124/// replacement.
125///
126/// # What an empty bridge does
127///
128/// Nothing, and its pipeline stays alive doing it. A mixer with no input
129/// emits silence and a compositor re-emits its last picture, because each has
130/// a rate of its own; a bridge has only what it is given. So a downstream
131/// pipeline with no upstream is idle rather than finished, and picks up again
132/// when something connects.
133///
134/// # Ends
135///
136/// An input's `Eos` ends *the input*, not the bridge — otherwise the first
137/// disconnection would tear down the very pipeline this exists to keep
138/// running. [`PipelineBridgeHandle::input_ended`] reports it and the next
139/// `connect` starts another.
140///
141/// What ends the bridge is [`PipelineBridgeHandle::finish`], which sends
142/// `Eos` downstream and returns from `run`. A muxer down there writes its
143/// trailer on that; stopping the pipeline instead tells it to abandon the
144/// file. The downstream pipeline's own `finish` does the same thing from the
145/// other side — two doors into one room, which is right when the two halves
146/// have different owners.
147///
148/// # Both sides can still be controlled
149///
150/// Each pipeline keeps its own `pause`, `resume`, `finish` and `stop`, and
151/// they mean what they always did. Pausing the *downstream* one stops the
152/// bridge emitting, which fills the queue between them, which is felt on the
153/// feeding side as ordinary backpressure — blocking or dropping according to
154/// [`PipelineBridgeOptions::policy`]. That is a queue behaving like a queue
155/// rather than anything the bridge decides.
156///
157/// What does not cross is control itself, with one exception: see
158/// [`Sink::control`] on the input this hands out. Seeking is refused here
159/// outright ([`SourceElement::is_seekable`] is `false`) — the timeline
160/// belongs to whatever feeds the bridge, and an application holding both
161/// pipelines seeks the one that owns it.
162///
163/// # Timestamps cross unchanged
164///
165/// The two pipelines have their own [`crate::clock::Clock`] and
166/// [`crate::playback_clock::PlaybackClock`], and this does not re-time what
167/// passes through it — it cannot, not knowing what that is. A mixer re-times
168/// to its own tick and a compositor to its own rate; a bridge hands over the
169/// timestamps it was given.
170///
171/// So downstream must be somewhere those still mean something: a muxer,
172/// which writes what it is handed, or anything preceded by
173/// [`crate::elements::TimestampOrigin`] to re-base them onto the clock that
174/// is actually going to be measured against. What must *not* follow a bridge
175/// unguarded is a [`crate::elements::Pacer`] — it would be pacing one
176/// pipeline's timestamps against another pipeline's clock, which is a stream
177/// released all at once or one that never arrives.
178pub struct PipelineBridge {
179    pp_log: PpLog,
180    name: Arc<str>,
181    shared: Arc<BridgeShared>,
182    pad: SrcPad,
183}
184
185/// Shared between the bridge and every handle and sink derived from it.
186struct BridgeShared {
187    /// The buffers in flight, and which connection put them there.
188    ///
189    /// One lock for the queue and the connection identity together: a sink
190    /// has to check that it is still the live one *and* push under the same
191    /// lock, or a replacement racing with it could have its own first buffer
192    /// overtaken by the old input's last.
193    state: Mutex<BridgeState>,
194    /// Woken by a push, by a connection change, and by `finish`.
195    changed: std::sync::Condvar,
196    /// Issues a distinct identity for every `connect`, including one
197    /// replacing another.
198    next_connection: AtomicU64,
199    /// Buffers `OverflowPolicy::DropNewest` has thrown away.
200    ///
201    /// Counted here rather than reported where it happens, because where it
202    /// happens is a `Sink` on the feeding pipeline's thread and the bus that
203    /// should hear about it belongs to this one. The run loop posts what it
204    /// sees this move by — same visibility a `Queue` gives its own drops,
205    /// from the side that has a bus to say it on.
206    dropped: AtomicU64,
207    options: PipelineBridgeOptions,
208}
209
210struct BridgeState {
211    buffers: std::collections::VecDeque<MediaBuffer>,
212    /// Which connection may push. `None` before the first `connect` and
213    /// after an input ends.
214    connection: Option<u64>,
215    /// Set by an input's `Eos`, cleared by the next `connect`.
216    input_ended: bool,
217    /// Set by `finish`: the bridge sends `Eos` downstream and returns.
218    finished: bool,
219    /// Set by a `Flush` from the feeding side, cleared once the bridge has
220    /// passed it on. A flag rather than a queued marker because a flush that
221    /// waited its turn behind the buffers it invalidates would be no flush.
222    flush: bool,
223}
224
225impl PipelineBridge {
226    /// Creates one, and the handle the feeding side connects through.
227    pub fn new(
228        name: impl Into<String>,
229        options: PipelineBridgeOptions,
230    ) -> (Self, PipelineBridgeHandle) {
231        let name: Arc<str> = name.into().into();
232        let pp_log = element_pp_log(ElementType::Other, &name, None);
233        pp_info!(
234            pp_log: &pp_log,
235            "created: depth={}, policy={:?}",
236            options.depth,
237            options.policy
238        );
239        let shared = Arc::new(BridgeShared {
240            state: Mutex::new(BridgeState {
241                buffers: std::collections::VecDeque::new(),
242                connection: None,
243                input_ended: false,
244                finished: false,
245                flush: false,
246            }),
247            changed: std::sync::Condvar::new(),
248            next_connection: AtomicU64::new(1),
249            dropped: AtomicU64::new(0),
250            options,
251        });
252        let handle = PipelineBridgeHandle {
253            shared: Arc::downgrade(&shared),
254            name: name.clone(),
255        };
256        let pad = SrcPad::with_contract(format!("{name}_src"), OutputContract::Passthrough);
257        (
258            Self {
259                pp_log,
260                name,
261                shared,
262                pad,
263            },
264            handle,
265        )
266    }
267}
268
269/// A cheaply-cloneable way to connect a pipeline to a [`PipelineBridge`], and
270/// to end it.
271///
272/// Holds only a [`Weak`] reference, for the same reason
273/// [`crate::elements::MixerHandle`] does: keeping one after the bridge's own
274/// pipeline has finished must not keep its buffers alive forever, and every
275/// operation becomes a harmless `None` once it is gone.
276#[derive(Clone)]
277pub struct PipelineBridgeHandle {
278    shared: Weak<BridgeShared>,
279    name: Arc<str>,
280}
281
282impl PipelineBridgeHandle {
283    /// A [`Sink`] for the feeding pipeline to terminate at, replacing
284    /// whatever was connected before.
285    ///
286    /// `None` once the bridge's own pipeline has finished.
287    pub fn connect(&self) -> Option<Box<dyn Sink>> {
288        let shared = self.shared.upgrade()?;
289        let id = shared.next_connection.fetch_add(1, Ordering::Relaxed);
290        {
291            let mut state = shared.state.lock().unwrap();
292            // What a *live* input left behind goes with it: cutting one off
293            // mid-stream abandons its timeline, and handing on the tail of it
294            // would put buffers from a stream nobody is producing any more in
295            // front of the new input's own.
296            //
297            // An input that reached its own end is the opposite case. It
298            // finished; what it handed over is complete, and dropping it here
299            // would lose data a producer successfully delivered — the tail of
300            // a clip, or of the connection a reconnection is replacing.
301            if !state.input_ended {
302                state.buffers.clear();
303            }
304            state.connection = Some(id);
305            state.input_ended = false;
306        }
307        shared.changed.notify_all();
308        Some(Box::new(PipelineBridgeSink {
309            pp_log: element_pp_log(ElementType::Other, &self.name, None),
310            name: self.name.clone(),
311            id,
312            shared: self.shared.clone(),
313        }))
314    }
315
316    /// Whether the connected input has reached its own end.
317    ///
318    /// `false` before anything has connected: nothing has ended, it simply
319    /// has not begun. What this is for is noticing that a source finished so
320    /// another can take its place — see [`PipelineBridgeHandle::connect`].
321    pub fn input_ended(&self) -> bool {
322        self.shared
323            .upgrade()
324            .is_some_and(|shared| shared.state.lock().unwrap().input_ended)
325    }
326
327    /// Ends the bridge: `Eos` goes downstream and its `run` returns.
328    ///
329    /// This is the ordered ending, and the difference from stopping the
330    /// downstream pipeline is what a muxer down there does about it — writes
331    /// its trailer, rather than abandoning the file.
332    pub fn finish(&self) {
333        let Some(shared) = self.shared.upgrade() else {
334            return;
335        };
336        shared.state.lock().unwrap().finished = true;
337        shared.changed.notify_all();
338    }
339}
340
341/// The upstream half: what a feeding pipeline's branch ends at.
342struct PipelineBridgeSink {
343    pp_log: PpLog,
344    name: Arc<str>,
345    id: u64,
346    shared: Weak<BridgeShared>,
347}
348
349impl Element for PipelineBridgeSink {
350    fn name(&self) -> Arc<str> {
351        self.name.clone()
352    }
353
354    fn element_type(&self) -> ElementType {
355        ElementType::Other
356    }
357
358    fn pp_log(&self) -> &PpLog {
359        &self.pp_log
360    }
361
362    fn pp_log_mut(&mut self) -> &mut PpLog {
363        &mut self.pp_log
364    }
365}
366
367impl Sink for PipelineBridgeSink {
368    /// Anything, because a bridge is defined by not caring: it exists for
369    /// what a mixer and a compositor cannot carry. Paired with the
370    /// `Passthrough` on the other half, so whatever contract arrives keeps
371    /// propagating past the boundary rather than stopping at it.
372    fn input_contract(&self) -> InputContract {
373        InputContract::Any
374    }
375
376    fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
377        let Some(shared) = self.shared.upgrade() else {
378            // The bridge's own pipeline has finished. Reported rather than
379            // swallowed, for the reason a superseded input is: a producer
380            // nobody reads has no other way to learn it should stop, and
381            // without this the whole feeding pipeline would go on reading,
382            // decoding and handing buffers to nothing at all.
383            return Err(PipelineBridgeError::Disconnected.into());
384        };
385        let deadline = std::time::Instant::now();
386        let mut state = shared.state.lock().unwrap();
387        loop {
388            if state.connection != Some(self.id) {
389                return Err(PipelineBridgeError::Superseded.into());
390            }
391            if let MediaBuffer::Eos = buf {
392                // The input's end, not the bridge's — see the type docs.
393                state.input_ended = true;
394                state.connection = None;
395                drop(state);
396                shared.changed.notify_all();
397                pp_info!(self, "input ended");
398                return Ok(());
399            }
400            if state.buffers.len() < shared.options.depth {
401                state.buffers.push_back(buf);
402                drop(state);
403                shared.changed.notify_all();
404                return Ok(());
405            }
406            match shared.options.policy {
407                OverflowPolicy::DropNewest => {
408                    drop(state);
409                    // Silent to the caller, as this policy is: falling behind
410                    // is what it exists to absorb. The bridge's own run loop
411                    // is what says so, on the bus that belongs to it.
412                    shared.dropped.fetch_add(1, Ordering::Relaxed);
413                    return Ok(());
414                }
415                OverflowPolicy::Block(timeout) => {
416                    let left = timeout.saturating_sub(deadline.elapsed());
417                    if left.is_zero() {
418                        return Err(PipelineBridgeError::SendTimedOut(timeout).into());
419                    }
420                    let (guard, _) = shared
421                        .changed
422                        .wait_timeout(state, left.min(CONTROL_POLL_INTERVAL))
423                        .unwrap();
424                    state = guard;
425                }
426            }
427        }
428    }
429
430    /// `Flush` crosses; nothing else does.
431    ///
432    /// The test is whether the message means something inside an element or
433    /// something about the pipeline that sent it. `Flush` is the first: drop
434    /// what you are holding, which downstream *must* hear after a seek or it
435    /// keeps frames belonging to a timeline that has been left. Ordering is
436    /// not a difficulty for this one message, because arriving ahead of the
437    /// buffers it invalidates is exactly what it is for.
438    ///
439    /// The rest name the sender's own clock. Injecting `Pause` here would
440    /// leave this side's elements believing they are paused while this side's
441    /// [`crate::clock::Clock`] — the one a `Pacer` and the playback clock
442    /// actually read — keeps running. Two authorities over one timeline is
443    /// the defect, not the missing feature; the downstream pipeline has its
444    /// own `pause`, `finish` and `stop` for what its owner wants of it.
445    fn control(&mut self, msg: ControlMsg) -> Result<()> {
446        let Some(shared) = self.shared.upgrade() else {
447            return Ok(());
448        };
449        if matches!(msg, ControlMsg::Flush) {
450            {
451                let mut state = shared.state.lock().unwrap();
452                state.buffers.clear();
453                state.flush = true;
454            }
455            shared.changed.notify_all();
456        }
457        Ok(())
458    }
459}
460
461impl Element for PipelineBridge {
462    fn name(&self) -> Arc<str> {
463        self.name.clone()
464    }
465
466    fn element_type(&self) -> ElementType {
467        ElementType::Other
468    }
469
470    fn pp_log(&self) -> &PpLog {
471        &self.pp_log
472    }
473
474    fn pp_log_mut(&mut self) -> &mut PpLog {
475        &mut self.pp_log
476    }
477}
478
479impl Source for PipelineBridge {
480    fn src_pads(&mut self) -> &mut [SrcPad] {
481        std::slice::from_mut(&mut self.pad)
482    }
483}
484
485impl SourceElement for PipelineBridge {
486    /// Live, because it cannot be asked for a buffer it has not been given.
487    /// Whether what feeds it is live is the other pipeline's business and not
488    /// something this can see.
489    fn is_live(&self) -> bool {
490        true
491    }
492
493    /// The timeline belongs to whatever is upstream, in a pipeline this one
494    /// has no authority over.
495    fn is_seekable(&self) -> bool {
496        false
497    }
498
499    fn seek(&mut self, _target: Duration) -> Result<Duration> {
500        Err(PipelineBridgeError::SeekUnsupported.into())
501    }
502
503    fn run(&mut self, control: &ControlReceiver, bus: &Bus) -> Result<()> {
504        pp_info!(self, "started");
505        let mut reported_drops = 0;
506        loop {
507            let dropped = self.shared.dropped.load(Ordering::Relaxed);
508            if dropped > reported_drops {
509                reported_drops = dropped;
510                bus.post(
511                    &self.pp_log,
512                    BusEvent::Dropped {
513                        element_type: self.element_type(),
514                        name: self.name.clone(),
515                    },
516                );
517            }
518            let outcome = drain_control(control, self, bus)?;
519            if outcome.stopped {
520                pp_info!(self, "stopped");
521                return Ok(());
522            }
523            // Waited on with a timeout rather than blocked on, because having
524            // nothing to carry is this element's ordinary state and a `Stop`
525            // has to arrive while it is in it.
526            let taken = {
527                let shared = &self.shared;
528                let state = shared.state.lock().unwrap();
529                let (mut state, _) = shared
530                    .changed
531                    .wait_timeout(state, CONTROL_POLL_INTERVAL)
532                    .unwrap();
533                if state.finished {
534                    drop(state);
535                    pp_info!(self, "finished");
536                    if let Err(error) = self.pad.push(MediaBuffer::Eos) {
537                        pp_warn!(self, "end of stream was not delivered: {error}");
538                    }
539                    return Ok(());
540                }
541                let flush = std::mem::take(&mut state.flush);
542                (state.buffers.pop_front(), flush)
543            };
544            let (taken, flush) = taken;
545            if flush {
546                self.pad.control(ControlMsg::Flush)?;
547            }
548            if let Some(buffer) = taken {
549                self.shared.changed.notify_all();
550                // One buffer's failure is not this bridge's end, the same way
551                // a `Queue` reports a failing downstream and keeps its worker.
552                if let Err(error) = self.pad.push(buffer) {
553                    bus.post(
554                        &self.pp_log,
555                        BusEvent::Error {
556                            element_type: self.element_type(),
557                            name: self.name.clone(),
558                            error,
559                        },
560                    );
561                }
562            }
563        }
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use std::sync::{Mutex as StdMutex, atomic::AtomicUsize};
570
571    use super::*;
572    use crate::{elements::AppSink, pipeline::Pipeline};
573
574    /// What crossed the bridge, in order.
575    /// A sink, the timestamps that reached it, and how many ends of stream it
576    /// saw.
577    type Watched = (
578        Box<dyn Sink>,
579        Arc<StdMutex<Vec<Option<i64>>>>,
580        Arc<AtomicUsize>,
581    );
582
583    fn watched() -> Watched {
584        let seen = Arc::new(StdMutex::new(Vec::new()));
585        let ends = Arc::new(AtomicUsize::new(0));
586        let sink = AppSink::new("watcher", {
587            let seen = Arc::clone(&seen);
588            let ends = Arc::clone(&ends);
589            move |buffer| {
590                match &buffer {
591                    MediaBuffer::Packet(packet) => seen.lock().unwrap().push(packet.pts()),
592                    MediaBuffer::Eos => {
593                        ends.fetch_add(1, Ordering::Relaxed);
594                    }
595                    _ => {}
596                }
597                Ok(())
598            }
599        });
600        (Box::new(sink), seen, ends)
601    }
602
603    /// Counts flushes and keeps what arrived between them.
604    struct FlushWatcher {
605        pp_log: PpLog,
606        flushed: Arc<AtomicUsize>,
607        seen: Arc<StdMutex<Vec<Option<i64>>>>,
608    }
609
610    impl Element for FlushWatcher {
611        fn name(&self) -> Arc<str> {
612            "flush-watcher".into()
613        }
614        fn element_type(&self) -> ElementType {
615            ElementType::Other
616        }
617        fn pp_log(&self) -> &PpLog {
618            &self.pp_log
619        }
620        fn pp_log_mut(&mut self) -> &mut PpLog {
621            &mut self.pp_log
622        }
623    }
624
625    impl Sink for FlushWatcher {
626        fn consume(&mut self, buf: MediaBuffer) -> Result<()> {
627            if let MediaBuffer::Packet(packet) = &buf {
628                self.seen.lock().unwrap().push(packet.pts());
629            }
630            Ok(())
631        }
632
633        fn control(&mut self, msg: ControlMsg) -> Result<()> {
634            if matches!(msg, ControlMsg::Flush) {
635                self.flushed.fetch_add(1, Ordering::Relaxed);
636            }
637            Ok(())
638        }
639    }
640
641    fn packet(pts: i64) -> MediaBuffer {
642        let mut packet = ffmpeg_next::Packet::empty();
643        packet.set_pts(Some(pts));
644        MediaBuffer::Packet(Arc::new(packet))
645    }
646
647    /// The downstream pipeline, running, with the bridge as its source.
648    fn downstream(bridge: PipelineBridge, sink: Box<dyn Sink>) -> std::sync::Arc<Pipeline> {
649        let pipeline = Pipeline::new("down", bridge, move |source, context| {
650            let branch = context.branch().to(sink)?;
651            context.attach(source, 0, branch)?;
652            Ok(())
653        })
654        .expect("wire the downstream pipeline");
655        pipeline.run().expect("run the downstream pipeline");
656        pipeline
657    }
658
659    fn wait_until(mut ready: impl FnMut() -> bool) {
660        let deadline = std::time::Instant::now() + Duration::from_secs(2);
661        while !ready() && std::time::Instant::now() < deadline {
662            std::thread::sleep(Duration::from_millis(5));
663        }
664    }
665
666    /// The whole point, in one line: what goes in one pipeline comes out of
667    /// the other.
668    #[test]
669    fn a_buffer_put_in_one_end_comes_out_of_the_other() {
670        let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
671        let (sink, seen, _) = watched();
672        let pipeline = downstream(bridge, sink);
673
674        let mut input = handle.connect().expect("the bridge is running");
675        input.consume(packet(7)).expect("hand it over");
676
677        wait_until(|| !seen.lock().unwrap().is_empty());
678        pipeline.stop();
679        assert_eq!(*seen.lock().unwrap(), vec![Some(7)]);
680    }
681
682    /// An input ending is not the bridge ending. If it were, the first
683    /// disconnection would take down the pipeline this exists to keep
684    /// running — and a second input could never replace the first.
685    #[test]
686    fn an_input_ending_leaves_the_bridge_running_for_the_next_one() {
687        let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
688        let (sink, seen, ends) = watched();
689        let pipeline = downstream(bridge, sink);
690
691        let mut first = handle.connect().expect("the bridge is running");
692        first.consume(packet(1)).expect("hand it over");
693        first.consume(MediaBuffer::Eos).expect("end the input");
694        wait_until(|| handle.input_ended());
695
696        assert!(handle.input_ended(), "the handle reports the input's end");
697        assert_eq!(
698            ends.load(Ordering::Relaxed),
699            0,
700            "an input's end must not reach downstream as the bridge's"
701        );
702        assert!(pipeline.is_running(), "nor end the pipeline it drives");
703
704        let mut second = handle.connect().expect("still running");
705        assert!(!handle.input_ended(), "a new input is not an ended one");
706        second.consume(packet(2)).expect("hand it over");
707
708        wait_until(|| seen.lock().unwrap().len() == 2);
709        pipeline.stop();
710        assert_eq!(*seen.lock().unwrap(), vec![Some(1), Some(2)]);
711    }
712
713    /// A replaced input must not be able to feed its own replacement, and
714    /// must be told rather than ignored: the pipeline still pushing into it
715    /// is how a producer nobody reads learns to stop.
716    #[test]
717    fn a_superseded_input_is_refused_rather_than_swallowed() {
718        let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
719        let (sink, seen, _) = watched();
720        let pipeline = downstream(bridge, sink);
721
722        let mut first = handle.connect().expect("the bridge is running");
723        let mut second = handle.connect().expect("replacing the first");
724
725        let refused = first.consume(packet(1));
726        second
727            .consume(packet(2))
728            .expect("the live input still works");
729
730        wait_until(|| !seen.lock().unwrap().is_empty());
731        pipeline.stop();
732
733        assert!(
734            matches!(
735                refused,
736                Err(crate::error::Error::PipelineBridgeError(
737                    PipelineBridgeError::Superseded
738                ))
739            ),
740            "the replaced input has to hear about it, got {refused:?}"
741        );
742        assert_eq!(
743            *seen.lock().unwrap(),
744            vec![Some(2)],
745            "and must not have put anything in front of its replacement"
746        );
747    }
748
749    /// A bridge with nothing to carry is the ordinary state, not a fault —
750    /// and a `Stop` has to arrive while it is in it. Blocking on the queue
751    /// instead of polling would make an idle bridge an unstoppable pipeline.
752    #[test]
753    fn a_starved_bridge_still_stops() {
754        let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
755        let (sink, _, _) = watched();
756        let pipeline = downstream(bridge, sink);
757        // Nothing ever connects.
758        drop(handle);
759
760        let started = std::time::Instant::now();
761        pipeline.stop();
762        assert!(
763            started.elapsed() < Duration::from_secs(1),
764            "stopping an idle bridge took {:?}",
765            started.elapsed()
766        );
767        assert!(!pipeline.is_running());
768    }
769
770    /// `finish` is the ordered ending, and the difference from stopping is
771    /// what a muxer downstream does about it: writes its trailer rather than
772    /// abandoning the file.
773    #[test]
774    fn finish_sends_end_of_stream_downstream() {
775        let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
776        let (sink, _, ends) = watched();
777        let pipeline = downstream(bridge, sink);
778
779        handle.finish();
780        wait_until(|| ends.load(Ordering::Relaxed) > 0);
781
782        assert_eq!(
783            ends.load(Ordering::Relaxed),
784            1,
785            "downstream has to see exactly one end of stream"
786        );
787        wait_until(|| !pipeline.is_running());
788        assert!(!pipeline.is_running(), "and the bridge's own run returns");
789    }
790
791    /// A seek on the feeding side leaves this side holding frames from a
792    /// timeline that has been left. `Flush` is the one message that crosses,
793    /// and it has to arrive ahead of them rather than behind.
794    #[test]
795    fn a_flush_crosses_and_takes_the_queued_buffers_with_it() {
796        let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
797        let flushed = Arc::new(AtomicUsize::new(0));
798        let seen = Arc::new(StdMutex::new(Vec::new()));
799        let sink = FlushWatcher {
800            pp_log: element_pp_log(ElementType::Other, "flush-watcher", None),
801            flushed: Arc::clone(&flushed),
802            seen: Arc::clone(&seen),
803        };
804        let pipeline = downstream(bridge, Box::new(sink));
805        let mut input = handle.connect().expect("the bridge is running");
806
807        input.consume(packet(1)).expect("queued");
808        input
809            .control(ControlMsg::Flush)
810            .expect("a flush from the feeding pipeline");
811        input.consume(packet(2)).expect("after the flush");
812
813        wait_until(|| flushed.load(Ordering::Relaxed) > 0 && !seen.lock().unwrap().is_empty());
814        pipeline.stop();
815
816        assert_eq!(flushed.load(Ordering::Relaxed), 1, "the flush crossed");
817        assert_eq!(
818            *seen.lock().unwrap(),
819            vec![Some(2)],
820            "and what belonged to the timeline it left did not"
821        );
822    }
823
824    /// A downstream pipeline that has ended is not a reason for the feeding
825    /// one to keep reading, decoding and handing buffers to nothing.
826    #[test]
827    fn feeding_a_bridge_whose_pipeline_has_gone_is_refused() {
828        let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
829        let mut input = handle.connect().expect("the bridge is alive");
830        drop(bridge);
831
832        assert!(
833            matches!(
834                input.consume(packet(1)),
835                Err(crate::error::Error::PipelineBridgeError(
836                    PipelineBridgeError::Disconnected
837                ))
838            ),
839            "the feeding side has to hear that there is nothing on the other side"
840        );
841    }
842
843    /// The other half of that rule: an input cut off while it was still
844    /// running leaves nothing behind, because what it had queued belongs to a
845    /// stream that is no longer being produced.
846    #[test]
847    fn superseding_a_live_input_discards_what_it_had_queued() {
848        let (bridge, handle) = PipelineBridge::new("bridge", PipelineBridgeOptions::default());
849        // No pipeline, so nothing drains and what is queued stays queued.
850        let shared = Arc::clone(&bridge.shared);
851        let mut first = handle.connect().expect("the bridge is alive");
852
853        first.consume(packet(1)).expect("queued behind nothing");
854        assert_eq!(shared.state.lock().unwrap().buffers.len(), 1);
855
856        let _second = handle.connect().expect("replacing a live input");
857
858        assert!(
859            shared.state.lock().unwrap().buffers.is_empty(),
860            "an abandoned timeline's buffers must not reach the new input's reader"
861        );
862        drop(bridge);
863    }
864
865    /// Falling behind is what `DropNewest` exists to absorb, so the feeding
866    /// side is not told — but something has to say it happened, and the bus
867    /// that can is the bridge's own.
868    #[test]
869    fn dropping_under_the_newest_policy_is_reported_on_the_bridges_own_bus() {
870        let (bridge, handle) = PipelineBridge::new(
871            "bridge",
872            PipelineBridgeOptions {
873                depth: 1,
874                policy: OverflowPolicy::DropNewest,
875            },
876        );
877        // No pipeline: nothing drains, so the second buffer has nowhere to go.
878        let shared = Arc::clone(&bridge.shared);
879        let mut input = handle.connect().expect("the bridge is alive");
880
881        input.consume(packet(1)).expect("fills the one slot");
882        input
883            .consume(packet(2))
884            .expect("dropped, and not an error here");
885
886        assert_eq!(
887            shared.dropped.load(Ordering::Relaxed),
888            1,
889            "the drop is counted for the run loop to report"
890        );
891        drop(bridge);
892    }
893}