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, 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            },
41            StreamType::Camera => Self {
42                stream_type,
43                label: "camera",
44                media_kind: MediaKind::Video,
45                policy: SourcePolicy::new(
46                    Some(SourceLayoutPolicy::new(
47                        SourceRoomPolicySelector::VisibleThumbnail,
48                        Some(SourceRoomPolicySelector::ActiveSpeaker),
49                    )),
50                    SourceAdaptationPolicy::ScalableVideo,
51                    Some(ActiveSpeakerPolicy::new(
52                        ActiveSpeakerGroup::MAIN,
53                        ActiveSpeakerSourceRole::Promotable,
54                    )),
55                ),
56            },
57            StreamType::Screen => Self {
58                stream_type,
59                label: "screen",
60                media_kind: MediaKind::Video,
61                policy: SourcePolicy::new(
62                    Some(SourceLayoutPolicy::new(
63                        SourceRoomPolicySelector::ReadableDetail,
64                        None,
65                    )),
66                    SourceAdaptationPolicy::ReadableDetail,
67                    None,
68                ),
69            },
70        }
71    }
72
73    pub(crate) fn for_stream_id(stream_id: &UserStreamId) -> Option<Self> {
74        Self::all()
75            .into_iter()
76            .find(|stream| stream.label == stream_id.as_str())
77    }
78
79    pub(crate) const fn label(self) -> &'static str {
80        self.label
81    }
82
83    pub(crate) fn stream_id(self) -> UserStreamId {
84        UserStreamId::new(self.label)
85    }
86
87    pub(crate) fn publish_intent(self) -> SourcePublishIntent {
88        SourcePublishIntent::new(self.stream_id(), self.media_kind, self.policy)
89            .with_presence(self.publication_presence(true))
90    }
91
92    pub(crate) fn deactivate_intent(self) -> SourceDeactivateIntent {
93        SourceDeactivateIntent::new(self.stream_id())
94            .with_presence(self.publication_presence(false))
95    }
96
97    pub(crate) fn subscription_intent_if_requested(
98        self,
99        states: &DownloadStates,
100    ) -> Option<(UserStreamId, SourceSubscriptionIntent)> {
101        let (active, layout) = match self.stream_type {
102            StreamType::Audio => (states.audio, None),
103            StreamType::Camera => (states.camera, states.camera_layout),
104            StreamType::Screen => (states.screen, states.screen_layout),
105        };
106        let intent = SourceSubscriptionIntent::new(active, layout);
107        (!intent.is_empty()).then(|| (self.stream_id(), intent))
108    }
109
110    fn publication_presence(self, active: bool) -> Option<UserInfo> {
111        match self.stream_type {
112            StreamType::Audio => None,
113            StreamType::Camera => Some(UserInfo {
114                is_camera_on: Some(active),
115                ..UserInfo::default()
116            }),
117            StreamType::Screen => Some(UserInfo {
118                is_screen_sharing_on: Some(active),
119                ..UserInfo::default()
120            }),
121        }
122    }
123}
124
125pub(crate) fn source_publish_intent_for_stream_type(
126    stream_type: StreamType,
127) -> SourcePublishIntent {
128    DiscussStream::for_type(stream_type).publish_intent()
129}
130
131pub(crate) fn stream_id_for_stream_type(stream_type: StreamType) -> UserStreamId {
132    DiscussStream::for_type(stream_type).stream_id()
133}
134
135pub(crate) fn stream_type_for_stream_id(stream_id: &UserStreamId) -> Option<StreamType> {
136    DiscussStream::for_stream_id(stream_id).map(|stream| stream.stream_type)
137}
138
139pub(crate) fn counter_for_stream_type(
140    by_stream: &BTreeMap<UserStreamId, u64>,
141    stream_type: StreamType,
142) -> u64 {
143    by_stream
144        .get(&stream_id_for_stream_type(stream_type))
145        .copied()
146        .unwrap_or(0)
147}
148
149#[cfg(test)]
150#[path = "TESTS/stream_catalog.rs"]
151mod tests;