Skip to main content

o_sfu/application/
stream_catalog.rs

1use std::collections::BTreeMap;
2
3use o_sfu_protocol::wire::{DownloadStates, StreamType, UserInfo};
4use o_sfu_router::MediaKind;
5
6use crate::core::prelude::{
7    ActiveSpeakerGroup, ActiveSpeakerPolicy, ActiveSpeakerSourceRole, SourceAdaptationPolicy,
8    SourceDeactivateIntent, SourceLayoutPolicy, SourcePolicy, SourcePublishIntent,
9    SourceRoomPolicySelector, SourceSubscriptionIntent, SourceSyncPolicy, UserStreamId,
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub(crate) struct DiscussStream {
14    stream_type: StreamType,
15    label: &'static str,
16    media_kind: MediaKind,
17    policy: SourcePolicy,
18}
19
20impl DiscussStream {
21    /// Returns descriptors in audio, camera and screen order.
22    pub(crate) fn all() -> [Self; 3] {
23        [StreamType::Audio, StreamType::Camera, StreamType::Screen].map(Self::for_type)
24    }
25
26    pub(crate) const fn for_type(stream_type: StreamType) -> Self {
27        match stream_type {
28            StreamType::Audio => Self {
29                stream_type,
30                label: "audio",
31                media_kind: MediaKind::Audio,
32                policy: SourcePolicy::new(
33                    None,
34                    SourceAdaptationPolicy::None,
35                    Some(ActiveSpeakerPolicy::new(
36                        ActiveSpeakerGroup::MAIN,
37                        ActiveSpeakerSourceRole::Detector,
38                    )),
39                )
40                .with_sync(SourceSyncPolicy::Publisher),
41            },
42            StreamType::Camera => Self {
43                stream_type,
44                label: "camera",
45                media_kind: MediaKind::Video,
46                policy: SourcePolicy::new(
47                    Some(SourceLayoutPolicy::new(
48                        SourceRoomPolicySelector::VisibleThumbnail,
49                        Some(SourceRoomPolicySelector::ActiveSpeaker),
50                    )),
51                    SourceAdaptationPolicy::ScalableVideo,
52                    Some(ActiveSpeakerPolicy::new(
53                        ActiveSpeakerGroup::MAIN,
54                        ActiveSpeakerSourceRole::Promotable,
55                    )),
56                )
57                .with_sync(SourceSyncPolicy::Publisher),
58            },
59            StreamType::Screen => Self {
60                stream_type,
61                label: "screen",
62                media_kind: MediaKind::Video,
63                policy: SourcePolicy::new(
64                    Some(SourceLayoutPolicy::new(
65                        SourceRoomPolicySelector::ReadableDetail,
66                        None,
67                    )),
68                    SourceAdaptationPolicy::ReadableDetail,
69                    None,
70                ),
71            },
72        }
73    }
74
75    pub(crate) fn for_stream_id(stream_id: &UserStreamId) -> Option<Self> {
76        Self::all()
77            .into_iter()
78            .find(|stream| stream.label == stream_id.as_str())
79    }
80
81    pub(crate) const fn label(self) -> &'static str {
82        self.label
83    }
84
85    pub(crate) fn stream_id(self) -> UserStreamId {
86        UserStreamId::new(self.label)
87    }
88
89    pub(crate) fn publish_intent(self) -> SourcePublishIntent {
90        SourcePublishIntent::new(self.stream_id(), self.media_kind, self.policy)
91            .with_presence(self.publication_presence(true))
92    }
93
94    pub(crate) fn deactivate_intent(self) -> SourceDeactivateIntent {
95        SourceDeactivateIntent::new(self.stream_id())
96            .with_presence(self.publication_presence(false))
97    }
98
99    pub(crate) fn subscription_intent_if_requested(
100        self,
101        states: &DownloadStates,
102    ) -> Option<(UserStreamId, SourceSubscriptionIntent)> {
103        let (active, layout) = match self.stream_type {
104            StreamType::Audio => (states.audio, None),
105            StreamType::Camera => (states.camera, states.camera_layout),
106            StreamType::Screen => (states.screen, states.screen_layout),
107        };
108        let intent = SourceSubscriptionIntent::new(active, layout);
109        (!intent.is_empty()).then(|| (self.stream_id(), intent))
110    }
111
112    fn publication_presence(self, active: bool) -> Option<UserInfo> {
113        match self.stream_type {
114            StreamType::Audio => None,
115            StreamType::Camera => Some(UserInfo {
116                is_camera_on: Some(active),
117                ..UserInfo::default()
118            }),
119            StreamType::Screen => Some(UserInfo {
120                is_screen_sharing_on: Some(active),
121                ..UserInfo::default()
122            }),
123        }
124    }
125}
126
127pub(crate) fn source_publish_intent_for_stream_type(
128    stream_type: StreamType,
129) -> SourcePublishIntent {
130    DiscussStream::for_type(stream_type).publish_intent()
131}
132
133pub(crate) fn stream_id_for_stream_type(stream_type: StreamType) -> UserStreamId {
134    DiscussStream::for_type(stream_type).stream_id()
135}
136
137pub(crate) fn stream_type_for_stream_id(stream_id: &UserStreamId) -> Option<StreamType> {
138    DiscussStream::for_stream_id(stream_id).map(|stream| stream.stream_type)
139}
140
141pub(crate) fn counter_for_stream_type(
142    by_stream: &BTreeMap<UserStreamId, u64>,
143    stream_type: StreamType,
144) -> u64 {
145    by_stream
146        .get(&stream_id_for_stream_type(stream_type))
147        .copied()
148        .unwrap_or(0)
149}
150
151#[cfg(test)]
152#[path = "TESTS/stream_catalog.rs"]
153mod tests;