1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! # oxvif
//!
//! An async Rust client library for the [ONVIF] IP camera protocol.
//!
//! ONVIF (Open Network Video Interface Forum) is the industry standard for
//! interoperability between IP-based security cameras. This library provides
//! a complete async client covering device management, media streaming,
//! PTZ control, imaging, on-screen display, events, recording, search, and
//! replay — all over SOAP/HTTP(S) with WS-Security authentication.
//!
//! ## ONVIF Profile coverage
//!
//! | Profile | Description | Coverage | Notes |
//! |---------|-------------|----------|-------|
//! | **Profile S** | Video streaming | ~95% | All core operations implemented; PTZ move commands available but not shown in examples |
//! | **Profile T** | Advanced streaming (H.265, focus, OSD, audio) | ~85% | Metadata RTP streaming excluded (not SOAP) |
//! | **Profile G** | Recording & playback | ~55% | Read/search/replay fully covered; write management (CreateRecording, RecordingJobs) not yet implemented |
//!
//! ## Supported services
//!
//! - **Device** — capabilities, scopes, device info, hostname, NTP, reboot,
//! user management, network interfaces/protocols/DNS/gateway, relay outputs,
//! storage configurations, system log/URIs, factory default, discovery mode
//! - **Media1 / Media2** — profiles, RTSP/snapshot URIs, video + audio config, OSD
//! - **PTZ** — absolute/relative/continuous move, presets, home position, status,
//! configurations, nodes
//! - **Imaging** — brightness/contrast/exposure settings, focus move/stop/status
//! - **Events** — pull-point subscriptions, event polling, renew, unsubscribe
//! - **Recording** — list stored recordings
//! - **Search** — find recordings by scope, collect results, end search
//! - **Replay** — get RTSP playback URI for a stored recording
//! - **WS-Discovery** — UDP multicast probe to find cameras on the local network
//!
//! ## Architecture
//!
//! ```text
//! ┌──────────────────────────────────────────────────────┐
//! │ OnvifSession │ ← high-level API
//! │ (caches service URLs, no URL params needed) │
//! ├──────────────────────────────────────────────────────┤
//! │ OnvifClient │ ← low-level API
//! ├──────────────────────────────────────────────────────┤
//! │ soap::SoapEnvelope │ soap::WsSecurityToken │ ← SOAP layer
//! ├──────────────────────────────────────────────────────┤
//! │ Transport trait │ ← HTTP abstraction
//! │ (HttpTransport / mock in tests) │
//! └──────────────────────────────────────────────────────┘
//! ```
//!
//! ## Quick start — `OnvifSession` (recommended)
//!
//! [`OnvifSession`] calls `GetCapabilities` once at construction and caches all
//! service URLs. No need to pass URL parameters to individual methods.
//!
//! ```no_run
//! use oxvif::{OnvifSession, OnvifError};
//!
//! async fn run() -> Result<(), OnvifError> {
//! let session = OnvifSession::builder("http://192.168.1.100/onvif/device_service")
//! .with_credentials("admin", "password")
//! .with_clock_sync() // syncs WS-Security timestamp with device clock
//! .build()
//! .await?;
//!
//! let profiles = session.get_profiles().await?;
//! let uri = session.get_stream_uri(&profiles[0].token).await?;
//! println!("RTSP stream: {}", uri.uri);
//!
//! let status = session.ptz_get_status(&profiles[0].token).await?;
//! println!("Pan: {:?} Tilt: {:?}", status.pan, status.tilt);
//! Ok(())
//! }
//! ```
//!
//! ## Low-level access — `OnvifClient`
//!
//! [`OnvifClient`] is stateless and gives direct control over every call.
//! Use it when you need fine-grained URL routing or a custom transport.
//!
//! ```no_run
//! use oxvif::{OnvifClient, OnvifError};
//!
//! async fn run() -> Result<(), OnvifError> {
//! let client = OnvifClient::new("http://192.168.1.100/onvif/device_service")
//! .with_credentials("admin", "password");
//!
//! let dt = client.get_system_date_and_time().await?;
//! let client = client.with_utc_offset(dt.utc_offset_secs());
//!
//! let caps = client.get_capabilities().await?;
//! let media_url = caps.media.url.as_deref().unwrap();
//!
//! let profiles = client.get_profiles(media_url).await?;
//! let uri = client.get_stream_uri(media_url, &profiles[0].token).await?;
//! println!("RTSP stream: {}", uri.uri);
//! Ok(())
//! }
//! ```
//!
//! ## Testing without a real camera
//!
//! Implement [`transport::Transport`] to inject any XML fixture:
//!
//! ```no_run
//! use oxvif::transport::{Transport, TransportError};
//! use async_trait::async_trait;
//! use std::sync::Arc;
//!
//! struct MockTransport { xml: String }
//!
//! #[async_trait]
//! impl Transport for MockTransport {
//! async fn soap_post(&self, _url: &str, _action: &str, _body: String)
//! -> Result<String, TransportError>
//! {
//! Ok(self.xml.clone())
//! }
//! }
//!
//! # async fn example() {
//! let client = oxvif::OnvifClient::new("http://ignored")
//! .with_transport(Arc::new(MockTransport { xml: "<s:Envelope/>".into() }));
//! # }
//! ```
//!
//! [ONVIF]: https://www.onvif.org
pub use OnvifClient;
pub use DiscoveredDevice;
pub use OnvifError;
pub use ;
pub use ;