Skip to main content

onvif_server/service/
media.rs

1use async_trait::async_trait;
2use bytes::Bytes;
3use quick_xml::events::Event;
4use quick_xml::NsReader;
5use soap_server::{escape_attr, escape_text, SoapFault, SoapHandler};
6use std::sync::Arc;
7
8use crate::constants::{
9    PTZ_CONFIG_TOKEN, PTZ_NODE_TOKEN, TRANSLATION_SPACE_FOV, VIDEO_ENCODER_TOKEN,
10    VIDEO_SOURCE_TOKEN,
11};
12use crate::error::OnvifError;
13use crate::service::xml_util::extract_text_ns;
14use crate::traits::MediaService;
15
16/// ONVIF namespaces accepted for media/device request elements.
17const ONVIF_MEDIA_NS: &[u8] = b"http://www.onvif.org/ver10/media/wsdl";
18const ONVIF_SCHEMA_NS: &[u8] = b"http://www.onvif.org/ver10/schema";
19
20#[allow(dead_code)]
21pub struct MediaServiceHandler {
22    pub(crate) svc: Arc<dyn MediaService>,
23    pub(crate) xaddr: String,
24}
25
26impl MediaServiceHandler {
27    pub fn new(svc: Arc<dyn MediaService>, xaddr: impl Into<String>) -> Self {
28        Self {
29            svc,
30            xaddr: xaddr.into(),
31        }
32    }
33}
34
35#[async_trait]
36impl SoapHandler for MediaServiceHandler {
37    async fn handle(&self, body: Bytes) -> Result<Bytes, SoapFault> {
38        let op = extract_local_name(&body)?;
39        match op.as_str() {
40            "GetProfiles" => self.handle_get_profiles().await,
41            "GetStreamUri" => self.handle_get_stream_uri(&body).await,
42            "GetSnapshotUri" => self.handle_get_snapshot_uri(&body).await,
43            "GetVideoSources" => self.handle_get_video_sources().await,
44            "GetVideoSourceConfigurations" => self.handle_get_video_source_configurations().await,
45            "GetVideoEncoderConfigurations" => self.handle_get_video_encoder_configurations().await,
46            _ => Err(OnvifError::ActionNotSupported.into_soap_fault()),
47        }
48    }
49}
50
51fn extract_local_name(body: &Bytes) -> Result<String, SoapFault> {
52    let mut reader = NsReader::from_reader(body.as_ref());
53    reader.config_mut().trim_text(true);
54    loop {
55        match reader
56            .read_resolved_event()
57            .map_err(|e| SoapFault::sender(format!("{e}")))?
58        {
59            (_, Event::Start(e)) | (_, Event::Empty(e)) => {
60                let local = std::str::from_utf8(e.local_name().as_ref())
61                    .map_err(|e| SoapFault::sender(format!("{e}")))?
62                    .to_string();
63                return Ok(local);
64            }
65            (_, Event::Eof) => return Err(SoapFault::sender("Empty body".to_string())),
66            _ => {}
67        }
68    }
69}
70
71fn extract_text_element(body: &Bytes, element_name: &str) -> Result<String, SoapFault> {
72    extract_text_ns(body, element_name, &[ONVIF_MEDIA_NS, ONVIF_SCHEMA_NS])
73}
74
75impl MediaServiceHandler {
76    async fn handle_get_profiles(&self) -> Result<Bytes, SoapFault> {
77        let profiles = self.svc.profiles();
78        let mut profile_blocks = String::new();
79        for p in &profiles {
80            profile_blocks.push_str(&format!(
81                r#"  <trt:Profiles token="{profile_token}" fixed="true">
82    <tt:Name>{name}</tt:Name>
83    <tt:VideoSourceConfiguration token="{vs_cfg_token}">
84      <tt:Name>VideoSourceConfig</tt:Name>
85      <tt:UseCount>1</tt:UseCount>
86      <tt:SourceToken>{vs_token}</tt:SourceToken>
87      <tt:Bounds x="0" y="0" width="{width}" height="{height}"/>
88    </tt:VideoSourceConfiguration>
89    <tt:VideoEncoderConfiguration token="{ve_cfg_token}">
90      <tt:Name>VideoEncoderConfig</tt:Name>
91      <tt:UseCount>1</tt:UseCount>
92      <tt:Encoding>{encoding}</tt:Encoding>
93      <tt:Resolution><tt:Width>{width}</tt:Width><tt:Height>{height}</tt:Height></tt:Resolution>
94      <tt:Quality>5</tt:Quality>
95      <tt:RateControl>
96        <tt:FrameRateLimit>{framerate}</tt:FrameRateLimit>
97        <tt:EncodingInterval>1</tt:EncodingInterval>
98        <tt:BitrateLimit>{bitrate}</tt:BitrateLimit>
99      </tt:RateControl>
100      <tt:Multicast>
101        <tt:Address><tt:Type>IPv4</tt:Type><tt:IPv4Address>0.0.0.0</tt:IPv4Address></tt:Address>
102        <tt:Port>0</tt:Port>
103        <tt:TTL>0</tt:TTL>
104        <tt:AutoStart>false</tt:AutoStart>
105      </tt:Multicast>
106      <tt:SessionTimeout>PT10S</tt:SessionTimeout>
107    </tt:VideoEncoderConfiguration>
108    <tt:PTZConfiguration token="{ptz_cfg_token}">
109      <tt:Name>PTZConfig</tt:Name>
110      <tt:UseCount>1</tt:UseCount>
111      <tt:NodeToken>{ptz_node_token}</tt:NodeToken>
112      <tt:DefaultContinuousPanTiltVelocitySpace>{translation_space_fov}</tt:DefaultContinuousPanTiltVelocitySpace>
113    </tt:PTZConfiguration>
114  </trt:Profiles>
115"#,
116                profile_token = escape_attr(&p.token),
117                name = escape_text(&p.name),
118                vs_cfg_token = escape_attr(&p.video_source_token),
119                vs_token = escape_text(&p.video_source_token),
120                ve_cfg_token = VIDEO_ENCODER_TOKEN,
121                width = p.width,
122                height = p.height,
123                encoding = escape_text(&p.encoding),
124                framerate = p.framerate,
125                bitrate = p.bitrate,
126                ptz_cfg_token = PTZ_CONFIG_TOKEN,
127                ptz_node_token = PTZ_NODE_TOKEN,
128                translation_space_fov = TRANSLATION_SPACE_FOV,
129            ));
130        }
131        let xml = format!(
132            r#"<trt:GetProfilesResponse xmlns:trt="http://www.onvif.org/ver10/media/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema">
133{profile_blocks}</trt:GetProfilesResponse>"#,
134        );
135        Ok(Bytes::from(xml))
136    }
137
138    async fn handle_get_stream_uri(&self, body: &Bytes) -> Result<Bytes, SoapFault> {
139        let profile_token = extract_text_element(body, "ProfileToken")?;
140        let uri = self
141            .svc
142            .get_stream_uri(&profile_token)
143            .await
144            .map_err(|e| e.into_soap_fault())?;
145        let xml = format!(
146            r#"<trt:GetStreamUriResponse xmlns:trt="http://www.onvif.org/ver10/media/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema">
147  <trt:MediaUri>
148    <tt:Uri>{uri}</tt:Uri>
149    <tt:InvalidAfterConnect>false</tt:InvalidAfterConnect>
150    <tt:InvalidAfterReboot>false</tt:InvalidAfterReboot>
151    <tt:Timeout>PT0S</tt:Timeout>
152  </trt:MediaUri>
153</trt:GetStreamUriResponse>"#,
154            uri = escape_text(&uri)
155        );
156        Ok(Bytes::from(xml))
157    }
158
159    async fn handle_get_snapshot_uri(&self, body: &Bytes) -> Result<Bytes, SoapFault> {
160        let profile_token = extract_text_element(body, "ProfileToken")?;
161        let uri = self
162            .svc
163            .get_snapshot_uri(&profile_token)
164            .await
165            .map_err(|e| e.into_soap_fault())?;
166        let xml = format!(
167            r#"<trt:GetSnapshotUriResponse xmlns:trt="http://www.onvif.org/ver10/media/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema">
168  <trt:MediaUri>
169    <tt:Uri>{uri}</tt:Uri>
170    <tt:InvalidAfterConnect>false</tt:InvalidAfterConnect>
171    <tt:InvalidAfterReboot>false</tt:InvalidAfterReboot>
172    <tt:Timeout>PT0S</tt:Timeout>
173  </trt:MediaUri>
174</trt:GetSnapshotUriResponse>"#,
175            uri = escape_text(&uri)
176        );
177        Ok(Bytes::from(xml))
178    }
179
180    async fn handle_get_video_sources(&self) -> Result<Bytes, SoapFault> {
181        let xml = format!(
182            r#"<trt:GetVideoSourcesResponse xmlns:trt="http://www.onvif.org/ver10/media/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema">
183  <trt:VideoSources token="{vs_token}">
184    <tt:Framerate>30</tt:Framerate>
185    <tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
186  </trt:VideoSources>
187</trt:GetVideoSourcesResponse>"#,
188            vs_token = VIDEO_SOURCE_TOKEN,
189        );
190        Ok(Bytes::from(xml))
191    }
192
193    async fn handle_get_video_source_configurations(&self) -> Result<Bytes, SoapFault> {
194        let xml = format!(
195            r#"<trt:GetVideoSourceConfigurationsResponse xmlns:trt="http://www.onvif.org/ver10/media/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema">
196  <trt:Configurations token="{vs_token}">
197    <tt:Name>VideoSourceConfig</tt:Name>
198    <tt:UseCount>1</tt:UseCount>
199    <tt:SourceToken>{vs_token}</tt:SourceToken>
200    <tt:Bounds x="0" y="0" width="1920" height="1080"/>
201  </trt:Configurations>
202</trt:GetVideoSourceConfigurationsResponse>"#,
203            vs_token = VIDEO_SOURCE_TOKEN,
204        );
205        Ok(Bytes::from(xml))
206    }
207
208    async fn handle_get_video_encoder_configurations(&self) -> Result<Bytes, SoapFault> {
209        let xml = format!(
210            r#"<trt:GetVideoEncoderConfigurationsResponse xmlns:trt="http://www.onvif.org/ver10/media/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema">
211  <trt:Configurations token="{ve_token}">
212    <tt:Name>VideoEncoderConfig</tt:Name>
213    <tt:UseCount>1</tt:UseCount>
214    <tt:Encoding>H264</tt:Encoding>
215    <tt:Resolution><tt:Width>1920</tt:Width><tt:Height>1080</tt:Height></tt:Resolution>
216    <tt:Quality>5</tt:Quality>
217    <tt:RateControl>
218      <tt:FrameRateLimit>30</tt:FrameRateLimit>
219      <tt:EncodingInterval>1</tt:EncodingInterval>
220      <tt:BitrateLimit>4096</tt:BitrateLimit>
221    </tt:RateControl>
222    <tt:Multicast>
223      <tt:Address><tt:Type>IPv4</tt:Type><tt:IPv4Address>0.0.0.0</tt:IPv4Address></tt:Address>
224      <tt:Port>0</tt:Port>
225      <tt:TTL>0</tt:TTL>
226      <tt:AutoStart>false</tt:AutoStart>
227    </tt:Multicast>
228    <tt:SessionTimeout>PT10S</tt:SessionTimeout>
229  </trt:Configurations>
230</trt:GetVideoEncoderConfigurationsResponse>"#,
231            ve_token = VIDEO_ENCODER_TOKEN,
232        );
233        Ok(Bytes::from(xml))
234    }
235}