rtc_interceptor/intervalpli/
generator.rs1use super::stream_supports_pli;
4use crate::Interceptor;
5use crate::stream_info::StreamInfo;
6use crate::{AttributedPacket, Packet, TaggedPacket};
7use sansio::Protocol;
8use shared::TransportContext;
9use shared::error::Error;
10use std::collections::{BTreeSet, VecDeque};
11use std::time::{Duration, Instant};
12
13pub const DEFAULT_INTERVAL: Duration = Duration::from_secs(3);
15
16pub struct IntervalPliInterceptor {
27 interval: Duration,
28 streams: BTreeSet<u32>,
30 pending_immediate: BTreeSet<u32>,
35 next_timeout: Option<Instant>,
36 write_queue: VecDeque<TaggedPacket>,
37 read_queue: VecDeque<TaggedPacket>,
39}
40
41impl Default for IntervalPliInterceptor {
42 fn default() -> Self {
43 Self::new(DEFAULT_INTERVAL)
44 }
45}
46
47impl IntervalPliInterceptor {
48 pub fn new(interval: Duration) -> Self {
53 Self {
54 read_queue: VecDeque::new(),
55 interval,
56 streams: BTreeSet::new(),
57 pending_immediate: BTreeSet::new(),
58 next_timeout: None,
59 write_queue: VecDeque::new(),
60 }
61 }
62
63 pub fn bound_streams(&self) -> impl Iterator<Item = u32> + '_ {
65 self.streams.iter().copied()
66 }
67
68 fn queue_plis(&mut self, now: Instant, ssrcs: &[u32]) {
73 if ssrcs.is_empty() {
74 return;
75 }
76
77 for ssrc in ssrcs {
80 self.pending_immediate.remove(ssrc);
81 }
82
83 let plis: Vec<Box<dyn rtcp::Packet>> = ssrcs
84 .iter()
85 .map(|&ssrc| {
86 Box::new(
87 rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication {
88 sender_ssrc: 0,
89 media_ssrc: ssrc,
90 },
91 ) as Box<dyn rtcp::Packet>
92 })
93 .collect();
94
95 self.write_queue.push_back(TaggedPacket {
96 now,
97 transport: TransportContext::default(),
98 message: AttributedPacket::new(Packet::Rtcp(plis)),
99 });
100 }
101
102 fn observe(&mut self, now: Instant) {
105 if !self.pending_immediate.is_empty() {
106 let ssrcs: Vec<u32> = self.pending_immediate.iter().copied().collect();
107 self.queue_plis(now, &ssrcs);
108 }
109 self.arm(now);
110 }
111
112 fn arm(&mut self, now: Instant) {
114 if self.next_timeout.is_none() && self.is_periodic() && !self.streams.is_empty() {
115 self.next_timeout = Some(now + self.interval);
116 }
117 }
118
119 fn is_periodic(&self) -> bool {
120 !self.interval.is_zero()
121 }
122
123 fn targets(&self, requested: Option<&Vec<u32>>) -> Vec<u32> {
127 match requested {
128 None => self.streams.iter().copied().collect(),
129 Some(ssrcs) => ssrcs
130 .iter()
131 .copied()
132 .filter(|ssrc| self.streams.contains(ssrc))
133 .collect(),
134 }
135 }
136}
137
138impl Protocol<TaggedPacket, TaggedPacket, ()> for IntervalPliInterceptor {
139 type Rout = TaggedPacket;
140 type Wout = TaggedPacket;
141 type Eout = ();
142 type Error = Error;
143 type Time = Instant;
144
145 fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
146 self.observe(msg.now);
147
148 self.read_queue.push_back(msg);
152 Ok(())
153 }
154
155 fn poll_read(&mut self) -> Option<Self::Rout> {
156 self.read_queue.pop_front()
157 }
158
159 fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
160 self.observe(msg.now);
161
162 self.write_queue.push_back(msg);
165 Ok(())
166 }
167
168 fn poll_write(&mut self) -> Option<TaggedPacket> {
169 self.write_queue.pop_front()
170 }
171
172 fn handle_timeout(&mut self, now: Instant) -> Result<(), Error> {
173 self.observe(now);
174
175 if let Some(next_timeout) = self.next_timeout
176 && now >= next_timeout
177 {
178 self.next_timeout = Some(now + self.interval);
179 let ssrcs: Vec<u32> = self.streams.iter().copied().collect();
180 self.queue_plis(now, &ssrcs);
181 }
182 Ok(())
183 }
184
185 fn poll_timeout(&mut self) -> Option<Instant> {
186 self.next_timeout
187 }
188}
189
190impl Interceptor for IntervalPliInterceptor {
191 fn bind_remote_stream(&mut self, info: &StreamInfo) {
192 if stream_supports_pli(info) {
193 self.streams.insert(info.ssrc);
194 self.pending_immediate.insert(info.ssrc);
195 }
196 }
197
198 fn unbind_remote_stream(&mut self, info: &StreamInfo) {
199 self.streams.remove(&info.ssrc);
200 self.pending_immediate.remove(&info.ssrc);
201 if self.streams.is_empty() {
202 self.next_timeout = None;
204 }
205 }
206
207 fn bind_local_stream(&mut self, _info: &StreamInfo) {}
208
209 fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use crate::chain::Chain;
216 use crate::stream_info::RTCPFeedback;
217 use sansio::Protocol;
218
219 fn stream_info(ssrc: u32) -> StreamInfo {
220 StreamInfo {
221 ssrc,
222 rtcp_feedback: vec![RTCPFeedback {
223 typ: "nack".to_owned(),
224 parameter: "pli".to_owned(),
225 }],
226 ..Default::default()
227 }
228 }
229
230 fn plis(chain: &mut Chain) -> Vec<u32> {
231 let mut out = Vec::new();
232 while let Some(pkt) = chain.poll_write() {
233 if let Packet::Rtcp(packets) = &pkt.message.packet {
234 for p in packets {
235 if let Some(pli) = p
236 .as_any()
237 .downcast_ref::<rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication>(
238 ) {
239 out.push(pli.media_ssrc);
240 }
241 }
242 }
243 }
244 out
245 }
246
247 fn chain(interval: Duration) -> Chain {
248 Chain::new(vec![Box::new(IntervalPliInterceptor::new(interval))])
249 }
250
251 #[test]
252 fn a_bound_stream_is_asked_immediately() {
253 let now = Instant::now();
254 let mut chain = chain(Duration::from_secs(3));
255 chain.bind_remote_stream(&stream_info(7));
256
257 chain.handle_timeout(now).unwrap();
258 assert_eq!(vec![7], plis(&mut chain));
259 }
260
261 #[test]
262 fn requests_repeat_on_the_interval() {
263 let now = Instant::now();
264 let mut chain = chain(Duration::from_secs(1));
265 chain.bind_remote_stream(&stream_info(7));
266
267 chain.handle_timeout(now).unwrap();
268 assert_eq!(vec![7], plis(&mut chain), "the immediate one");
269
270 chain
271 .handle_timeout(now + Duration::from_millis(999))
272 .unwrap();
273 assert!(plis(&mut chain).is_empty(), "not due yet");
274
275 chain.handle_timeout(now + Duration::from_secs(1)).unwrap();
276 assert_eq!(vec![7], plis(&mut chain), "due");
277 }
278
279 #[test]
280 fn an_unbound_stream_stops_being_asked() {
281 let now = Instant::now();
282 let mut chain = chain(Duration::from_secs(1));
283 chain.bind_remote_stream(&stream_info(7));
284 chain.handle_timeout(now).unwrap();
285 let _ = plis(&mut chain);
286
287 chain.unbind_remote_stream(&stream_info(7));
288 chain.handle_timeout(now + Duration::from_secs(5)).unwrap();
289
290 assert!(plis(&mut chain).is_empty());
291 assert_eq!(None, chain.poll_timeout(), "and stops asking to be woken");
292 }
293
294 #[test]
296 fn a_zero_interval_disables_the_periodic_requests() {
297 let now = Instant::now();
298 let mut chain = chain(Duration::ZERO);
299 chain.bind_remote_stream(&stream_info(7));
300
301 chain.handle_timeout(now).unwrap();
302 assert_eq!(vec![7], plis(&mut chain), "the immediate one still happens");
303 assert_eq!(None, chain.poll_timeout(), "but no interval is armed");
304
305 chain.handle_timeout(now + Duration::from_secs(60)).unwrap();
306 assert!(plis(&mut chain).is_empty());
307 }
308}