o_sfu_core/engine/room/transition/
publication.rs1use o_sfu_router::rtp::MediaStream as RouterRtpParameters;
24use tracing::warn;
25
26use super::super::{
27 Room, RoomUserOperation,
28 effects::batch::{RoomEffectContext, RoomEffects},
29 media_graph::{ProducerActivityCommit, PublishIntentPlan, ValidatedPublish},
30};
31#[cfg(any(test, feature = "testing-transport"))]
32use crate::engine::{ConnectionId, UserId};
33use crate::engine::{
34 media_transport::{AppliedSessionAnswer, TransportAdapterError},
35 source_model::{SourceDeactivateIntent, SourcePublishIntent, UserStreamId},
36};
37
38mod staging;
39#[cfg(any(test, feature = "testing-transport"))]
40#[path = "TESTS/publication_support.rs"]
41mod test_support;
42
43pub use staging::{StagedPublish, StagedPublishes};
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum PublishIntentOutcome {
47 Noop,
48 Queue,
49 Activated,
50 Staged,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum DeactivateIntentOutcome {
55 Noop,
56 RolledBack,
57 Deactivated,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum PublishStageOutcome {
62 Staged,
63 Duplicate,
64 DuplicateAfterReservation,
65 #[cfg(test)]
66 Rejected,
67}
68
69impl RoomUserOperation<'_> {
70 #[cfg(test)]
71 pub async fn stage_negotiated_publish(
72 self,
73 intent: &SourcePublishIntent,
74 ) -> Result<PublishStageOutcome, TransportAdapterError> {
75 let Some(validated_descriptor) = ({
76 let state = self.room.state.read().await;
77 state.validate_publish(self.user_id, self.connection_id, intent)
78 }) else {
79 return Ok(PublishStageOutcome::Rejected);
80 };
81 self.stage_validated_publish(validated_descriptor).await
82 }
83
84 async fn stage_validated_publish(
85 self,
86 validated_descriptor: ValidatedPublish,
87 ) -> Result<PublishStageOutcome, TransportAdapterError> {
88 let is_duplicate = {
89 let state = self.room.state.read().await;
90 state.staged_publishes.contains(
91 self.user_id,
92 self.connection_id,
93 &validated_descriptor.stream_id,
94 )
95 };
96 if is_duplicate {
97 return Ok(PublishStageOutcome::Duplicate);
98 }
99 let rtp_parameters = RouterRtpParameters::default();
100 let media = match self
101 .media_transport
102 .publish_media(
103 &validated_descriptor.session_key,
104 validated_descriptor.media_kind,
105 &rtp_parameters,
106 )
107 .await
108 {
109 Ok(media) => media,
110 Err(error) => {
111 warn!(
112 user_id = ?self.user_id,
113 connection_id = ?self.connection_id,
114 stream_id = %validated_descriptor.stream_id,
115 media_kind = ?validated_descriptor.media_kind,
116 "failed to stage negotiated publish stream"
117 );
118 return Err(error);
119 }
120 };
121 let reserved_publish = StagedPublish::new(validated_descriptor, media);
122 let duplicate = {
123 let mut state = self.room.state.write().await;
124 if state
127 .validate_publish_commit(&reserved_publish.descriptor, reserved_publish.media)
128 .is_some()
129 {
130 state.staged_publishes.stage(reserved_publish)
131 } else {
132 Some(reserved_publish)
133 }
134 };
135 if let Some(duplicate) = duplicate {
136 duplicate.release_reserved_media(self).await;
137 return Ok(PublishStageOutcome::DuplicateAfterReservation);
138 }
139 Ok(PublishStageOutcome::Staged)
140 }
141
142 pub(crate) async fn start_publish(
143 self,
144 intent: &SourcePublishIntent,
145 can_stage: bool,
146 ) -> Result<PublishIntentOutcome, TransportAdapterError> {
147 let has_staged_publish = {
148 let state = self.room.state.read().await;
149 state
150 .staged_publishes
151 .contains(self.user_id, self.connection_id, intent.stream_id())
152 };
153 if has_staged_publish {
154 return Ok(PublishIntentOutcome::Noop);
155 }
156 let source_policy_guard = self.room.source_policy_turn.lock().await;
157 let plan = {
158 let mut state = self.room.state.write().await;
159 state.apply_publish_intent(self.user_id, self.connection_id, intent, can_stage)
160 };
161 match plan {
162 PublishIntentPlan::Activate(commit) => {
163 self.execute_publication_activity(commit).await;
166 Ok(PublishIntentOutcome::Activated)
167 }
168 PublishIntentPlan::Noop => {
169 drop(source_policy_guard);
170 Ok(PublishIntentOutcome::Noop)
171 }
172 PublishIntentPlan::Queue => {
173 drop(source_policy_guard);
174 Ok(PublishIntentOutcome::Queue)
175 }
176 PublishIntentPlan::Stage(validated) => {
177 drop(source_policy_guard);
178 if self.stage_validated_publish(validated).await? == PublishStageOutcome::Staged {
179 Ok(PublishIntentOutcome::Staged)
180 } else {
181 Ok(PublishIntentOutcome::Noop)
182 }
183 }
184 }
185 }
186
187 pub async fn rollback_staged_publish(self, stream_id: &UserStreamId) -> bool {
188 let Some(staged) = ({
189 let mut state = self.room.state.write().await;
190 state
191 .staged_publishes
192 .take(self.user_id, self.connection_id, stream_id)
193 }) else {
194 return false;
195 };
196 staged.release_reserved_media(self).await;
197 true
198 }
199
200 pub(crate) async fn deactivate_publication(
201 self,
202 intent: &SourceDeactivateIntent,
203 ) -> DeactivateIntentOutcome {
204 if self.rollback_staged_publish(intent.stream_id()).await {
205 return DeactivateIntentOutcome::RolledBack;
206 }
207 let _source_policy_guard = self.room.source_policy_turn.lock().await;
210 let commit = {
211 let mut state = self.room.state.write().await;
212 state.apply_publication_activity(
213 self.user_id,
214 self.connection_id,
215 intent.stream_id(),
216 false,
217 intent.presence(),
218 )
219 };
220 let Ok(commit) = commit else {
221 return DeactivateIntentOutcome::Noop;
222 };
223 self.execute_publication_activity(commit).await;
224 DeactivateIntentOutcome::Deactivated
225 }
226
227 pub(crate) async fn commit_staged_publishes(self, applied_answer: &AppliedSessionAnswer) {
229 let _source_policy_guard = self.room.source_policy_turn.lock().await;
233 let staged = {
234 let mut state = self.room.state.write().await;
235 state
236 .staged_publishes
237 .take_for_connection(self.user_id, self.connection_id)
238 };
239 for publish in staged {
240 publish.commit_answer_guarded(self, applied_answer).await;
241 }
242 }
243
244 async fn execute_publication_activity(self, commit: ProducerActivityCommit) {
245 RoomEffects::from_publication_activity(commit)
246 .execute_with_source_policy_guard(
247 self.room,
248 RoomEffectContext::runtime(self.media_transport),
249 )
250 .await;
251 }
252}
253
254impl Room {
255 #[cfg(any(test, feature = "testing-transport"))]
256 #[must_use]
257 pub async fn has_staged_publish(
258 &self,
259 user_id: &UserId,
260 connection_id: ConnectionId,
261 stream_id: &UserStreamId,
262 ) -> bool {
263 self.state
264 .read()
265 .await
266 .staged_publishes
267 .contains(user_id, connection_id, stream_id)
268 }
269}
270
271#[cfg(test)]
272#[path = "TESTS/publication.rs"]
273mod tests;