1use std::collections::{BTreeMap, BTreeSet};
2
3use o_sfu_router::{MediaKind as RouterMediaKind, negotiation::negotiate_consumer_rtp_parameters};
4
5use super::{
6 super::state::{ActiveUser, RoomState},
7 ConsumerId, ConsumerRouteTarget, SubscriptionKey,
8 consumer_setup::{ConsumerSetupTarget, PendingConsumerSetup},
9};
10use crate::engine::{
11 ConnectionId, UserId,
12 media_transport::{TransportMediaId, TransportRelayRouteEffect, TransportTeardown},
13 room::{
14 outbound::{OutboundSender, VersionedRemoteTrackSnapshot},
15 source_policy::VideoAdmissionRank,
16 },
17 source_model::{
18 ConsumerSourceSelection, PolicyPauseReason, PublishedSourceId, SourceRoutePriority,
19 SourceSubscriptionIntent, UserStreamId,
20 },
21};
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct ReceiverRouteActivity {
25 target: ConsumerRouteTarget,
26 active: bool,
27}
28
29impl ReceiverRouteActivity {
30 pub const fn new(target: ConsumerRouteTarget, active: bool) -> Self {
31 Self { target, active }
32 }
33
34 pub const fn target(&self) -> &ConsumerRouteTarget {
35 &self.target
36 }
37
38 pub const fn active(&self) -> bool {
39 self.active
40 }
41}
42
43#[cfg(any(test, feature = "testing-transport"))]
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum ConsumerRouteState {
47 Absent,
48 Inactive,
49 Active,
50}
51
52#[derive(Debug, Default)]
53pub struct ReceiverRouteWork {
54 pub(in crate::engine::room) activities: Vec<ReceiverRouteActivity>,
55 pub(in crate::engine::room) setups: Vec<PendingConsumerSetup>,
56 pub(in crate::engine::room) relays: Vec<TransportRelayRouteEffect>,
57 pub(in crate::engine::room) teardown: Vec<TransportTeardown>,
58}
59
60#[derive(Debug)]
61pub struct ReceiverRouteCommit {
62 pub(in crate::engine::room) work: ReceiverRouteWork,
63 pub(in crate::engine::room) track_snapshots:
64 Vec<(OutboundSender, VersionedRemoteTrackSnapshot)>,
65}
66
67#[derive(Clone, Copy)]
68pub(super) enum ReceiverRouteScope<'a> {
69 Source(PublishedSourceId),
70 Receiver(&'a UserId, ConnectionId),
71 SourceUser(&'a UserId, ConnectionId, &'a UserId),
72}
73
74impl RoomState {
75 pub fn apply_receiver_intent(
76 &mut self,
77 user_id: &UserId,
78 connection_id: ConnectionId,
79 target_user_id: &UserId,
80 intents: &BTreeMap<UserStreamId, SourceSubscriptionIntent>,
81 ) -> Option<ReceiverRouteCommit> {
82 self.user_for_connection(user_id, connection_id)?;
83 let work =
84 self.plan_receiver_intent_change(user_id, connection_id, target_user_id, intents);
85 Some(ReceiverRouteCommit {
86 work,
87 track_snapshots: Vec::new(),
88 })
89 }
90
91 #[cfg(test)]
92 pub fn plan_receiver_route_work(
93 &mut self,
94 user_id: &UserId,
95 connection_id: ConnectionId,
96 target_user_id: &UserId,
97 intents: &BTreeMap<UserStreamId, SourceSubscriptionIntent>,
98 ) -> ReceiverRouteWork {
99 if self.user_for_connection(user_id, connection_id).is_none() {
100 return ReceiverRouteWork::default();
101 }
102 self.plan_receiver_intent_change(user_id, connection_id, target_user_id, intents)
103 }
104
105 fn plan_receiver_intent_change(
106 &mut self,
107 user_id: &UserId,
108 connection_id: ConnectionId,
109 target_user_id: &UserId,
110 intents: &BTreeMap<UserStreamId, SourceSubscriptionIntent>,
111 ) -> ReceiverRouteWork {
112 for (stream_id, intent) in intents {
113 self.topology.merge_subscription_intent(
114 SubscriptionKey::new(user_id, target_user_id, stream_id),
115 *intent,
116 );
117 }
118 let (updates, relays) =
119 self.apply_route_updates(user_id, connection_id, target_user_id, intents);
120 let ReceiverRouteWork { setups, .. } = self.plan_missing_receiver_routes(
121 ReceiverRouteScope::SourceUser(user_id, connection_id, target_user_id),
122 );
123 ReceiverRouteWork {
124 activities: updates,
125 setups,
126 relays,
127 ..Default::default()
128 }
129 }
130
131 pub fn refresh_consumer_readiness(
132 &mut self,
133 user_id: &UserId,
134 connection_id: ConnectionId,
135 declined_consumers: &[TransportMediaId],
136 ) -> Option<ReceiverRouteCommit> {
137 let sender = {
138 let user = self.user_for_connection(user_id, connection_id)?;
139 user.parsed_client_rtp_capabilities.as_ref()?;
140 (!declined_consumers.is_empty()).then(|| user.sender.clone())
141 };
142 let session = self
143 .topology
144 .transport_user_key(user_id.clone(), connection_id);
145 let mut work =
148 self.plan_missing_receiver_routes(ReceiverRouteScope::Receiver(user_id, connection_id));
149 let (relays, teardown, detached) = self
150 .topology
151 .detach_declined_consumers(&session, declined_consumers);
152 work.relays.extend(relays);
153 work.teardown.extend(teardown);
154 let track_snapshots = sender
155 .filter(|_| detached)
156 .map(|sender| (sender, self.remote_track_snapshot_for_user(user_id, false)))
157 .into_iter()
158 .collect();
159 Some(ReceiverRouteCommit {
160 work,
161 track_snapshots,
162 })
163 }
164
165 #[cfg(test)]
166 pub fn plan_missing_consumers(
167 &mut self,
168 user_id: &UserId,
169 connection_id: ConnectionId,
170 ) -> Option<Vec<PendingConsumerSetup>> {
171 let user = self.user_for_connection(user_id, connection_id)?;
172 if user.parsed_client_rtp_capabilities.is_none() {
173 return Some(Vec::new());
174 }
175 let ReceiverRouteWork { setups, .. } =
176 self.plan_missing_receiver_routes(ReceiverRouteScope::Receiver(user_id, connection_id));
177 Some(setups)
178 }
179
180 pub(super) fn plan_missing_receiver_routes(
181 &mut self,
182 scope: ReceiverRouteScope<'_>,
183 ) -> ReceiverRouteWork {
184 let targets = self.missing_receiver_route_targets(scope);
185 ReceiverRouteWork {
186 setups: self.plan_consumers(targets),
187 ..Default::default()
188 }
189 }
190
191 fn plan_consumers(
192 &mut self,
193 mut targets: Vec<ConsumerSetupTarget>,
194 ) -> Vec<PendingConsumerSetup> {
195 let active_speakers = BTreeSet::new();
198 targets.sort_by_key(|target| self.setup_rank(target, &active_speakers));
199 targets
200 .into_iter()
201 .filter_map(|target| self.plan_consumer(target))
202 .collect()
203 }
204
205 fn setup_rank(
206 &self,
207 target: &ConsumerSetupTarget,
208 active_speakers: &BTreeSet<UserId>,
209 ) -> VideoAdmissionRank {
210 if target.kind != RouterMediaKind::Video {
211 return VideoAdmissionRank::new(
212 SourceRoutePriority::PinnedOrFeatured,
213 None,
214 target.source_id,
215 );
216 }
217 let Some(source) = self.topology.source_descriptor(target.source_id) else {
218 return VideoAdmissionRank::new(
219 SourceRoutePriority::HiddenOrOverflow,
220 None,
221 target.source_id,
222 );
223 };
224 VideoAdmissionRank::new(
225 self.receiver_video_layout_role(target.session.user_id(), source, active_speakers)
226 .priority(),
227 None,
228 target.source_id,
229 )
230 }
231
232 fn apply_route_updates(
233 &mut self,
234 user_id: &UserId,
235 connection_id: ConnectionId,
236 target_user_id: &UserId,
237 intents: &BTreeMap<UserStreamId, SourceSubscriptionIntent>,
238 ) -> (Vec<ReceiverRouteActivity>, Vec<TransportRelayRouteEffect>) {
239 let mut updates = Vec::new();
240 let mut relays = Vec::new();
241 let receiver_deafened = self
242 .user_for_connection(user_id, connection_id)
243 .is_some_and(ActiveUser::is_deaf);
244 for (stream_id, intent) in intents {
245 let Some(active) = intent.active() else {
246 continue;
247 };
248 let Some(commit) = self.topology.set_consumer_activity(
249 user_id,
250 connection_id,
251 target_user_id,
252 stream_id,
253 active,
254 active && receiver_deafened,
255 ) else {
256 continue;
257 };
258 relays.extend(commit.relay_effects);
259 if let Some(update) = commit.update {
260 updates.push(update);
261 }
262 }
263 (updates, relays)
264 }
265
266 fn missing_receiver_route_targets(
267 &mut self,
268 scope: ReceiverRouteScope<'_>,
269 ) -> Vec<ConsumerSetupTarget> {
270 match scope {
271 ReceiverRouteScope::Source(source_id) => {
272 let users = &self.users;
273 self.topology.missing_consumer_targets_for_source(
274 source_id,
275 users
276 .iter()
277 .map(|(user, state)| (user, state.connection_id)),
278 )
279 }
280 ReceiverRouteScope::Receiver(user, connection) => self
281 .topology
282 .missing_consumer_targets(user, connection, |_| true),
283 ReceiverRouteScope::SourceUser(user, connection, source_user_id) => self
284 .topology
285 .missing_consumer_targets(user, connection, |source| {
286 source.descriptor.owner().user_id() == source_user_id
287 }),
288 }
289 }
290
291 fn plan_consumer(&mut self, target: ConsumerSetupTarget) -> Option<PendingConsumerSetup> {
292 let (sender, client_caps) = {
293 let user = self.users.get(target.session.user_id())?;
294 if user.connection_id != target.session.connection_id() {
295 return None;
296 }
297 (
298 user.sender.clone(),
299 user.parsed_client_rtp_capabilities.as_ref()?,
300 )
301 };
302 let source = self.topology.published_source(target.source_id)?;
303 if !target.matches_identity(source) {
304 return None;
305 }
306 let selection = self.setup_selection(&target, source.active);
307 let rtp = negotiate_consumer_rtp_parameters(&source.rtp, client_caps).ok()?;
308 let consumer = ConsumerId::allocate(&mut self.next_consumer_id);
309 self.topology
310 .reserve_consumer_setup(target, consumer, selection, sender, rtp)
311 }
312
313 pub(super) fn setup_selection(
314 &self,
315 target: &ConsumerSetupTarget,
316 source_active: bool,
317 ) -> ConsumerSourceSelection {
318 let key = target.subscription_key();
319 let selection = self
320 .topology
321 .consumer_source_selection(&key, target.source_id)
322 .unwrap_or_else(|| {
323 ConsumerSourceSelection::open(
324 self.topology
325 .subscription_intent(&key)
326 .active()
327 .unwrap_or(true),
328 )
329 });
330 let selection = self.apply_initial_video_download_cap(target, source_active, selection);
331 self.apply_initial_receiver_deafened(target, selection)
332 }
333
334 fn apply_initial_receiver_deafened(
335 &self,
336 target: &ConsumerSetupTarget,
337 mut selection: ConsumerSourceSelection,
338 ) -> ConsumerSourceSelection {
339 if target.kind == RouterMediaKind::Audio
340 && selection.delivery_active()
341 && self
342 .user_for_connection(target.session.user_id(), target.session.connection_id())
343 .is_some_and(ActiveUser::is_deaf)
344 {
345 selection.set_policy_pause_reason(Some(PolicyPauseReason::ReceiverDeafened));
346 }
347 selection
348 }
349
350 fn apply_initial_video_download_cap(
351 &self,
352 target: &ConsumerSetupTarget,
353 source_active: bool,
354 mut selection: ConsumerSourceSelection,
355 ) -> ConsumerSourceSelection {
356 if target.kind != RouterMediaKind::Video
357 || !source_active
358 || !selection.delivery_active()
359 || self.active_video_count(target.session.user_id())
360 < self.media_limits.max_video_downloads_per_receiver()
361 {
362 return selection;
363 }
364 selection.set_policy_pause_reason(Some(PolicyPauseReason::VideoDownloadLimit));
365 selection
366 }
367
368 fn active_video_count(&self, consumer_user_id: &UserId) -> usize {
369 let committed = self
370 .topology
371 .committed_consumer_routes_for_user(consumer_user_id)
372 .filter(|route| route.source.descriptor.media_kind() == RouterMediaKind::Video)
373 .filter(|route| route.source.active)
374 .filter(|route| route.selection.delivery_active())
375 .count();
376 let pending = self
379 .topology
380 .pending_consumer_routes_for_user(consumer_user_id)
381 .filter(|route| route.source.descriptor.media_kind() == RouterMediaKind::Video)
382 .filter(|route| route.source.active)
383 .filter(|route| route.selection.delivery_active())
384 .count();
385 committed + pending
386 }
387
388 #[cfg(any(test, feature = "testing-transport"))]
389 pub fn consumer_route_state(
390 &self,
391 consumer_user_id: &UserId,
392 producer_user_id: &UserId,
393 stream_id: &UserStreamId,
394 ) -> Option<ConsumerRouteState> {
395 self.users.get(consumer_user_id)?;
396 let key = SubscriptionKey::new(consumer_user_id, producer_user_id, stream_id);
397 let Some(route) = self.topology.committed_consumer_route_for_key(&key) else {
398 return Some(ConsumerRouteState::Absent);
399 };
400 let route_active = route.source.active && route.selection.delivery_active();
401 Some(if route_active {
402 ConsumerRouteState::Active
403 } else {
404 ConsumerRouteState::Inactive
405 })
406 }
407}