medea_control_api_proto/control/endpoint/
web_rtc_play.rs

1//! [`WebRtcPlay`] [`Endpoint`] definitions.
2//!
3//! [`Endpoint`]: crate::Endpoint
4
5use std::str::FromStr;
6
7use derive_more::with_trait::{AsRef, Display, Error, From, Into};
8use ref_cast::RefCast;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
11use url::Url;
12
13use crate::control::{
14    endpoint::{self, web_rtc_publish},
15    member, room,
16};
17
18/// Media [`Element`] playing media data for a client via [WebRTC].
19///
20/// [`Element`]: crate::Element
21/// [WebRTC]: https://w3.org/TR/webrtc
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct WebRtcPlay {
24    /// ID of this [`WebRtcPlay`] media [`Element`].
25    ///
26    /// [`Element`]: crate::Element
27    pub id: Id,
28
29    /// [`Spec`] of this [`WebRtcPlay`] media [`Element`].
30    ///
31    /// [`Element`]: crate::Element
32    pub spec: Spec,
33}
34
35/// Spec of a [`WebRtcPlay`] media [`Element`].
36///
37/// [`Element`]: crate::Element
38#[derive(Clone, Debug, Eq, PartialEq)]
39#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
40pub struct Spec {
41    /// Source to play media data from.
42    pub src: LocalSrcUri,
43
44    /// Indicator whether to relay all media data through a [TURN] server
45    /// forcibly.
46    ///
47    /// [TURN]: https://webrtc.org/getting-started/turn-server
48    #[cfg_attr(feature = "serde", serde(default))]
49    pub force_relay: bool,
50}
51
52/// ID of a [`WebRtcPlay`] media [`Element`].
53///
54/// [`Element`]: crate::Element
55#[derive(
56    AsRef,
57    Clone,
58    Debug,
59    Display,
60    Eq,
61    From,
62    Hash,
63    Into,
64    Ord,
65    PartialEq,
66    PartialOrd,
67    RefCast,
68)]
69#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
70#[cfg_attr(feature = "serde", serde(transparent))]
71#[from(Box<str>, &str, String)]
72#[into(Box<str>, String)]
73#[repr(transparent)]
74pub struct Id(Box<str>);
75
76impl AsRef<endpoint::Id> for Id {
77    fn as_ref(&self) -> &endpoint::Id {
78        endpoint::Id::ref_cast(&self.0)
79    }
80}
81
82/// [URI] describing a source of media data for a [`WebRtcPlay`] media
83/// [`Element`] located locally on the same media server.
84///
85/// [`Element`]: crate::Element
86/// [URI]: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
87#[derive(Clone, Debug, Display, Eq, PartialEq)]
88#[display("local://{room_id}/{member_id}/{endpoint_id}")]
89pub struct LocalSrcUri {
90    /// ID of the [`Room`].
91    ///
92    /// [`Room`]: crate::Room
93    pub room_id: room::Id,
94
95    /// ID of the [`Member`].
96    ///
97    /// [`Member`]: crate::Member
98    pub member_id: member::Id,
99
100    /// ID of the [`WebRtcPublish`] [`Element`] the media data is produced by.
101    ///
102    /// [`Element`]: crate::Element
103    /// [`WebRtcPublish`]: web_rtc_publish::WebRtcPublish
104    pub endpoint_id: web_rtc_publish::Id,
105}
106
107impl FromStr for LocalSrcUri {
108    type Err = LocalSrcUriParseError;
109
110    fn from_str(val: &str) -> Result<Self, Self::Err> {
111        if val.is_empty() {
112            return Err(LocalSrcUriParseError::Empty);
113        }
114
115        let url = Url::parse(val)
116            .map_err(|e| LocalSrcUriParseError::UrlParseErr(val.into(), e))?;
117        if url.scheme() != "local" {
118            return Err(LocalSrcUriParseError::NotLocal(val.into()));
119        }
120
121        let room_id = url
122            .host_str()
123            .filter(|h| !h.is_empty())
124            .ok_or_else(|| LocalSrcUriParseError::MissingPaths(val.into()))?
125            .into();
126
127        let mut path = url
128            .path_segments()
129            .ok_or_else(|| LocalSrcUriParseError::MissingPaths(val.into()))?;
130
131        let member_id = path
132            .next()
133            .filter(|id| !id.is_empty())
134            .ok_or_else(|| LocalSrcUriParseError::MissingPaths(val.into()))?
135            .into();
136
137        let endpoint_id = path
138            .next()
139            .filter(|id| !id.is_empty())
140            .ok_or_else(|| LocalSrcUriParseError::MissingPaths(val.into()))?
141            .into();
142
143        if path.next().is_some() {
144            return Err(LocalSrcUriParseError::TooManyPaths(val.into()));
145        }
146
147        Ok(Self { room_id, member_id, endpoint_id })
148    }
149}
150
151#[cfg(feature = "serde")]
152impl<'de> Deserialize<'de> for LocalSrcUri {
153    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154    where
155        D: Deserializer<'de>,
156    {
157        String::deserialize(deserializer)?
158            .parse::<Self>()
159            .map_err(D::Error::custom)
160    }
161}
162
163#[cfg(feature = "serde")]
164impl Serialize for LocalSrcUri {
165    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
166    where
167        S: Serializer,
168    {
169        serializer.serialize_str(&self.to_string())
170    }
171}
172
173/// Possible errors of parsing a [`LocalSrcUri`].
174#[derive(Debug, Display, Error)]
175pub enum LocalSrcUriParseError {
176    /// Protocol of the provided URI is not `local://`.
177    #[display("Provided URI protocol is not `local://`: {_0}")]
178    NotLocal(#[error(not(source))] Box<str>),
179
180    /// Too many paths in the provided URI.
181    ///
182    /// `local://room_id/member_id/endpoint_id/redundant_path` for example.
183    #[display("Too many paths in URI: {_0}")]
184    TooManyPaths(#[error(not(source))] Box<str>),
185
186    /// Some paths are missing in the provided URI.
187    ///
188    /// `local://room_id//qwerty` for example.
189    #[display("Missing paths in URI: {_0}")]
190    MissingPaths(#[error(not(source))] Box<str>),
191
192    /// Error of parsing the provided URI.
193    #[display("Cannot parse provided URI `{_0}`: {_1}")]
194    UrlParseErr(Box<str>, #[error(source)] url::ParseError),
195
196    /// Provided URI is empty.
197    #[display("Provided URI cannot be empty")]
198    Empty,
199}