media_pp\core/pad.rs
1use std::sync::Arc;
2
3use crate::{
4 buffer::MediaBuffer,
5 control::ControlMsg,
6 element::{ElementType, Sink},
7 error::Result,
8 pp_log::{PpLog, pp_trace},
9};
10
11/// An output port an [`Element`](crate::element::Element) owns. Data only
12/// ever leaves an element through one of its src pads — there is no other
13/// way to reach downstream.
14///
15/// This is what fan-out is, in this design: an element with more than one
16/// src pad *is* a tee — there's no separate "Tee" primitive in the
17/// pad/element model itself. [`crate::elements::FileDemuxer`] is the
18/// plain form of it: one pad per container stream, chosen once at wiring
19/// time via a normal `&mut [SrcPad]`. [`crate::elements::Tee`] is the one
20/// deliberate exception to that plain shape — its pads live behind a lock
21/// instead, so a `TeeHandle` can add or remove one from a different
22/// thread than whatever is driving it; see that module for why.
23pub struct SrcPad {
24 name: String,
25 peer: Option<Box<dyn Sink>>,
26}
27
28impl SrcPad {
29 pub fn new(name: impl Into<String>) -> Self {
30 Self {
31 name: name.into(),
32 peer: None,
33 }
34 }
35
36 pub fn name(&self) -> &str {
37 &self.name
38 }
39
40 pub fn is_linked(&self) -> bool {
41 self.peer.is_some()
42 }
43
44 /// The linked sink's own identity, without touching the link itself —
45 /// lets a caller that just saw [`SrcPad::push`]/[`SrcPad::control`]
46 /// fail (e.g. [`crate::elements::Tee`], fanning out to several pads at
47 /// once) report *which* downstream element the failure actually came
48 /// from, instead of only knowing its own. `None` for an unlinked pad.
49 pub fn peer_identity(&self) -> Option<(ElementType, Arc<str>)> {
50 self.peer
51 .as_ref()
52 .map(|sink| (sink.element_type(), sink.name()))
53 }
54
55 /// Runtime half of a connection. Pipeline users connect through
56 /// [`crate::element::Context::attach`], which keeps the graph and this
57 /// peer in sync. Kept crate-visible for element-level unit tests.
58 pub(crate) fn link(&mut self, sink: Box<dyn Sink>) {
59 self.peer = Some(sink);
60 }
61
62 /// Pushes a buffer to whatever this pad is linked to. Pushing into an
63 /// unlinked pad silently drops the buffer (e.g. a demuxer stream
64 /// nobody cared to link).
65 pub fn push(&mut self, buf: MediaBuffer) -> Result<()> {
66 match &mut self.peer {
67 Some(sink) => sink.consume(buf),
68 None => Ok(()),
69 }
70 }
71
72 /// Sends a source-originated EOS with explicit pad-level trace records.
73 /// Filters are traced by the pipeline's common element wrapper; this is
74 /// for the source boundary where EOS first enters the dataflow graph.
75 pub(crate) fn push_eos(&mut self, pp_log: &PpLog) -> Result<()> {
76 if self.peer.is_none() {
77 pp_trace!(
78 pp_log: pp_log,
79 "event=eos phase=skipped pad={} reason=unlinked",
80 self.name
81 );
82 return Ok(());
83 }
84
85 pp_trace!(
86 pp_log: pp_log,
87 "event=eos phase=sending pad={}",
88 self.name
89 );
90 let result = self.push(MediaBuffer::Eos);
91 match &result {
92 Ok(()) => pp_trace!(
93 pp_log: pp_log,
94 "event=eos phase=sent pad={} outcome=ok",
95 self.name
96 ),
97 Err(error) => pp_trace!(
98 pp_log: pp_log,
99 "event=eos phase=sent pad={} outcome=error error={error}",
100 self.name
101 ),
102 }
103 result
104 }
105
106 /// Forwards a [`ControlMsg`] to whatever this pad is linked to —
107 /// mirrors [`SrcPad::push`], just for control instead of data.
108 /// Pushing into an unlinked pad is a no-op, same as `push`.
109 pub fn control(&mut self, msg: ControlMsg) -> Result<()> {
110 match &mut self.peer {
111 Some(sink) => sink.control(msg),
112 None => Ok(()),
113 }
114 }
115}