1use std::{fmt::Write as _, net::SocketAddr, sync::Arc};
11
12use o_sfu_rfc::{tcp_framing::TcpFrame, webrtc::sdp};
13use o_sfu_router::rtp::MediaStream as RouterRtpParameters;
14use str0m::{
15 change::{SdpAnswer, SdpOffer},
16 media::{KeyframeRequestKind, MediaKind, Rid},
17};
18use tokio::sync::{mpsc, oneshot};
19
20use super::{
21 codec::{ParsedAnswerRids, RepairSummary, validate_answer_sdp},
22 state::{
23 relay_registry::{RelayPacketMailbox, RelayTargetId},
24 route_control::PacketLayerGate,
25 },
26};
27use crate::engine::{
28 media_transport::{
29 ActiveSpeakerSource, AppliedSessionAnswer, ConsumerRouteControl,
30 ConsumerRouteControlOutcome, ProducerRouteControl, ReceiverBweTargetUpdate, SessionOffer,
31 SessionUploadSlot, SourceActivityUpdate, TransportAdapterError, TransportConsumerRoute,
32 TransportMediaId, TransportResult, TransportSessionKey, TransportSourceDiagnosticsSnapshot,
33 TransportSourceKey,
34 },
35 metrics::{
36 RtcMetricsRecorder, RtcRemoteControlDropKind, RtcRemotePacketGateConvergence,
37 RtcWorkerObservationKind,
38 },
39 source_model::SourceSyncPolicy,
40};
41
42#[derive(Debug, Clone)]
53pub struct RemoteSourceControl {
54 tx: mpsc::Sender<RtcWorkerCommand>,
55 target_id: RelayTargetId,
56 rtc_metrics: Arc<RtcMetricsRecorder>,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub(super) enum RemoteControlSendOutcome {
61 Forwarded,
62 Full,
63 Closed,
64}
65
66impl RemoteSourceControl {
67 pub(super) fn new(
69 tx: mpsc::Sender<RtcWorkerCommand>,
70 target_id: RelayTargetId,
71 rtc_metrics: Arc<RtcMetricsRecorder>,
72 ) -> Self {
73 Self {
74 tx,
75 target_id,
76 rtc_metrics,
77 }
78 }
79
80 pub(super) fn request_kf(
85 &self,
86 source: &TransportSourceKey,
87 rid: Option<Rid>,
88 kind: KeyframeRequestKind,
89 ) -> RemoteControlSendOutcome {
90 self.send_command(
91 RouteControlRequest::RequestRemoteKeyframe {
92 source: source.clone(),
93 target_id: self.target_id,
94 rid,
95 kind,
96 },
97 RtcRemoteControlDropKind::Keyframe,
98 )
99 }
100
101 pub(super) fn set_pkt_gate(
105 &self,
106 source: &TransportSourceKey,
107 packet_gate: PacketLayerGate,
108 ) -> RemoteControlSendOutcome {
109 self.send_command(
110 RouteControlRequest::SetRemoteSourcePacketGate {
111 source: source.clone(),
112 target_id: self.target_id,
113 packet_gate,
114 },
115 RtcRemoteControlDropKind::PacketGate,
116 )
117 }
118
119 pub(super) fn record_pkt_gate_retry(&self) {
120 self.rtc_metrics
121 .record_rtc_remote_packet_gate_convergence(RtcRemotePacketGateConvergence::Retry);
122 }
123
124 pub(super) fn record_pkt_gate_flushed(&self) {
125 self.rtc_metrics
126 .record_rtc_remote_packet_gate_convergence(RtcRemotePacketGateConvergence::Flushed);
127 }
128
129 #[inline]
130 fn send_command(
131 &self,
132 request: RouteControlRequest,
133 drop_kind: RtcRemoteControlDropKind,
134 ) -> RemoteControlSendOutcome {
135 match self.tx.try_send(RtcWorkerCommand::RouteControl {
136 request,
137 response: None,
138 }) {
139 Ok(()) => RemoteControlSendOutcome::Forwarded,
140 Err(mpsc::error::TrySendError::Full(_)) => {
141 self.rtc_metrics.record_rtc_remote_control_drop(drop_kind);
142 RemoteControlSendOutcome::Full
143 }
144 Err(mpsc::error::TrySendError::Closed(_)) => {
145 self.rtc_metrics.record_rtc_remote_control_drop(drop_kind);
146 RemoteControlSendOutcome::Closed
147 }
148 }
149 }
150}
151
152pub type RtcWorkerResponse<T> = oneshot::Sender<TransportResult<T>>;
157
158pub struct ParsedSessionAnswer {
160 pub(super) answer: SdpAnswer,
161 pub(super) rids: ParsedAnswerRids,
162 pub(super) repair: RepairSummary,
163}
164
165impl ParsedSessionAnswer {
166 pub(in crate::engine::media_transport) fn parse(answer_sdp: &str) -> TransportResult<Self> {
173 let repair = validate_answer_sdp(answer_sdp)?;
174 let answer = SdpAnswer::from_sdp_string(answer_sdp)
175 .map_err(|_error| TransportAdapterError::InvalidInput)?;
176 let rids = ParsedAnswerRids::parse(answer_sdp, &answer);
177 Ok(Self {
178 answer,
179 rids,
180 repair,
181 })
182 }
183}
184
185pub struct RtcSessionOffer {
186 offer: SdpOffer,
187 upload_slots: Vec<SessionUploadSlot>,
188}
189
190impl RtcSessionOffer {
191 pub(super) fn new(offer: SdpOffer, upload_slots: Vec<SessionUploadSlot>) -> Self {
192 Self {
193 offer,
194 upload_slots,
195 }
196 }
197
198 pub(in crate::engine::media_transport) fn into_session_offer(self) -> SessionOffer {
199 let mut sdp = self.offer.session.to_string();
203 sdp.push_str(sdp::EOC_LINE);
204 for media in &self.offer.media_lines {
205 let _ = write!(sdp, "{media}");
206 }
207 SessionOffer::new(sdp).with_upload_slots(self.upload_slots)
208 }
209}
210
211#[derive(Debug)]
212pub enum WorkerMediaControlBatch {
213 ReceiverBwe(Vec<(usize, ReceiverBweTargetUpdate)>),
214 ProducerActivity(Vec<(usize, ProducerRouteControl)>),
215 ConsumerGates {
216 source: TransportSourceKey,
217 updates: Vec<(usize, TransportConsumerRoute, PacketLayerGate)>,
218 },
219 ConsumerFollowUp(Vec<(usize, ConsumerRouteControl)>),
220}
221
222#[derive(Debug)]
223pub enum WorkerMediaControlBatchOutcome {
224 Applied(Vec<TransportResult<()>>),
225 Consumers(Vec<ConsumerRouteControlOutcome>),
226}
227
228pub enum RouteControlRequest {
229 AddRelayTarget {
230 source: TransportSourceKey,
231 target_id: RelayTargetId,
232 target: RelayPacketMailbox,
233 },
234 RemoveRelayTarget {
235 source: TransportSourceKey,
236 target_id: RelayTargetId,
237 },
238 SetRelayTargetActive {
239 source: TransportSourceKey,
240 target_id: RelayTargetId,
241 active: bool,
242 },
243 SetRemoteSourceActivity {
244 source: TransportSourceKey,
245 update: SourceActivityUpdate,
246 },
247 RequestRemoteKeyframe {
248 source: TransportSourceKey,
249 target_id: RelayTargetId,
250 rid: Option<Rid>,
251 kind: KeyframeRequestKind,
252 },
253 SetRemoteSourcePacketGate {
254 source: TransportSourceKey,
255 target_id: RelayTargetId,
256 packet_gate: PacketLayerGate,
257 },
258}
259
260pub enum RtcWorkerCommand {
267 CreateInitialSessionOffer {
273 room_id: Arc<str>,
274 session_key: TransportSessionKey,
275 response: RtcWorkerResponse<RtcSessionOffer>,
276 },
277 CreateSessionRenegotiationOffer {
284 session_key: TransportSessionKey,
285 response: RtcWorkerResponse<RtcSessionOffer>,
286 },
287 ActiveSpeakerSourceSnapshot {
292 response: RtcWorkerResponse<Vec<ActiveSpeakerSource>>,
293 },
294 SourceDiagnosticsSnapshot {
296 transport_media_ids: Vec<TransportMediaId>,
297 response: RtcWorkerResponse<TransportSourceDiagnosticsSnapshot>,
298 },
299 ApplySessionAnswer {
305 session_key: TransportSessionKey,
306 answer: ParsedSessionAnswer,
307 response: RtcWorkerResponse<AppliedSessionAnswer>,
308 },
309 CloseSession {
314 session_key: TransportSessionKey,
315 response: RtcWorkerResponse<()>,
316 },
317 RemoveMedia {
324 session_key: TransportSessionKey,
325 transport_media_id: TransportMediaId,
326 response: RtcWorkerResponse<()>,
327 },
328 #[cfg(test)]
334 ResolveNegotiatedProducerParameters {
335 session_key: TransportSessionKey,
336 transport_media_id: TransportMediaId,
337 response: RtcWorkerResponse<RouterRtpParameters>,
338 },
339 #[cfg(test)]
344 ResolveMediaMid {
345 transport_media_id: TransportMediaId,
346 response: RtcWorkerResponse<Option<String>>,
347 },
348 AddRecvMedia {
356 session_key: TransportSessionKey,
357 media_kind: MediaKind,
358 rtp_parameters: RouterRtpParameters,
359 response: RtcWorkerResponse<TransportMediaId>,
360 },
361 AddSendMedia {
369 consumer_key: TransportSessionKey,
370 media_kind: MediaKind,
371 source: TransportSourceKey,
372 sync: SourceSyncPolicy,
373 remote_source_control: Option<RemoteSourceControl>,
374 consumer_rtp_parameters: RouterRtpParameters,
375 active: bool,
376 response: RtcWorkerResponse<(TransportMediaId, String)>,
377 },
378 ApplyMediaControlBatch {
379 batch: WorkerMediaControlBatch,
380 response: RtcWorkerResponse<WorkerMediaControlBatchOutcome>,
381 },
382 RouteControl {
383 request: RouteControlRequest,
384 response: Option<RtcWorkerResponse<()>>,
385 },
386 RegisterTcpOutbox {
387 peer_addr: SocketAddr,
388 ufrag: String,
389 outbox_tx: mpsc::Sender<TcpFrame>,
390 response: RtcWorkerResponse<()>,
391 },
392}
393
394impl RtcWorkerCommand {
395 pub(super) fn observation_kind(&self) -> Option<RtcWorkerObservationKind> {
397 match self {
398 Self::ActiveSpeakerSourceSnapshot { .. } => {
399 Some(RtcWorkerObservationKind::ActiveSpeakerSources)
400 }
401 Self::SourceDiagnosticsSnapshot { .. } => {
402 Some(RtcWorkerObservationKind::SourceDiagnostics)
403 }
404 #[cfg(test)]
405 Self::ResolveMediaMid { .. } => Some(RtcWorkerObservationKind::ResolveMediaMid),
406 #[cfg(test)]
407 Self::ResolveNegotiatedProducerParameters { .. } => {
408 Some(RtcWorkerObservationKind::NegotiatedProducerParameters)
409 }
410 Self::CreateInitialSessionOffer { .. }
411 | Self::CreateSessionRenegotiationOffer { .. }
412 | Self::ApplySessionAnswer { .. }
413 | Self::CloseSession { .. }
414 | Self::RemoveMedia { .. }
415 | Self::AddRecvMedia { .. }
416 | Self::AddSendMedia { .. }
417 | Self::ApplyMediaControlBatch { .. }
418 | Self::RouteControl { .. }
419 | Self::RegisterTcpOutbox { .. } => None,
420 }
421 }
422
423 pub(super) fn may_change_demux_topology(&self) -> bool {
424 match self {
425 Self::CreateInitialSessionOffer { .. }
426 | Self::ApplySessionAnswer { .. }
427 | Self::CloseSession { .. } => true,
428 Self::CreateSessionRenegotiationOffer { .. }
429 | Self::ActiveSpeakerSourceSnapshot { .. }
430 | Self::SourceDiagnosticsSnapshot { .. }
431 | Self::RemoveMedia { .. }
432 | Self::AddRecvMedia { .. }
433 | Self::AddSendMedia { .. }
434 | Self::ApplyMediaControlBatch { .. }
435 | Self::RouteControl { .. }
436 | Self::RegisterTcpOutbox { .. } => false,
437 #[cfg(test)]
438 Self::ResolveMediaMid { .. } => false,
439 #[cfg(test)]
440 Self::ResolveNegotiatedProducerParameters { .. } => false,
441 }
442 }
443}