onvif_server/service/
media.rs1use 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
16const 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}