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