pub struct ClientManager { /* private fields */ }
Expand description
High-level SIP client manager that coordinates all client operations
The ClientManager
is the primary entry point for VoIP client functionality.
It provides a high-level, async API for SIP registration, call management,
and media control while delegating to session-core for the underlying
SIP protocol implementation.
§Architecture
┌─────────────────────────┐
│ Application Layer │
└───────────┬─────────────┘
│
┌───────────▼─────────────┐
│ ClientManager │ ◄── This Layer
│ ┌─────────────────────┐ │
│ │ Registration Mgmt │ │ • SIP REGISTER handling
│ │ Call Management │ │ • Event coordination
│ │ Media Integration │ │ • State management
│ │ Event Broadcasting │ │ • Error handling
│ └─────────────────────┘ │
└───────────┬─────────────┘
│
┌───────────▼─────────────┐
│ session-core │
│ SessionCoordinator │
└─────────────────────────┘
§Core Features
§Registration Management
- SIP Registration: Register with SIP servers using REGISTER requests
- Authentication: Handle digest authentication challenges
- Refresh: Automatic and manual registration refresh
- Multiple Registrations: Support multiple simultaneous registrations
§Call Management
- Outbound Calls: Initiate calls to SIP URIs or phone numbers
- Inbound Calls: Accept incoming calls with proper SDP negotiation
- Call Control: Hold, resume, transfer, and hangup operations
- DTMF: Send dual-tone multi-frequency signals during calls
§Media Integration
- Codec Support: Multiple audio codecs (G.711, G.729, Opus)
- Quality Control: Real-time media quality monitoring
- Echo Cancellation: Built-in acoustic echo cancellation
- Noise Suppression: Advanced noise reduction algorithms
§Event System
- Real-time Events: Registration, call, and media events
- Broadcast Channel: Multi-consumer event distribution
- Typed Events: Strongly-typed event structures
- Priority Levels: Event prioritization for handling
§Usage Examples
§Basic Client Setup
use rvoip_client_core::{ClientManager, ClientConfig};
use std::net::SocketAddr;
async fn basic_setup() -> Result<(), Box<dyn std::error::Error>> {
// Create client configuration
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5060".parse()?);
// Create and start client manager
let client = ClientManager::new(config).await?;
client.start().await?;
println!("✅ SIP client started successfully");
// Clean shutdown
client.stop().await?;
Ok(())
}
§Registration and Call Flow
use rvoip_client_core::{ClientManager, ClientConfig, RegistrationConfig};
use std::time::Duration;
async fn registration_flow() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5061".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
// Register with SIP server
let reg_config = RegistrationConfig {
server_uri: "sip:192.168.1.100:5060".to_string(),
from_uri: "sip:alice@example.com".to_string(),
contact_uri: "sip:alice@127.0.0.1:5061".to_string(),
expires: 3600,
username: None,
password: None,
realm: None,
};
let registration_id = client.register(reg_config).await?;
println!("✅ Registered with ID: {}", registration_id);
// Make a call (would be implemented in calls.rs)
// let call_id = client.make_call("sip:bob@example.com").await?;
// Clean up
client.unregister(registration_id).await?;
client.stop().await?;
Ok(())
}
§Event Monitoring
use rvoip_client_core::{ClientManager, ClientConfig, ClientEvent};
use tokio::time::{timeout, Duration};
async fn event_monitoring() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5062".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
// Subscribe to events
let mut event_rx = client.subscribe_events();
// Monitor events for a short time
let event_task = tokio::spawn(async move {
let mut event_count = 0;
while event_count < 3 {
if let Ok(event) = timeout(Duration::from_millis(100), event_rx.recv()).await {
match event {
Ok(ClientEvent::RegistrationStatusChanged { info, .. }) => {
println!("📋 Registration event: {} -> {:?}",
info.user_uri, info.status);
}
Ok(ClientEvent::CallStateChanged { info, .. }) => {
println!("📞 Call event: {} -> {:?}",
info.call_id, info.new_state);
}
Ok(ClientEvent::MediaEvent { info, .. }) => {
println!("🎵 Media event: Call {} event occurred",
info.call_id);
}
Ok(ClientEvent::IncomingCall { .. }) |
Ok(ClientEvent::ClientError { .. }) |
Ok(ClientEvent::NetworkEvent { .. }) => {
// Handle other events as needed
}
Err(_) => break,
}
event_count += 1;
} else {
break; // Timeout
}
}
});
// Wait for event monitoring to complete
let _ = event_task.await;
client.stop().await?;
Ok(())
}
Implementations§
Source§impl ClientManager
Call operations implementation for ClientManager
impl ClientManager
Call operations implementation for ClientManager
Sourcepub async fn make_call(
&self,
from: String,
to: String,
subject: Option<String>,
) -> ClientResult<CallId>
pub async fn make_call( &self, from: String, to: String, subject: Option<String>, ) -> ClientResult<CallId>
Make an outgoing call with enhanced information tracking
This method initiates a new outgoing call using the session-core infrastructure. It handles SDP generation, session creation, and proper event notification.
§Arguments
from
- The caller’s SIP URI (e.g., “sip:alice@example.com”)to
- The callee’s SIP URI (e.g., “sip:bob@example.com”)subject
- Optional call subject/reason
§Returns
Returns a unique CallId
that can be used to track and control the call.
§Errors
ClientError::InvalidConfiguration
- If the URIs are malformedClientError::NetworkError
- If there’s a network connectivity issueClientError::CallSetupFailed
- If the call cannot be initiated
§Examples
Basic call:
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn make_basic_call() -> Result<CallId, Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5060".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = client.make_call(
"sip:alice@ourcompany.com".to_string(),
"sip:bob@example.com".to_string(),
None,
).await?;
println!("Outgoing call started: {}", call_id);
Ok(call_id)
}
Call with subject:
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn make_call_with_subject() -> Result<CallId, Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5061".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = client.make_call(
"sip:support@ourcompany.com".to_string(),
"sip:customer@example.com".to_string(),
Some("Technical Support Call".to_string()),
).await?;
Ok(call_id)
}
§Call Flow
- Validates the SIP URIs
- Creates a new session via session-core
- Generates and stores call metadata
- Emits appropriate events
- Returns the CallId for tracking
Sourcepub async fn answer_call(&self, call_id: &CallId) -> ClientResult<()>
pub async fn answer_call(&self, call_id: &CallId) -> ClientResult<()>
Answer an incoming call with SDP negotiation
This method accepts an incoming call that was previously stored by the event handler. It performs SDP offer/answer negotiation and establishes the media session.
§Arguments
call_id
- The unique identifier of the incoming call to answer
§Returns
Returns Ok(())
if the call was successfully answered and connected.
§Errors
ClientError::CallNotFound
- If no incoming call exists with the given IDClientError::CallSetupFailed
- If SDP negotiation or call setup failsClientError::InvalidCallState
- If the call is not in an answerable state
§Examples
Basic call answering:
use rvoip_client_core::{ClientManager, ClientConfig, CallId, ClientEventHandler, CallAction, IncomingCallInfo};
use std::sync::Arc;
struct MyEventHandler;
#[async_trait::async_trait]
impl ClientEventHandler for MyEventHandler {
async fn on_incoming_call(&self, call_info: IncomingCallInfo) -> CallAction {
// Store call_id for later use
println!("Incoming call from: {}", call_info.caller_uri);
CallAction::Ignore // Let application handle it
}
async fn on_call_state_changed(&self, _info: rvoip_client_core::CallStatusInfo) {}
async fn on_registration_status_changed(&self, _info: rvoip_client_core::RegistrationStatusInfo) {}
async fn on_media_event(&self, _info: rvoip_client_core::MediaEventInfo) {}
async fn on_client_error(&self, _error: rvoip_client_core::ClientError, _call_id: Option<CallId>) {}
async fn on_network_event(&self, _connected: bool, _reason: Option<String>) {}
}
async fn answer_incoming_call(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5062".parse()?);
let client = ClientManager::new(config).await?;
client.set_event_handler(Arc::new(MyEventHandler)).await;
client.start().await?;
// Answer the call (assuming call_id was obtained from event handler)
client.answer_call(&call_id).await?;
println!("Successfully answered call: {}", call_id);
Ok(())
}
§SDP Negotiation Process
- Retrieves the stored incoming call information
- If an SDP offer was provided, generates an appropriate SDP answer
- If no offer was provided, generates an SDP offer (rare case)
- Calls session-core to accept the call with the negotiated SDP
- Updates call state to Connected and emits events
§Thread Safety
This method is async and thread-safe. Multiple calls can be answered concurrently.
Sourcepub async fn reject_call(&self, call_id: &CallId) -> ClientResult<()>
pub async fn reject_call(&self, call_id: &CallId) -> ClientResult<()>
Reject an incoming call with optional reason
This method rejects an incoming call that was previously stored by the event handler. The call will be terminated with a SIP rejection response.
§Arguments
call_id
- The unique identifier of the incoming call to reject
§Returns
Returns Ok(())
if the call was successfully rejected.
§Errors
ClientError::CallNotFound
- If no incoming call exists with the given IDClientError::CallTerminated
- If the rejection fails to send properly
§Examples
Basic call rejection:
use rvoip_client_core::{ClientManager, ClientConfig, CallId, ClientEventHandler, CallAction, IncomingCallInfo};
use std::sync::Arc;
struct RejectingEventHandler;
#[async_trait::async_trait]
impl ClientEventHandler for RejectingEventHandler {
async fn on_incoming_call(&self, call_info: IncomingCallInfo) -> CallAction {
// Automatically reject calls from unknown numbers
if !call_info.caller_uri.contains("@trusted-domain.com") {
CallAction::Reject
} else {
CallAction::Ignore
}
}
async fn on_call_state_changed(&self, _info: rvoip_client_core::CallStatusInfo) {}
async fn on_registration_status_changed(&self, _info: rvoip_client_core::RegistrationStatusInfo) {}
async fn on_media_event(&self, _info: rvoip_client_core::MediaEventInfo) {}
async fn on_client_error(&self, _error: rvoip_client_core::ClientError, _call_id: Option<CallId>) {}
async fn on_network_event(&self, _connected: bool, _reason: Option<String>) {}
}
async fn reject_unwanted_call(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5063".parse()?);
let client = ClientManager::new(config).await?;
client.set_event_handler(Arc::new(RejectingEventHandler)).await;
client.start().await?;
// Reject the call
client.reject_call(&call_id).await?;
println!("Successfully rejected call: {}", call_id);
Ok(())
}
§Call Rejection Process
- Retrieves the stored incoming call information
- Sends a SIP rejection response (typically 603 Decline)
- Updates call state to Terminated
- Records rejection reason in metadata
- Emits appropriate events
§SIP Response Codes
The rejection will typically result in a SIP 603 “Decline” response being sent to the caller, indicating that the call was explicitly rejected by the user.
Sourcepub async fn hangup_call(&self, call_id: &CallId) -> ClientResult<()>
pub async fn hangup_call(&self, call_id: &CallId) -> ClientResult<()>
Terminate an active call (hang up)
This method terminates any call regardless of its current state. It handles proper session cleanup and state management.
§Arguments
call_id
- The unique identifier of the call to terminate
§Returns
Returns Ok(())
if the call was successfully terminated or was already terminated.
§Errors
ClientError::CallNotFound
- If no call exists with the given IDClientError::CallTerminated
- If the termination process fails
§Examples
Basic call hangup:
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn hangup_active_call(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5064".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
// Terminate the call
client.hangup_call(&call_id).await?;
println!("Successfully hung up call: {}", call_id);
Ok(())
}
Hangup with error handling:
use rvoip_client_core::{ClientManager, ClientConfig, CallId, ClientError};
async fn safe_hangup(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5065".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
match client.hangup_call(&call_id).await {
Ok(()) => {
println!("Call terminated successfully");
}
Err(ClientError::CallNotFound { .. }) => {
println!("Call was already terminated or doesn't exist");
}
Err(e) => {
eprintln!("Failed to hangup call: {}", e);
return Err(e.into());
}
}
Ok(())
}
§Termination Process
- Locates the session associated with the call
- Checks current call state (skips if already terminated)
- Calls session-core to terminate the SIP session
- Updates call state to Terminated
- Updates statistics and emits events
§Idempotent Operation
This method is idempotent - calling it multiple times on the same call will not cause errors. If the call is already terminated, it will return success immediately.
Sourcepub async fn get_call(&self, call_id: &CallId) -> ClientResult<CallInfo>
pub async fn get_call(&self, call_id: &CallId) -> ClientResult<CallInfo>
Get basic information about a specific call
Retrieves the current state and metadata for a call by its ID.
§Arguments
call_id
- The unique identifier of the call to query
§Returns
Returns a CallInfo
struct containing all information about the call.
§Errors
ClientError::CallNotFound
- If no call exists with the given ID
§Examples
use rvoip_client_core::{ClientManager, ClientConfig, CallId, CallState};
async fn check_call_status(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5066".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_info = client.get_call(&call_id).await?;
println!("Call ID: {}", call_info.call_id);
println!("State: {:?}", call_info.state);
println!("From: {}", call_info.local_uri);
println!("To: {}", call_info.remote_uri);
if let Some(connected_at) = call_info.connected_at {
println!("Connected at: {}", connected_at);
}
match call_info.state {
CallState::Connected => println!("Call is active"),
CallState::Terminated => println!("Call has ended"),
_ => println!("Call is in progress"),
}
Ok(())
}
Sourcepub async fn get_call_detailed(
&self,
call_id: &CallId,
) -> ClientResult<CallInfo>
pub async fn get_call_detailed( &self, call_id: &CallId, ) -> ClientResult<CallInfo>
Get detailed call information with enhanced metadata
Retrieves comprehensive information about a call including session metadata and real-time statistics.
§Arguments
call_id
- The unique identifier of the call to query
§Returns
Returns a CallInfo
struct with additional metadata fields populated.
§Errors
ClientError::CallNotFound
- If no call exists with the given ID
§Examples
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn get_detailed_call_info(call_id: CallId) -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5067".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let detailed_info = client.get_call_detailed(&call_id).await?;
println!("Call Details:");
println!(" ID: {}", detailed_info.call_id);
println!(" SIP Call-ID: {}", detailed_info.sip_call_id);
// Check enhanced metadata
for (key, value) in &detailed_info.metadata {
println!(" {}: {}", key, value);
}
if let Some(session_id) = detailed_info.metadata.get("session_id") {
println!(" Session tracking: {}", session_id);
}
Ok(())
}
§Enhanced Metadata
The detailed call information includes additional metadata fields:
session_id
- The internal session-core session identifierlast_updated
- ISO 8601 timestamp of the last metadata update- Plus any existing metadata from the basic call info
Sourcepub async fn list_calls(&self) -> Vec<CallInfo>
pub async fn list_calls(&self) -> Vec<CallInfo>
List all calls (active and historical)
Returns a vector of all calls known to the client, regardless of their state. This includes active calls, completed calls, and failed calls.
§Returns
Returns a Vec<CallInfo>
containing all calls. The list is not sorted.
§Examples
use rvoip_client_core::{ClientManager, ClientConfig, CallState};
async fn review_all_calls() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5068".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let all_calls = client.list_calls().await;
println!("Total calls: {}", all_calls.len());
for call in all_calls {
println!("Call {}: {} -> {} ({:?})",
call.call_id,
call.local_uri,
call.remote_uri,
call.state);
}
Ok(())
}
§Performance Note
This method iterates through all stored calls. For applications with
many historical calls, consider using filtered methods like
get_active_calls()
or get_calls_by_state()
instead.
Sourcepub async fn get_calls_by_state(&self, state: CallState) -> Vec<CallInfo>
pub async fn get_calls_by_state(&self, state: CallState) -> Vec<CallInfo>
Get calls filtered by state
Returns all calls that are currently in the specified state.
§Arguments
state
- The call state to filter by
§Returns
Returns a Vec<CallInfo>
containing all calls in the specified state.
§Examples
Get all connected calls:
use rvoip_client_core::{ClientManager, ClientConfig, CallState};
async fn list_connected_calls() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5069".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let connected_calls = client.get_calls_by_state(CallState::Connected).await;
println!("Currently connected calls: {}", connected_calls.len());
for call in connected_calls {
if let Some(connected_at) = call.connected_at {
let duration = chrono::Utc::now().signed_duration_since(connected_at);
println!("Call {}: {} minutes active",
call.call_id,
duration.num_minutes());
}
}
Ok(())
}
Get all failed calls:
use rvoip_client_core::{ClientManager, ClientConfig, CallState};
async fn review_failed_calls() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5070".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let failed_calls = client.get_calls_by_state(CallState::Failed).await;
for call in failed_calls {
println!("Failed call: {} -> {}", call.local_uri, call.remote_uri);
if let Some(reason) = call.metadata.get("failure_reason") {
println!(" Reason: {}", reason);
}
}
Ok(())
}
Sourcepub async fn get_calls_by_direction(
&self,
direction: CallDirection,
) -> Vec<CallInfo>
pub async fn get_calls_by_direction( &self, direction: CallDirection, ) -> Vec<CallInfo>
Get calls filtered by direction (incoming or outgoing)
Returns all calls that match the specified direction.
§Arguments
direction
- The call direction to filter by (Incoming
orOutgoing
)
§Returns
Returns a Vec<CallInfo>
containing all calls with the specified direction.
§Examples
Get all outgoing calls:
use rvoip_client_core::{ClientManager, ClientConfig, CallDirection};
async fn review_outgoing_calls() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5071".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let outgoing_calls = client.get_calls_by_direction(CallDirection::Outgoing).await;
println!("Outgoing calls made: {}", outgoing_calls.len());
for call in outgoing_calls {
println!("Called: {} at {}", call.remote_uri, call.created_at);
}
Ok(())
}
Get all incoming calls:
use rvoip_client_core::{ClientManager, ClientConfig, CallDirection};
async fn review_incoming_calls() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5072".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let incoming_calls = client.get_calls_by_direction(CallDirection::Incoming).await;
println!("Calls received: {}", incoming_calls.len());
for call in incoming_calls {
println!("From: {} ({})",
call.remote_display_name.as_deref().unwrap_or("Unknown"),
call.remote_uri);
}
Ok(())
}
Sourcepub async fn get_call_history(&self) -> Vec<CallInfo>
pub async fn get_call_history(&self) -> Vec<CallInfo>
Get call history (completed and terminated calls)
Returns all calls that have finished, regardless of how they ended. This includes successfully completed calls, failed calls, and cancelled calls.
§Returns
Returns a Vec<CallInfo>
containing all terminated calls.
§Examples
use rvoip_client_core::{ClientManager, ClientConfig, CallState};
async fn generate_call_report() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5073".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let history = client.get_call_history().await;
let mut completed = 0;
let mut failed = 0;
let mut cancelled = 0;
let mut total_duration = chrono::Duration::zero();
for call in history {
match call.state {
CallState::Terminated => {
completed += 1;
if let (Some(connected), Some(ended)) = (call.connected_at, call.ended_at) {
total_duration = total_duration + ended.signed_duration_since(connected);
}
}
CallState::Failed => failed += 1,
CallState::Cancelled => cancelled += 1,
_ => {} // Should not happen in history
}
}
println!("Call History Summary:");
println!(" Completed: {}", completed);
println!(" Failed: {}", failed);
println!(" Cancelled: {}", cancelled);
println!(" Total talk time: {} minutes", total_duration.num_minutes());
Ok(())
}
§Use Cases
- Call reporting and analytics
- Billing and usage tracking
- Debugging call quality issues
- User activity monitoring
Sourcepub async fn get_active_calls(&self) -> Vec<CallInfo>
pub async fn get_active_calls(&self) -> Vec<CallInfo>
Get all currently active calls
Returns all calls that are not in a terminated state. This includes calls that are connecting, ringing, connected, or in any other non-final state.
§Returns
Returns a Vec<CallInfo>
containing all active calls.
§Examples
Monitor active calls:
use rvoip_client_core::{ClientManager, ClientConfig, CallState};
async fn monitor_active_calls() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5074".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let active_calls = client.get_active_calls().await;
if active_calls.is_empty() {
println!("No active calls");
} else {
println!("Active calls: {}", active_calls.len());
for call in active_calls {
match call.state {
CallState::Initiating => {
println!("📞 Dialing {} -> {}", call.local_uri, call.remote_uri);
}
CallState::Ringing => {
println!("📳 Ringing {} -> {}", call.local_uri, call.remote_uri);
}
CallState::Connected => {
if let Some(connected_at) = call.connected_at {
let duration = chrono::Utc::now().signed_duration_since(connected_at);
println!("☎️ Connected {} -> {} ({}:{})",
call.local_uri, call.remote_uri,
duration.num_minutes(),
duration.num_seconds() % 60);
}
}
_ => {
println!("📱 {} -> {} ({:?})", call.local_uri, call.remote_uri, call.state);
}
}
}
}
Ok(())
}
§Real-time Monitoring
This method is useful for:
- Dashboard displays showing current call status
- Resource management (checking call limits)
- User interface updates
- Load balancing decisions
Sourcepub async fn get_client_stats(&self) -> ClientStats
pub async fn get_client_stats(&self) -> ClientStats
Get comprehensive client statistics
Returns detailed statistics about the client’s call activity and performance. The statistics are recalculated on each call to ensure accuracy.
§Returns
Returns a ClientStats
struct containing:
- Total number of calls ever made/received
- Currently connected calls count
- Other performance metrics
§Examples
Basic statistics display:
use rvoip_client_core::{ClientManager, ClientConfig};
async fn display_client_stats() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5075".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let stats = client.get_client_stats().await;
println!("Client Statistics:");
println!(" Total calls: {}", stats.total_calls);
println!(" Connected calls: {}", stats.connected_calls);
println!(" Utilization: {:.1}%",
if stats.total_calls > 0 {
(stats.connected_calls as f64 / stats.total_calls as f64) * 100.0
} else {
0.0
});
Ok(())
}
Monitoring loop:
use rvoip_client_core::{ClientManager, ClientConfig};
use tokio::time::{interval, Duration};
async fn monitor_client_performance() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5076".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let mut interval = interval(Duration::from_secs(30));
// Monitor for a limited time in doc test
for _ in 0..3 {
interval.tick().await;
let stats = client.get_client_stats().await;
println!("📊 Stats: {} total, {} active",
stats.total_calls, stats.connected_calls);
if stats.connected_calls > 10 {
println!("⚠️ High call volume detected");
}
}
Ok(())
}
§Accuracy Guarantee
This method recalculates statistics from the actual call states rather than relying on potentially inconsistent counters. This prevents issues with:
- Race conditions in concurrent call handling
- Integer overflow/underflow
- Inconsistent state after error recovery
§Performance Note
The recalculation involves iterating through all calls, so for applications with very large call histories, consider calling this method judiciously.
Source§impl ClientManager
Call control operations implementation for ClientManager
impl ClientManager
Call control operations implementation for ClientManager
Sourcepub async fn hold_call(&self, call_id: &CallId) -> ClientResult<()>
pub async fn hold_call(&self, call_id: &CallId) -> ClientResult<()>
Put an active call on hold
This method places a call in hold state, which typically mutes the audio stream and may play hold music to the remote party. The call remains connected but media transmission is suspended until the call is resumed.
§Arguments
call_id
- The unique identifier of the call to put on hold
§Returns
Returns Ok(())
if the call was successfully placed on hold.
§Errors
ClientError::CallNotFound
- If no call exists with the given IDClientError::InvalidCallState
- If the call is not in a holdable stateClientError::CallSetupFailed
- If the hold operation fails
§State Requirements
The call must be in the Connected
state to be placed on hold. Calls in
other states (such as Ringing
, Terminated
, etc.) cannot be held.
§Examples
§Basic Hold Operation
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn hold_active_call() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5060".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Put call on hold
match client.hold_call(&call_id).await {
Ok(()) => {
println!("Call {} successfully placed on hold", call_id);
// Verify hold status
if let Ok(on_hold) = client.is_call_on_hold(&call_id).await {
assert!(on_hold);
}
}
Err(e) => {
eprintln!("Failed to hold call: {}", e);
}
}
Ok(())
}
§Hold with Capability Check
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn safe_hold_call() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5061".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Check if call can be held before attempting
if let Ok(capabilities) = client.get_call_capabilities(&call_id).await {
if capabilities.can_hold {
client.hold_call(&call_id).await?;
println!("Call successfully held");
} else {
println!("Call cannot be held in current state");
}
}
Ok(())
}
§Error Handling
use rvoip_client_core::{ClientManager, ClientConfig, CallId, ClientError};
async fn hold_with_error_handling() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5062".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
match client.hold_call(&call_id).await {
Ok(()) => {
println!("✅ Call placed on hold successfully");
}
Err(ClientError::CallNotFound { .. }) => {
println!("❌ Call not found - may have been terminated");
}
Err(ClientError::InvalidCallState { current_state, .. }) => {
println!("❌ Cannot hold call in state: {:?}", current_state);
}
Err(e) => {
println!("❌ Hold operation failed: {}", e);
}
}
Ok(())
}
§Side Effects
- Updates call metadata with hold status and timestamp
- Triggers media events for hold state change
- May play hold music to the remote party (depending on server configuration)
- Audio transmission is suspended for the local party
Sourcepub async fn resume_call(&self, call_id: &CallId) -> ClientResult<()>
pub async fn resume_call(&self, call_id: &CallId) -> ClientResult<()>
Resume a call from hold state
This method resumes a previously held call, restoring audio transmission
and returning the call to its active connected state. The call must have
been previously placed on hold using hold_call()
.
§Arguments
call_id
- The unique identifier of the call to resume
§Returns
Returns Ok(())
if the call was successfully resumed from hold.
§Errors
ClientError::CallNotFound
- If no call exists with the given IDClientError::CallSetupFailed
- If the resume operation fails
§State Requirements
The call should be in a held state to be resumed. However, this method will attempt to resume any call that exists, as the session-core layer handles the actual state validation.
§Examples
§Basic Resume Operation
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn resume_held_call() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5063".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// First put call on hold (would normally be done earlier)
if let Err(e) = client.hold_call(&call_id).await {
println!("Hold failed: {}", e);
return Ok(());
}
// Verify call is on hold
if let Ok(on_hold) = client.is_call_on_hold(&call_id).await {
println!("Call on hold: {}", on_hold);
}
// Resume the call
match client.resume_call(&call_id).await {
Ok(()) => {
println!("Call {} successfully resumed", call_id);
// Verify call is no longer on hold
if let Ok(on_hold) = client.is_call_on_hold(&call_id).await {
assert!(!on_hold);
}
}
Err(e) => {
eprintln!("Failed to resume call: {}", e);
}
}
Ok(())
}
§Resume with Capability Check
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn safe_resume_call() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5064".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Check if call can be resumed before attempting
if let Ok(capabilities) = client.get_call_capabilities(&call_id).await {
if capabilities.can_resume {
client.resume_call(&call_id).await?;
println!("Call successfully resumed");
} else {
println!("Call cannot be resumed (not on hold or wrong state)");
}
}
Ok(())
}
§Hold/Resume Cycle
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
use tokio::time::{sleep, Duration};
async fn hold_resume_cycle() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5065".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Put call on hold
if client.hold_call(&call_id).await.is_ok() {
println!("Call placed on hold");
// Wait briefly (in real app, this might be much longer)
sleep(Duration::from_millis(100)).await;
// Resume the call
if client.resume_call(&call_id).await.is_ok() {
println!("Call resumed from hold");
// Verify final state
if let Ok(on_hold) = client.is_call_on_hold(&call_id).await {
println!("Final hold state: {}", on_hold);
}
}
}
Ok(())
}
§Side Effects
- Updates call metadata to remove hold status and add resume timestamp
- Triggers media events for hold state change (on_hold: false)
- Resumes audio transmission between parties
- May stop hold music playback (if configured)
Sourcepub async fn is_call_on_hold(&self, call_id: &CallId) -> ClientResult<bool>
pub async fn is_call_on_hold(&self, call_id: &CallId) -> ClientResult<bool>
Check if a call is currently on hold
This method queries the hold status of a call by examining its metadata.
It returns true
if the call is currently on hold, false
if active,
or an error if the call doesn’t exist.
§Arguments
call_id
- The unique identifier of the call to check
§Returns
Returns Ok(true)
if the call is on hold, Ok(false)
if active.
§Errors
ClientError::CallNotFound
- If no call exists with the given ID
§Examples
§Basic Hold Status Check
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn check_hold_status() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5066".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Check initial status
match client.is_call_on_hold(&call_id).await {
Ok(on_hold) => {
println!("Call on hold: {}", on_hold);
assert!(!on_hold); // Should be false initially
}
Err(e) => {
println!("Error checking hold status: {}", e);
}
}
Ok(())
}
§Hold Status Monitoring
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn monitor_hold_status() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5067".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Check status before hold
if let Ok(status) = client.is_call_on_hold(&call_id).await {
println!("Before hold: {}", status);
}
// Put call on hold (ignore errors for doc test)
let _ = client.hold_call(&call_id).await;
// Check status after hold
if let Ok(status) = client.is_call_on_hold(&call_id).await {
println!("After hold: {}", status);
if status {
println!("✅ Call is now on hold");
}
}
// Resume call (ignore errors for doc test)
let _ = client.resume_call(&call_id).await;
// Check status after resume
if let Ok(status) = client.is_call_on_hold(&call_id).await {
println!("After resume: {}", status);
if !status {
println!("✅ Call is now active");
}
}
Ok(())
}
§Conditional Operations Based on Hold Status
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn conditional_operations() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5068".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Perform different actions based on hold status
match client.is_call_on_hold(&call_id).await {
Ok(true) => {
println!("Call is on hold - offering to resume");
// Could resume the call
// client.resume_call(&call_id).await?;
}
Ok(false) => {
println!("Call is active - offering to hold");
// Could put call on hold
// client.hold_call(&call_id).await?;
}
Err(e) => {
println!("Cannot check hold status: {}", e);
}
}
Ok(())
}
§Implementation Notes
This method checks the call’s metadata for an “on_hold” field that is set to “true” when a call is placed on hold and “false” when resumed. If the metadata field doesn’t exist, the call is considered not on hold.
Sourcepub async fn send_dtmf(
&self,
call_id: &CallId,
digits: &str,
) -> ClientResult<()>
pub async fn send_dtmf( &self, call_id: &CallId, digits: &str, ) -> ClientResult<()>
Send DTMF (Dual-Tone Multi-Frequency) digits during an active call
This method transmits DTMF tones to the remote party during a connected call. DTMF is commonly used for navigating phone menus, entering PINs, or other interactive voice response (IVR) interactions.
§Arguments
call_id
- The unique identifier of the call to send DTMF todigits
- A string containing valid DTMF characters to transmit
§Valid DTMF Characters
- Digits:
0-9
(standard numeric keypad) - Letters:
A-D
(extended DTMF for special applications) - Symbols:
*
(star) and#
(pound/hash)
§Returns
Returns Ok(())
if the DTMF digits were successfully transmitted.
§Errors
ClientError::CallNotFound
- If no call exists with the given IDClientError::InvalidCallState
- If the call is not in a connected stateClientError::InvalidConfiguration
- If digits are empty or contain invalid charactersClientError::CallSetupFailed
- If the DTMF transmission fails
§State Requirements
The call must be in the Connected
state to send DTMF. Calls that are
ringing, terminated, or in other states cannot transmit DTMF tones.
§Examples
§Basic DTMF Transmission
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn send_basic_dtmf() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5069".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Send individual digit
match client.send_dtmf(&call_id, "1").await {
Ok(()) => println!("✅ Sent DTMF digit '1'"),
Err(e) => println!("❌ DTMF failed: {}", e),
}
// Send multiple digits
if client.send_dtmf(&call_id, "123").await.is_ok() {
println!("✅ Sent DTMF sequence '123'");
}
Ok(())
}
§Interactive Menu Navigation
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
use tokio::time::{sleep, Duration};
async fn navigate_menu() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5070".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Navigate through a typical phone menu
let menu_sequence = [
("1", "Select English"),
("2", "Customer Service"),
("3", "Account Information"),
("*", "Return to previous menu"),
("#", "End menu navigation"),
];
for (digit, description) in menu_sequence {
if client.send_dtmf(&call_id, digit).await.is_ok() {
println!("📞 Sent '{}' - {}", digit, description);
// Wait between menu selections
sleep(Duration::from_millis(50)).await;
}
}
Ok(())
}
§PIN Entry with Validation
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn enter_pin() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5071".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Example PIN entry
let pin = "1234";
// Validate PIN before sending
for ch in pin.chars() {
if !ch.is_ascii_digit() {
println!("❌ Invalid PIN character: {}", ch);
return Ok(());
}
}
// Send PIN digits
match client.send_dtmf(&call_id, pin).await {
Ok(()) => {
println!("✅ PIN entered successfully");
// Send confirmation tone
if client.send_dtmf(&call_id, "#").await.is_ok() {
println!("✅ PIN confirmed with #");
}
}
Err(e) => {
println!("❌ PIN entry failed: {}", e);
}
}
Ok(())
}
§Extended DTMF Usage
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn extended_dtmf() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5072".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Use extended DTMF characters (A-D)
let extended_sequence = "123A456B789C0*#D";
match client.send_dtmf(&call_id, extended_sequence).await {
Ok(()) => {
println!("✅ Extended DTMF sequence sent");
println!("Sequence: {}", extended_sequence);
}
Err(e) => {
println!("❌ Extended DTMF failed: {}", e);
}
}
// Test individual extended characters
for digit in ['A', 'B', 'C', 'D'] {
let digit_str = digit.to_string();
if client.send_dtmf(&call_id, &digit_str).await.is_ok() {
println!("✅ Sent extended DTMF: {}", digit);
}
}
Ok(())
}
§Side Effects
- Updates call metadata with DTMF history and timestamps
- Triggers media events for DTMF transmission
- Transmits actual audio tones to the remote party
- Maintains a history of all DTMF transmissions for the call
§Implementation Notes
The method validates DTMF characters before transmission and maintains a history of all DTMF sequences sent during the call. Both uppercase and lowercase letters (A-D, a-d) are accepted and normalized.
Sourcepub async fn transfer_call(
&self,
call_id: &CallId,
target: &str,
) -> ClientResult<()>
pub async fn transfer_call( &self, call_id: &CallId, target: &str, ) -> ClientResult<()>
Transfer a call to another destination (blind transfer)
This method performs a blind transfer, which immediately transfers the call to the specified destination without consultation. The original caller is connected directly to the transfer target, and the transferring party is removed from the call.
§Arguments
call_id
- The unique identifier of the call to transfertarget
- The SIP or TEL URI of the transfer destination
§Valid Target Formats
- SIP URI:
sip:user@domain.com
orsip:user@192.168.1.100:5060
- TEL URI:
tel:+15551234567
(for PSTN numbers)
§Returns
Returns Ok(())
if the transfer was successfully initiated.
§Errors
ClientError::CallNotFound
- If no call exists with the given IDClientError::InvalidCallState
- If the call is not in a transferable stateClientError::InvalidConfiguration
- If the target URI is empty or invalidClientError::CallSetupFailed
- If the transfer operation fails
§State Requirements
The call must be in the Connected
state to be transferred. Calls in
other states (ringing, terminated, etc.) cannot be transferred.
§Examples
§Basic Blind Transfer
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn perform_blind_transfer() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5073".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Transfer to another SIP user
let transfer_target = "sip:support@example.com";
match client.transfer_call(&call_id, transfer_target).await {
Ok(()) => {
println!("✅ Call {} transferred to {}", call_id, transfer_target);
}
Err(e) => {
println!("❌ Transfer failed: {}", e);
}
}
Ok(())
}
§Transfer to PSTN Number
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn transfer_to_pstn() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5074".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Transfer to external phone number
let phone_number = "tel:+15551234567";
if client.transfer_call(&call_id, phone_number).await.is_ok() {
println!("✅ Call transferred to phone: {}", phone_number);
} else {
println!("❌ PSTN transfer failed");
}
Ok(())
}
§Transfer with Validation
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn validated_transfer() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5075".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
let target = "sip:manager@company.com";
// Check if call can be transferred before attempting
if let Ok(capabilities) = client.get_call_capabilities(&call_id).await {
if capabilities.can_transfer {
// Validate target format
if target.starts_with("sip:") || target.starts_with("tel:") {
match client.transfer_call(&call_id, target).await {
Ok(()) => {
println!("✅ Transfer completed successfully");
}
Err(e) => {
println!("❌ Transfer failed: {}", e);
}
}
} else {
println!("❌ Invalid target URI format");
}
} else {
println!("❌ Call cannot be transferred in current state");
}
}
Ok(())
}
§Multiple Transfer Destinations
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn try_multiple_transfers() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5076".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Try multiple transfer destinations in order
let transfer_options = [
("sip:primary@support.com", "Primary Support"),
("sip:backup@support.com", "Backup Support"),
("tel:+15551234567", "Emergency Line"),
];
for (target, description) in transfer_options {
match client.transfer_call(&call_id, target).await {
Ok(()) => {
println!("✅ Successfully transferred to {} ({})", target, description);
break; // Stop after first successful transfer
}
Err(e) => {
println!("❌ Failed to transfer to {}: {}", description, e);
// Continue to next option
}
}
}
Ok(())
}
§Transfer Types
This method performs a blind transfer (also called unattended transfer):
- The call is immediately transferred without consultation
- The transferring party does not speak to the transfer target first
- The original caller is connected directly to the transfer destination
- The transferring party is removed from the call immediately
For attended transfers (with consultation), use the attended_transfer()
method.
§Side Effects
- Updates call metadata with transfer information and timestamp
- Triggers media events for transfer initiation
- The local party is immediately disconnected from the call
- The remote party is connected to the transfer target
§SIP Protocol Notes
This method uses SIP REFER requests to perform the transfer, which is the standard mechanism defined in RFC 3515. The transfer target must be reachable and accept the incoming call for the transfer to succeed.
Sourcepub async fn attended_transfer(
&self,
call_id1: &CallId,
call_id2: &CallId,
) -> ClientResult<()>
pub async fn attended_transfer( &self, call_id1: &CallId, call_id2: &CallId, ) -> ClientResult<()>
Perform an attended transfer (consultative transfer)
This method performs an attended transfer, which connects two existing calls together. The typical scenario is having one call on hold while establishing a consultation call with the transfer target, then connecting the original caller directly to the transfer target.
§Arguments
call_id1
- The primary call to be transferred (usually the original call)call_id2
- The consultation call (the transfer target)
§Transfer Process
- Hold: The primary call (
call_id1
) is placed on hold - Consultation: The agent speaks with the transfer target (
call_id2
) - Transfer: The primary caller is connected to the transfer target
- Cleanup: The consultation call is terminated
§Returns
Returns Ok(())
if the attended transfer was successfully completed.
§Errors
ClientError::CallNotFound
- If either call ID doesn’t existClientError::InvalidCallState
- If either call is not in a transferable stateClientError::CallSetupFailed
- If any step of the transfer process fails
§State Requirements
Both calls must be in the Connected
state to perform an attended transfer.
§Examples
§Basic Attended Transfer
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn perform_attended_transfer() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5077".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
// Assume we have two active calls
let primary_call = CallId::new_v4(); // Original caller
let consultation_call = CallId::new_v4(); // Transfer target
match client.attended_transfer(&primary_call, &consultation_call).await {
Ok(()) => {
println!("✅ Attended transfer completed successfully");
println!("Primary caller connected to transfer target");
}
Err(e) => {
println!("❌ Attended transfer failed: {}", e);
}
}
Ok(())
}
§Step-by-Step Transfer Workflow
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
use tokio::time::{sleep, Duration};
async fn transfer_workflow() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5078".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let customer_call = CallId::new_v4();
let manager_call = CallId::new_v4();
// Step 1: Answer customer call (would be done earlier)
println!("📞 Customer call in progress...");
// Step 2: Put customer on hold to make consultation call
println!("⏸️ Putting customer on hold...");
if client.hold_call(&customer_call).await.is_ok() {
println!("✅ Customer on hold");
}
// Step 3: Make consultation call to manager (would be done earlier)
println!("📞 Calling manager for consultation...");
// client.make_call("sip:manager@company.com").await?;
// Step 4: Brief consultation (simulated)
sleep(Duration::from_millis(100)).await;
println!("💬 Consultation complete - transferring call");
// Step 5: Perform the attended transfer
match client.attended_transfer(&customer_call, &manager_call).await {
Ok(()) => {
println!("✅ Transfer complete - customer now speaking with manager");
}
Err(e) => {
println!("❌ Transfer failed: {}", e);
// Would typically resume customer call here
let _ = client.resume_call(&customer_call).await;
}
}
Ok(())
}
§Error Recovery Attended Transfer
use rvoip_client_core::{ClientManager, ClientConfig, CallId, ClientError};
async fn robust_attended_transfer() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5079".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let caller_id = CallId::new_v4();
let target_id = CallId::new_v4();
// Check capabilities before attempting transfer
let can_transfer_caller = client.get_call_capabilities(&caller_id).await
.map(|caps| caps.can_transfer)
.unwrap_or(false);
let can_transfer_target = client.get_call_capabilities(&target_id).await
.map(|caps| caps.can_transfer)
.unwrap_or(false);
if !can_transfer_caller || !can_transfer_target {
println!("❌ One or both calls cannot be transferred");
return Ok(());
}
match client.attended_transfer(&caller_id, &target_id).await {
Ok(()) => {
println!("✅ Attended transfer successful");
}
Err(ClientError::CallNotFound { call_id }) => {
println!("❌ Call {} no longer exists", call_id);
}
Err(ClientError::InvalidCallState { call_id, current_state }) => {
println!("❌ Call {} in invalid state: {:?}", call_id, current_state);
// Try to recover by resuming the original call
if let Err(e) = client.resume_call(&caller_id).await {
println!("❌ Failed to resume original call: {}", e);
}
}
Err(e) => {
println!("❌ Transfer failed: {}", e);
// General recovery - try to resume original call
let _ = client.resume_call(&caller_id).await;
}
}
Ok(())
}
§Comparison with Blind Transfer
Feature | Blind Transfer | Attended Transfer |
---|---|---|
Consultation | No | Yes |
Agent Control | Immediate | Full control |
Success Rate | Lower | Higher |
User Experience | Basic | Professional |
Call Setup | Single call | Two calls |
§Side Effects
- The primary call is placed on hold during the process
- Call metadata is updated with transfer type “attended”
- Media events are triggered for transfer completion
- The consultation call is automatically terminated
- The transferring agent is removed from both calls
§Best Practices
- Always verify both calls exist before attempting transfer
- Check call capabilities to ensure transfer is possible
- Implement error recovery to handle failed transfers gracefully
- Inform the customer when placing them on hold for consultation
- Have a fallback plan if the transfer target is unavailable
Sourcepub async fn get_call_capabilities(
&self,
call_id: &CallId,
) -> ClientResult<CallCapabilities>
pub async fn get_call_capabilities( &self, call_id: &CallId, ) -> ClientResult<CallCapabilities>
Get call control capabilities for a specific call
This method returns the available call control operations for a call based on its current state. Different call states support different operations, and this method helps applications determine what actions are available before attempting them.
§Arguments
call_id
- The unique identifier of the call to query
§Returns
Returns a CallCapabilities
struct indicating which operations are available:
can_hold
- Whether the call can be placed on holdcan_resume
- Whether the call can be resumed from holdcan_transfer
- Whether the call can be transferredcan_send_dtmf
- Whether DTMF digits can be sentcan_mute
- Whether the call can be mutedcan_hangup
- Whether the call can be terminated
§Errors
ClientError::CallNotFound
- If no call exists with the given ID
§Capability Matrix by Call State
State | Hold | Resume | Transfer | DTMF | Mute | Hangup |
---|---|---|---|---|---|---|
Connected | ✅ | ⚡ | ✅ | ✅ | ✅ | ✅ |
Ringing | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Initiating | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Proceeding | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Terminated | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
⚡ = Available only if call is currently on hold
§Examples
§Basic Capability Check
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn check_capabilities() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5080".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
match client.get_call_capabilities(&call_id).await {
Ok(capabilities) => {
println!("Call capabilities:");
println!(" Hold: {}", capabilities.can_hold);
println!(" Resume: {}", capabilities.can_resume);
println!(" Transfer: {}", capabilities.can_transfer);
println!(" DTMF: {}", capabilities.can_send_dtmf);
println!(" Mute: {}", capabilities.can_mute);
println!(" Hangup: {}", capabilities.can_hangup);
}
Err(e) => {
println!("Failed to get capabilities: {}", e);
}
}
Ok(())
}
§Conditional Operation Execution
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn conditional_operations() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5081".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
if let Ok(caps) = client.get_call_capabilities(&call_id).await {
// Only attempt operations that are available
if caps.can_hold {
println!("✅ Hold operation available");
// client.hold_call(&call_id).await?;
} else {
println!("❌ Cannot hold call in current state");
}
if caps.can_send_dtmf {
println!("✅ DTMF available - can send digits");
// client.send_dtmf(&call_id, "123").await?;
}
if caps.can_transfer {
println!("✅ Transfer available");
// client.transfer_call(&call_id, "sip:target@example.com").await?;
}
}
Ok(())
}
§Dynamic UI Updates
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
async fn update_ui_based_on_capabilities() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5082".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
if let Ok(capabilities) = client.get_call_capabilities(&call_id).await {
// Simulate UI button states
let buttons = vec![
("Hold", capabilities.can_hold),
("Resume", capabilities.can_resume),
("Transfer", capabilities.can_transfer),
("DTMF", capabilities.can_send_dtmf),
("Mute", capabilities.can_mute),
("Hangup", capabilities.can_hangup),
];
println!("UI Button States:");
for (button_name, enabled) in buttons {
let status = if enabled { "ENABLED" } else { "DISABLED" };
println!(" [{}] {}", status, button_name);
}
// Special logic for hold/resume button
if capabilities.can_hold && !capabilities.can_resume {
println!("💡 Show 'Hold' button");
} else if !capabilities.can_hold && capabilities.can_resume {
println!("💡 Show 'Resume' button");
}
}
Ok(())
}
§Capability Monitoring
use rvoip_client_core::{ClientManager, ClientConfig, CallId};
use tokio::time::{sleep, Duration};
async fn monitor_capability_changes() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5083".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let call_id = CallId::new_v4();
// Monitor capabilities over time (e.g., during state changes)
for i in 0..3 {
if let Ok(caps) = client.get_call_capabilities(&call_id).await {
println!("Check {}: Hold={}, Resume={}, Transfer={}",
i + 1, caps.can_hold, caps.can_resume, caps.can_transfer);
}
sleep(Duration::from_millis(50)).await;
}
Ok(())
}
§Implementation Notes
The capabilities are determined based on the call’s current state:
- Connected calls have the most capabilities available
- Ringing calls can only be answered or rejected (hangup)
- Initiating calls can only be cancelled (hangup)
- Terminated calls have no available operations
The can_resume
capability is dynamically determined by checking if
the call is currently on hold using the call’s metadata.
§Best Practices
- Always check capabilities before attempting operations
- Update UI dynamically based on capability changes
- Handle capability changes during call state transitions
- Provide user feedback when operations are not available
- Cache capabilities briefly to avoid excessive queries
Source§impl ClientManager
impl ClientManager
Sourcepub async fn new(config: ClientConfig) -> ClientResult<Arc<Self>>
pub async fn new(config: ClientConfig) -> ClientResult<Arc<Self>>
Create a new client manager with the given configuration
This method initializes a new ClientManager
instance with the provided
configuration. It sets up the underlying session coordinator, event system,
call mapping structures, and media configuration.
§Arguments
config
- The client configuration specifying SIP addresses, media settings, codec preferences, and other operational parameters
§Returns
Returns an Arc<ClientManager>
wrapped in a ClientResult
. The Arc allows
the manager to be shared across multiple async tasks safely.
§Errors
ClientError::InternalError
- If the session coordinator cannot be created due to invalid configuration or system resource constraints
§Examples
§Basic Client Creation
use rvoip_client_core::{ClientManager, ClientConfig};
async fn create_basic_client() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5060".parse()?);
let client = ClientManager::new(config).await?;
println!("✅ Client created successfully");
// Client is ready but not started yet
assert!(!client.is_running().await);
Ok(())
}
§Client with Custom Media Configuration
use rvoip_client_core::{ClientManager, ClientConfig, MediaConfig, MediaPreset};
async fn create_custom_media_client() -> Result<(), Box<dyn std::error::Error>> {
use rvoip_client_core::client::config::MediaPreset;
let mut media_config = MediaConfig::from_preset(MediaPreset::VoiceOptimized);
media_config.echo_cancellation = true;
media_config.noise_suppression = true;
media_config.rtp_port_start = 10000;
media_config.rtp_port_end = 20000;
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5061".parse()?)
.with_media(media_config);
let client = ClientManager::new(config).await?;
// Verify media configuration was applied
let applied_config = client.get_media_config();
assert!(applied_config.echo_cancellation);
assert!(applied_config.noise_suppression);
assert_eq!(applied_config.rtp_port_start, 10000);
assert_eq!(applied_config.rtp_port_end, 20000);
println!("✅ Custom media client created");
Ok(())
}
§Enterprise Client Setup
use rvoip_client_core::{ClientManager, ClientConfig, MediaConfig};
async fn create_enterprise_client() -> Result<(), Box<dyn std::error::Error>> {
use rvoip_client_core::client::config::MediaPreset;
let mut media_config = MediaConfig::from_preset(MediaPreset::Secure);
media_config.max_bandwidth_kbps = Some(128); // 128 kbps max
media_config.preferred_ptime = Some(20); // 20ms packet time
let config = ClientConfig::new()
.with_sip_addr("0.0.0.0:5060".parse()?) // Bind to all interfaces
.with_media_addr("0.0.0.0:0".parse()?) // Dynamic media port
.with_media(media_config);
let client = ClientManager::new(config).await?;
println!("✅ Enterprise client ready for production");
Ok(())
}
§Error Handling
use rvoip_client_core::{ClientManager, ClientConfig, ClientError};
async fn handle_creation_errors() -> Result<(), Box<dyn std::error::Error>> {
// Try to create client with potentially problematic config
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5062".parse()?);
match ClientManager::new(config).await {
Ok(client) => {
println!("✅ Client created successfully");
// Use client...
}
Err(ClientError::InternalError { message }) => {
println!("❌ Failed to create client: {}", message);
// Handle error (retry, use different config, etc.)
}
Err(e) => {
println!("❌ Unexpected error: {}", e);
}
}
Ok(())
}
§Multi-Client Architecture
use rvoip_client_core::{ClientManager, ClientConfig};
use std::sync::Arc;
async fn multi_client_setup() -> Result<(), Box<dyn std::error::Error>> {
// Create multiple clients for different purposes
let client1_config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5060".parse()?);
let client1 = ClientManager::new(client1_config).await?;
let client2_config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5061".parse()?);
let client2 = ClientManager::new(client2_config).await?;
// Clients can be shared across tasks
let client1_clone = Arc::clone(&client1);
let task1 = tokio::spawn(async move {
// Use client1_clone in this task
println!("Task 1 using client on port 5060");
});
let client2_clone = Arc::clone(&client2);
let task2 = tokio::spawn(async move {
// Use client2_clone in this task
println!("Task 2 using client on port 5061");
});
// Wait for tasks to complete
let _ = tokio::try_join!(task1, task2)?;
println!("✅ Multi-client setup complete");
Ok(())
}
§Implementation Notes
The constructor performs several key initialization steps:
- Session Coordinator Setup: Creates the underlying session-core coordinator with media preferences and SIP configuration
- Event System: Initializes broadcast channels for real-time events
- Call Mapping: Sets up concurrent data structures for call tracking
- Media Configuration: Applies codec preferences and quality settings
- Handler Registration: Registers the call handler for SIP events
The returned Arc<ClientManager>
enables safe sharing across async tasks
and ensures proper cleanup through RAII patterns.
Sourcepub async fn set_event_handler(&self, handler: Arc<dyn ClientEventHandler>)
pub async fn set_event_handler(&self, handler: Arc<dyn ClientEventHandler>)
Set the event handler for client events
This method registers an application-provided event handler that will receive notifications for all client events including registration changes, call status updates, and media quality notifications. The handler is called asynchronously and should not block for extended periods.
§Arguments
handler
- An implementation of theClientEventHandler
trait wrapped in anArc
for thread-safe sharing across the event system
§Examples
§Basic Event Handler
use rvoip_client_core::{
ClientManager, ClientConfig, ClientEventHandler,
events::{CallStatusInfo, RegistrationStatusInfo, MediaEventInfo, IncomingCallInfo, CallAction}
};
use async_trait::async_trait;
use std::sync::Arc;
struct MyEventHandler;
#[async_trait]
impl ClientEventHandler for MyEventHandler {
async fn on_incoming_call(&self, _info: IncomingCallInfo) -> CallAction {
CallAction::Accept
}
async fn on_call_state_changed(&self, info: CallStatusInfo) {
println!("📞 Call {} changed to {:?}", info.call_id, info.new_state);
}
async fn on_registration_status_changed(&self, info: RegistrationStatusInfo) {
println!("📋 Registration {} changed to {:?}", info.user_uri, info.status);
}
async fn on_media_event(&self, info: MediaEventInfo) {
println!("🎵 Media event for call {}: {:?}", info.call_id, info.event_type);
}
}
async fn setup_event_handler() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5063".parse()?);
let client = ClientManager::new(config).await?;
// Register our event handler
let handler = Arc::new(MyEventHandler);
client.set_event_handler(handler).await;
client.start().await?;
println!("✅ Event handler registered and client started");
client.stop().await?;
Ok(())
}
§Stateful Event Handler
use rvoip_client_core::{
ClientManager, ClientConfig, ClientEventHandler,
events::{CallStatusInfo, RegistrationStatusInfo, MediaEventInfo, IncomingCallInfo, CallAction}
};
use async_trait::async_trait;
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
struct StatefulEventHandler {
call_states: Mutex<HashMap<String, String>>,
event_count: Mutex<u64>,
}
impl StatefulEventHandler {
fn new() -> Self {
Self {
call_states: Mutex::new(HashMap::new()),
event_count: Mutex::new(0),
}
}
}
#[async_trait]
impl ClientEventHandler for StatefulEventHandler {
async fn on_incoming_call(&self, _info: IncomingCallInfo) -> CallAction {
CallAction::Accept
}
async fn on_call_state_changed(&self, info: CallStatusInfo) {
// Update state tracking
let mut states = self.call_states.lock().unwrap();
states.insert(info.call_id.to_string(), format!("{:?}", info.new_state));
let mut count = self.event_count.lock().unwrap();
*count += 1;
println!("📞 Call event #{}: {} -> {:?}", *count, info.call_id, info.new_state);
}
async fn on_registration_status_changed(&self, info: RegistrationStatusInfo) {
println!("📋 Registration: {} -> {:?}", info.user_uri, info.status);
}
async fn on_media_event(&self, info: MediaEventInfo) {
println!("🎵 Media: Call {} -> {:?}", info.call_id, info.event_type);
}
}
async fn stateful_handler() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5064".parse()?);
let client = ClientManager::new(config).await?;
// Create stateful handler
let handler = Arc::new(StatefulEventHandler::new());
client.set_event_handler(handler.clone()).await;
client.start().await?;
// Handler is now tracking events
println!("✅ Stateful event handler active");
client.stop().await?;
Ok(())
}
§Logging Event Handler
use rvoip_client_core::{
ClientManager, ClientConfig, ClientEventHandler,
events::{CallStatusInfo, RegistrationStatusInfo, MediaEventInfo, IncomingCallInfo, CallAction}
};
use async_trait::async_trait;
use std::sync::Arc;
use chrono::Utc;
struct LoggingEventHandler {
component_name: String,
}
impl LoggingEventHandler {
fn new(name: &str) -> Self {
Self {
component_name: name.to_string(),
}
}
}
#[async_trait]
impl ClientEventHandler for LoggingEventHandler {
async fn on_incoming_call(&self, _info: IncomingCallInfo) -> CallAction {
CallAction::Accept
}
async fn on_call_state_changed(&self, info: CallStatusInfo) {
tracing::info!(
component = %self.component_name,
call_id = %info.call_id,
previous_state = ?info.previous_state,
new_state = ?info.new_state,
timestamp = %info.timestamp,
"Call status changed"
);
}
async fn on_registration_status_changed(&self, info: RegistrationStatusInfo) {
tracing::info!(
component = %self.component_name,
user_uri = %info.user_uri,
status = ?info.status,
server = %info.server_uri,
"Registration status changed"
);
}
async fn on_media_event(&self, info: MediaEventInfo) {
tracing::debug!(
component = %self.component_name,
call_id = %info.call_id,
event_type = ?info.event_type,
"Media event occurred"
);
}
}
async fn logging_handler() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5065".parse()?);
let client = ClientManager::new(config).await?;
// Create logging handler
let handler = Arc::new(LoggingEventHandler::new("MyVoIPApp"));
client.set_event_handler(handler).await;
client.start().await?;
println!("✅ Logging event handler registered");
client.stop().await?;
Ok(())
}
§Event Handler Replacement
use rvoip_client_core::{
ClientManager, ClientConfig, ClientEventHandler,
events::{CallStatusInfo, RegistrationStatusInfo, MediaEventInfo, IncomingCallInfo, CallAction}
};
use async_trait::async_trait;
use std::sync::Arc;
struct Handler1;
struct Handler2;
#[async_trait]
impl ClientEventHandler for Handler1 {
async fn on_incoming_call(&self, _info: IncomingCallInfo) -> CallAction {
CallAction::Accept
}
async fn on_call_state_changed(&self, info: CallStatusInfo) {
println!("Handler1: Call {} -> {:?}", info.call_id, info.new_state);
}
async fn on_registration_status_changed(&self, _info: RegistrationStatusInfo) {}
async fn on_media_event(&self, _info: MediaEventInfo) {}
}
#[async_trait]
impl ClientEventHandler for Handler2 {
async fn on_incoming_call(&self, _info: IncomingCallInfo) -> CallAction {
CallAction::Accept
}
async fn on_call_state_changed(&self, info: CallStatusInfo) {
println!("Handler2: Call {} -> {:?}", info.call_id, info.new_state);
}
async fn on_registration_status_changed(&self, _info: RegistrationStatusInfo) {}
async fn on_media_event(&self, _info: MediaEventInfo) {}
}
async fn handler_replacement() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5066".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
// Set initial handler
let handler1 = Arc::new(Handler1);
client.set_event_handler(handler1).await;
println!("✅ Handler1 registered");
// Replace with different handler
let handler2 = Arc::new(Handler2);
client.set_event_handler(handler2).await;
println!("✅ Handler2 replaced Handler1");
client.stop().await?;
Ok(())
}
§Implementation Notes
- Thread Safety: The handler is stored in an Arc
for safe concurrent access - Async Execution: All handler methods are called asynchronously
- No Blocking: Handlers should avoid blocking operations to prevent event queue backup
- Error Handling: Handler errors are logged but don’t affect client operation
- Replacement: Setting a new handler replaces the previous one
§Best Practices
- Keep handlers lightweight - Avoid heavy computation in event callbacks
- Use async patterns - Leverage tokio for concurrent event processing
- Handle errors gracefully - Don’t panic in event handlers
- Consider batching - For high-frequency events, consider batching updates
- State management - Use appropriate synchronization for handler state
Sourcepub async fn start(&self) -> ClientResult<()>
pub async fn start(&self) -> ClientResult<()>
Start the client manager
This method starts the client manager, initializing the underlying SIP transport, binding to network addresses, and beginning event processing. The client must be started before it can handle registrations, calls, or other SIP operations.
§Returns
Returns Ok(())
if the client started successfully.
§Errors
ClientError::InternalError
- If the session coordinator fails to start (e.g., port already in use, network unavailable)
§Examples
§Basic Start/Stop Cycle
use rvoip_client_core::{ClientManager, ClientConfig};
async fn start_stop_cycle() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5067".parse()?);
let client = ClientManager::new(config).await?;
// Initially not running
assert!(!client.is_running().await);
// Start the client
client.start().await?;
assert!(client.is_running().await);
println!("✅ Client started successfully");
// Stop the client
client.stop().await?;
assert!(!client.is_running().await);
println!("✅ Client stopped successfully");
Ok(())
}
§Error Handling on Start
use rvoip_client_core::{ClientManager, ClientConfig, ClientError};
async fn handle_start_errors() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5068".parse()?);
let client = ClientManager::new(config).await?;
match client.start().await {
Ok(()) => {
println!("✅ Client started successfully");
client.stop().await?;
}
Err(ClientError::InternalError { message }) => {
println!("❌ Failed to start client: {}", message);
// Handle the error (retry with different port, etc.)
}
Err(e) => {
println!("❌ Unexpected error: {}", e);
}
}
Ok(())
}
§Multiple Start Attempts
use rvoip_client_core::{ClientManager, ClientConfig};
async fn multiple_start_safe() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5069".parse()?);
let client = ClientManager::new(config).await?;
// Start the client
client.start().await?;
println!("✅ First start successful");
// Multiple starts should be safe (idempotent)
client.start().await?;
println!("✅ Second start (should be no-op)");
assert!(client.is_running().await);
client.stop().await?;
Ok(())
}
Sourcepub async fn stop(&self) -> ClientResult<()>
pub async fn stop(&self) -> ClientResult<()>
Stop the client manager
This method gracefully shuts down the client manager, terminating all active calls, cleaning up network resources, and stopping event processing. Any active registrations will be automatically unregistered.
§Returns
Returns Ok(())
if the client stopped successfully.
§Errors
ClientError::InternalError
- If the session coordinator fails to stop cleanly
§Examples
§Graceful Shutdown
use rvoip_client_core::{ClientManager, ClientConfig};
async fn graceful_shutdown() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5070".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
// Do some work...
println!("Client running...");
// Graceful shutdown
client.stop().await?;
assert!(!client.is_running().await);
println!("✅ Client stopped gracefully");
Ok(())
}
§Error Handling on Stop
use rvoip_client_core::{ClientManager, ClientConfig, ClientError};
async fn handle_stop_errors() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5071".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
match client.stop().await {
Ok(()) => {
println!("✅ Client stopped successfully");
}
Err(ClientError::InternalError { message }) => {
println!("⚠️ Stop had issues: {}", message);
// Resources may still be partially cleaned up
}
Err(e) => {
println!("❌ Unexpected error during stop: {}", e);
}
}
Ok(())
}
§Multiple Stop Attempts
use rvoip_client_core::{ClientManager, ClientConfig};
async fn multiple_stop_safe() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5072".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
// Stop the client
client.stop().await?;
println!("✅ First stop successful");
// Multiple stops should be safe (idempotent)
client.stop().await?;
println!("✅ Second stop (should be no-op)");
assert!(!client.is_running().await);
Ok(())
}
Sourcepub async fn register(&self, config: RegistrationConfig) -> ClientResult<Uuid>
pub async fn register(&self, config: RegistrationConfig) -> ClientResult<Uuid>
Register with a SIP server
This method registers the client with a SIP server using the REGISTER method. Registration allows the client to receive incoming calls and establishes its presence on the SIP network. The method handles authentication challenges automatically and includes retry logic for network issues.
§Arguments
config
- Registration configuration including server URI, user credentials, and expiration settings
§Returns
Returns a Uuid
that uniquely identifies this registration for future operations.
§Errors
ClientError::AuthenticationFailed
- Invalid credentials or auth challenge failedClientError::RegistrationFailed
- Server rejected registration (403, etc.)ClientError::NetworkError
- Network timeout or connectivity issues
§Examples
§Basic Registration
use rvoip_client_core::{ClientManager, ClientConfig, RegistrationConfig};
async fn basic_registration() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5073".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let reg_config = RegistrationConfig {
server_uri: "sip:sip.example.com:5060".to_string(),
from_uri: "sip:alice@example.com".to_string(),
contact_uri: "sip:alice@127.0.0.1:5073".to_string(),
expires: 3600,
username: None,
password: None,
realm: None,
};
let reg_id = client.register(reg_config).await?;
println!("✅ Registered with ID: {}", reg_id);
client.unregister(reg_id).await?;
client.stop().await?;
Ok(())
}
§Registration with Authentication
use rvoip_client_core::{ClientManager, ClientConfig, RegistrationConfig};
async fn authenticated_registration() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5074".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let reg_config = RegistrationConfig {
server_uri: "sip:pbx.company.com".to_string(),
from_uri: "sip:user@company.com".to_string(),
contact_uri: "sip:user@127.0.0.1:5074".to_string(),
expires: 1800, // 30 minutes
username: Some("user".to_string()),
password: Some("password123".to_string()),
realm: Some("company.com".to_string()),
};
match client.register(reg_config).await {
Ok(reg_id) => {
println!("✅ Authenticated registration successful: {}", reg_id);
client.unregister(reg_id).await?;
}
Err(e) => {
println!("❌ Registration failed: {}", e);
}
}
client.stop().await?;
Ok(())
}
§Multiple Registrations
use rvoip_client_core::{ClientManager, ClientConfig, RegistrationConfig};
async fn multiple_registrations() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5075".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
// Register with multiple servers
let reg1_config = RegistrationConfig {
server_uri: "sip:server1.com".to_string(),
from_uri: "sip:alice@server1.com".to_string(),
contact_uri: "sip:alice@127.0.0.1:5075".to_string(),
expires: 3600,
username: None,
password: None,
realm: None,
};
let reg2_config = RegistrationConfig {
server_uri: "sip:server2.com".to_string(),
from_uri: "sip:alice@server2.com".to_string(),
contact_uri: "sip:alice@127.0.0.1:5075".to_string(),
expires: 3600,
username: None,
password: None,
realm: None,
};
let reg1_id = client.register(reg1_config).await?;
let reg2_id = client.register(reg2_config).await?;
println!("✅ Registered with {} servers", 2);
// Check all registrations
let all_regs = client.get_all_registrations().await;
assert_eq!(all_regs.len(), 2);
// Clean up
client.unregister(reg1_id).await?;
client.unregister(reg2_id).await?;
client.stop().await?;
Ok(())
}
Sourcepub async fn unregister(&self, reg_id: Uuid) -> ClientResult<()>
pub async fn unregister(&self, reg_id: Uuid) -> ClientResult<()>
Unregister from a SIP server
This method removes a registration from a SIP server by sending a REGISTER request with expires=0. This gracefully removes the client’s presence from the server and stops receiving incoming calls for that registration.
§Arguments
reg_id
- The UUID of the registration to remove
§Returns
Returns Ok(())
if the unregistration was successful.
§Errors
ClientError::InvalidConfiguration
- If the registration ID is not foundClientError::InternalError
- If the unregistration request fails
§Examples
use rvoip_client_core::{ClientManager, ClientConfig, RegistrationConfig};
async fn unregister_example() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5079".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let reg_config = RegistrationConfig {
server_uri: "sip:server.example.com".to_string(),
from_uri: "sip:alice@example.com".to_string(),
contact_uri: "sip:alice@127.0.0.1:5079".to_string(),
expires: 3600,
username: None,
password: None,
realm: None,
};
let reg_id = client.register(reg_config).await?;
println!("✅ Registered with ID: {}", reg_id);
// Unregister
client.unregister(reg_id).await?;
println!("✅ Successfully unregistered");
client.stop().await?;
Ok(())
}
Sourcepub async fn get_registration(
&self,
reg_id: Uuid,
) -> ClientResult<RegistrationInfo>
pub async fn get_registration( &self, reg_id: Uuid, ) -> ClientResult<RegistrationInfo>
Get registration information
Retrieves detailed information about a specific registration including status, timestamps, and server details.
§Arguments
reg_id
- The UUID of the registration to retrieve
§Returns
Returns the RegistrationInfo
struct containing all registration details.
§Errors
ClientError::InvalidConfiguration
- If the registration ID is not found
§Examples
use rvoip_client_core::{ClientManager, ClientConfig, RegistrationConfig};
async fn get_registration_info() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5080".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let reg_config = RegistrationConfig {
server_uri: "sip:server.example.com".to_string(),
from_uri: "sip:user@example.com".to_string(),
contact_uri: "sip:user@127.0.0.1:5080".to_string(),
expires: 1800,
username: None,
password: None,
realm: None,
};
let reg_id = client.register(reg_config).await?;
// Get registration details
let reg_info = client.get_registration(reg_id).await?;
println!("Registration status: {:?}", reg_info.status);
println!("Server: {}", reg_info.server_uri);
println!("User: {}", reg_info.from_uri);
println!("Expires: {} seconds", reg_info.expires);
client.unregister(reg_id).await?;
client.stop().await?;
Ok(())
}
Sourcepub async fn get_all_registrations(&self) -> Vec<RegistrationInfo>
pub async fn get_all_registrations(&self) -> Vec<RegistrationInfo>
Get all active registrations
Returns a list of all currently active registrations. This includes only
registrations with status Active
, filtering out expired or cancelled ones.
§Returns
Returns a Vec<RegistrationInfo>
containing all active registrations.
§Examples
use rvoip_client_core::{ClientManager, ClientConfig, RegistrationConfig};
async fn list_registrations() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5081".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
// Create multiple registrations
let reg1_config = RegistrationConfig {
server_uri: "sip:server1.com".to_string(),
from_uri: "sip:alice@server1.com".to_string(),
contact_uri: "sip:alice@127.0.0.1:5081".to_string(),
expires: 3600,
username: None,
password: None,
realm: None,
};
let reg2_config = RegistrationConfig {
server_uri: "sip:server2.com".to_string(),
from_uri: "sip:alice@server2.com".to_string(),
contact_uri: "sip:alice@127.0.0.1:5081".to_string(),
expires: 1800,
username: None,
password: None,
realm: None,
};
let _reg1_id = client.register(reg1_config).await?;
let _reg2_id = client.register(reg2_config).await?;
// List all active registrations
let active_regs = client.get_all_registrations().await;
println!("Active registrations: {}", active_regs.len());
for reg in active_regs {
println!("- {} at {}", reg.from_uri, reg.server_uri);
}
client.stop().await?;
Ok(())
}
Sourcepub async fn refresh_registration(&self, reg_id: Uuid) -> ClientResult<()>
pub async fn refresh_registration(&self, reg_id: Uuid) -> ClientResult<()>
Refresh a registration
Manually refreshes a registration by sending a new REGISTER request with the same parameters. This is useful for extending registration lifetime before expiration or after network connectivity issues.
§Arguments
reg_id
- The UUID of the registration to refresh
§Returns
Returns Ok(())
if the registration was successfully refreshed.
§Errors
ClientError::InvalidConfiguration
- If the registration ID is not foundClientError::InternalError
- If the refresh request fails
§Examples
use rvoip_client_core::{ClientManager, ClientConfig, RegistrationConfig};
async fn refresh_registration_example() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5082".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let reg_config = RegistrationConfig {
server_uri: "sip:server.example.com".to_string(),
from_uri: "sip:user@example.com".to_string(),
contact_uri: "sip:user@127.0.0.1:5082".to_string(),
expires: 300, // Short expiration for demo
username: None,
password: None,
realm: None,
};
let reg_id = client.register(reg_config).await?;
println!("✅ Initial registration completed");
// Refresh the registration
client.refresh_registration(reg_id).await?;
println!("✅ Registration refreshed successfully");
// Check registration info
if let Ok(reg_info) = client.get_registration(reg_id).await {
if let Some(refresh_time) = reg_info.refresh_time {
println!("Last refreshed: {}", refresh_time);
}
}
client.unregister(reg_id).await?;
client.stop().await?;
Ok(())
}
Sourcepub async fn clear_expired_registrations(&self)
pub async fn clear_expired_registrations(&self)
Clear expired registrations
Removes all registrations with Expired
status from the internal storage.
This is a maintenance operation that cleans up stale registration entries
and updates statistics accordingly.
§Examples
use rvoip_client_core::{ClientManager, ClientConfig};
async fn cleanup_registrations() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5083".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
// In a real application, you might have some expired registrations
// This method would clean them up
client.clear_expired_registrations().await;
println!("✅ Expired registrations cleaned up");
// Check remaining active registrations
let active_count = client.get_all_registrations().await.len();
println!("Active registrations remaining: {}", active_count);
client.stop().await?;
Ok(())
}
Sourcepub async fn register_simple(
&self,
agent_uri: &str,
server_addr: &SocketAddr,
duration: Duration,
) -> ClientResult<()>
pub async fn register_simple( &self, agent_uri: &str, server_addr: &SocketAddr, duration: Duration, ) -> ClientResult<()>
Convenience method: Register with simple parameters (for examples)
This is a simplified registration method that takes basic parameters and
constructs a complete RegistrationConfig
automatically. It’s designed
for quick testing and simple use cases.
§Arguments
agent_uri
- The SIP URI for this agent (e.g., “sip:alice@example.com”)server_addr
- The SIP server address and portduration
- How long the registration should last
§Returns
Returns Ok(())
if registration was successful.
§Examples
use rvoip_client_core::{ClientManager, ClientConfig};
use std::time::Duration;
async fn simple_register() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5084".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let server_addr = "192.168.1.100:5060".parse()?;
let duration = Duration::from_secs(3600); // 1 hour
// Simple registration
client.register_simple(
"sip:testuser@example.com",
&server_addr,
duration
).await?;
println!("✅ Simple registration completed");
// Cleanup using the simple unregister method
client.unregister_simple(
"sip:testuser@example.com",
&server_addr
).await?;
client.stop().await?;
Ok(())
}
Sourcepub async fn unregister_simple(
&self,
agent_uri: &str,
server_addr: &SocketAddr,
) -> ClientResult<()>
pub async fn unregister_simple( &self, agent_uri: &str, server_addr: &SocketAddr, ) -> ClientResult<()>
Convenience method: Unregister with simple parameters (for examples)
This method finds and unregisters a registration that matches the given
agent URI and server address. It’s the counterpart to register_simple()
and provides an easy way to clean up simple registrations.
§Arguments
agent_uri
- The SIP URI that was registeredserver_addr
- The SIP server address that was used
§Returns
Returns Ok(())
if unregistration was successful.
§Errors
ClientError::InvalidConfiguration
- If no matching registration is found
§Examples
use rvoip_client_core::{ClientManager, ClientConfig};
use std::time::Duration;
async fn simple_unregister() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5085".parse()?);
let client = ClientManager::new(config).await?;
client.start().await?;
let agent_uri = "sip:testuser@example.com";
let server_addr = "192.168.1.100:5060".parse()?;
// Register first
client.register_simple(
agent_uri,
&server_addr,
Duration::from_secs(3600)
).await?;
println!("✅ Registration completed");
// Now unregister using the same parameters
client.unregister_simple(agent_uri, &server_addr).await?;
println!("✅ Unregistration completed");
// Verify no active registrations remain
let active_regs = client.get_all_registrations().await;
assert_eq!(active_regs.len(), 0);
client.stop().await?;
Ok(())
}
Sourcepub fn subscribe_events(&self) -> Receiver<ClientEvent>
pub fn subscribe_events(&self) -> Receiver<ClientEvent>
Subscribe to client events
Creates a new receiver for the client event broadcast channel. Multiple subscribers can listen to the same events simultaneously.
§Returns
Returns a broadcast::Receiver<ClientEvent>
for receiving real-time events.
§Examples
use rvoip_client_core::{ClientManager, ClientConfig, ClientEvent};
use tokio::time::{timeout, Duration};
async fn event_subscription() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5076".parse()?);
let client = ClientManager::new(config).await?;
let mut events = client.subscribe_events();
client.start().await?;
// Listen for events (with timeout for doc test)
if let Ok(Ok(event)) = timeout(Duration::from_millis(10), events.recv()).await {
match event {
ClientEvent::CallStateChanged { info, .. } => {
println!("Call event: {:?}", info.new_state);
}
ClientEvent::RegistrationStatusChanged { info, .. } => {
println!("Registration event: {:?}", info.status);
}
ClientEvent::MediaEvent { info, .. } => {
println!("Media event for call: {}", info.call_id);
}
ClientEvent::IncomingCall { .. } |
ClientEvent::ClientError { .. } |
ClientEvent::NetworkEvent { .. } => {
// Handle other events as needed
}
}
}
client.stop().await?;
Ok(())
}
Sourcepub async fn is_running(&self) -> bool
pub async fn is_running(&self) -> bool
Check if the client is running
Returns the current running state of the client manager. A client must be started before it can handle SIP operations.
§Returns
Returns true
if the client is currently running, false
otherwise.
§Examples
use rvoip_client_core::{ClientManager, ClientConfig};
async fn check_running_state() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5077".parse()?);
let client = ClientManager::new(config).await?;
// Initially not running
assert!(!client.is_running().await);
// Start and check
client.start().await?;
assert!(client.is_running().await);
// Stop and check
client.stop().await?;
assert!(!client.is_running().await);
Ok(())
}
Sourcepub fn get_media_config(&self) -> &MediaConfig
pub fn get_media_config(&self) -> &MediaConfig
Get the media configuration
Returns a reference to the current media configuration being used by the client. This includes codec preferences, quality settings, and network parameters.
§Returns
Returns a reference to the MediaConfig
used during client initialization.
§Examples
use rvoip_client_core::{ClientManager, ClientConfig, MediaConfig, MediaPreset};
async fn check_media_config() -> Result<(), Box<dyn std::error::Error>> {
use rvoip_client_core::client::config::MediaPreset;
let mut media_config = MediaConfig::from_preset(MediaPreset::VoiceOptimized);
media_config.echo_cancellation = true;
media_config.max_bandwidth_kbps = Some(128);
let config = ClientConfig::new()
.with_sip_addr("127.0.0.1:5078".parse()?)
.with_media(media_config);
let client = ClientManager::new(config).await?;
// Check applied configuration
let applied_config = client.get_media_config();
assert!(applied_config.echo_cancellation);
assert_eq!(applied_config.max_bandwidth_kbps, Some(128));
println!("Echo cancellation: {}", applied_config.echo_cancellation);
println!("Noise suppression: {}", applied_config.noise_suppression);
println!("RTP port range: {}-{}",
applied_config.rtp_port_start, applied_config.rtp_port_end);
Ok(())
}
Source§impl ClientManager
Media operations implementation for ClientManager
impl ClientManager
Media operations implementation for ClientManager
Sourcepub async fn set_microphone_mute(
&self,
call_id: &CallId,
muted: bool,
) -> ClientResult<()>
pub async fn set_microphone_mute( &self, call_id: &CallId, muted: bool, ) -> ClientResult<()>
Enhanced microphone mute/unmute with proper session-core integration
Controls the microphone mute state for a specific call. When muted, the local audio transmission is stopped, preventing the remote party from hearing your voice. This operation validates the call state and emits appropriate media events.
§Arguments
call_id
- The unique identifier of the call to mute/unmutemuted
-true
to mute the microphone,false
to unmute
§Returns
Returns Ok(())
if the operation succeeds, or a ClientError
if:
- The call is not found
- The call is in an invalid state (terminated, failed, cancelled)
- The underlying media session fails to change mute state
§Examples
// Basic usage - mute the microphone
let call_id: CallId = Uuid::new_v4();
println!("Would mute microphone for call {}", call_id);
// Toggle functionality
let current_state = false; // Simulated current state
let new_state = !current_state;
println!("Would toggle microphone from {} to {}", current_state, new_state);
// Privacy mode example
let call_id: CallId = Uuid::new_v4();
println!("Enabling privacy mode for call {}", call_id);
println!("Microphone would be muted");
§Side Effects
- Updates call metadata with mute state and timestamp
- Emits a
MediaEventType::MicrophoneStateChanged
event - Calls session-core to actually control audio transmission
Sourcepub async fn set_speaker_mute(
&self,
call_id: &CallId,
muted: bool,
) -> ClientResult<()>
pub async fn set_speaker_mute( &self, call_id: &CallId, muted: bool, ) -> ClientResult<()>
Enhanced speaker mute/unmute with event emission
Controls the speaker (audio output) mute state for a specific call. When speaker is muted, you won’t hear audio from the remote party. This is typically handled client-side as it controls local audio playback rather than network transmission.
§Arguments
call_id
- The unique identifier of the call to mute/unmutemuted
-true
to mute the speaker,false
to unmute
§Returns
Returns Ok(())
if the operation succeeds, or a ClientError
if the call is not found.
§Examples
// Basic speaker control
let call_id: CallId = Uuid::new_v4();
println!("Would mute speaker for call {}", call_id);
// Unmute speaker
println!("Would unmute speaker for call {}", call_id);
// Privacy mode: mute both microphone and speaker
let call_id: CallId = Uuid::new_v4();
println!("Enabling full privacy mode for call {}", call_id);
println!("Would mute microphone and speaker");
§Implementation Notes
This function handles client-side audio output control and does not affect
network RTP streams. The mute state is stored in call metadata and can be
retrieved using get_speaker_mute_state()
.
§Side Effects
- Updates call metadata with speaker mute state and timestamp
- Emits a
MediaEventType::SpeakerStateChanged
event
Sourcepub async fn get_call_media_info(
&self,
call_id: &CallId,
) -> ClientResult<CallMediaInfo>
pub async fn get_call_media_info( &self, call_id: &CallId, ) -> ClientResult<CallMediaInfo>
Get comprehensive media information for a call using session-core
Retrieves detailed media information about an active call, including SDP negotiation details, RTP port assignments, codec information, and current media state. This is useful for monitoring call quality, debugging media issues, and displaying technical call information to users or administrators.
§Arguments
call_id
- The unique identifier of the call to query
§Returns
Returns a CallMediaInfo
struct containing:
- Local and remote SDP descriptions
- RTP port assignments (local and remote)
- Negotiated audio codec
- Current mute and hold states
- Audio direction (send/receive/both/inactive)
- Quality metrics (if available)
Returns ClientError::CallNotFound
if the call doesn’t exist, or
ClientError::InternalError
if media information cannot be retrieved.
§Examples
// Basic media info retrieval
let call_id: CallId = Uuid::new_v4();
println!("Would get media info for call {}", call_id);
// Check audio direction
let audio_direction = AudioDirection::SendReceive;
match audio_direction {
AudioDirection::SendReceive => println!("Full duplex audio"),
AudioDirection::SendOnly => println!("Send-only (e.g., hold)"),
AudioDirection::ReceiveOnly => println!("Receive-only"),
AudioDirection::Inactive => println!("No audio flow"),
}
// Diagnostic information
let call_id: CallId = Uuid::new_v4();
println!("Getting diagnostic info for call {}", call_id);
println!("This would include SDP, ports, codec, and states");
Sourcepub async fn get_microphone_mute_state(
&self,
call_id: &CallId,
) -> ClientResult<bool>
pub async fn get_microphone_mute_state( &self, call_id: &CallId, ) -> ClientResult<bool>
Get the current microphone mute state for a call
Retrieves the current mute state of the microphone for the specified call. This state is maintained in the call’s metadata and reflects whether local audio transmission is currently enabled (false) or disabled (true).
§Arguments
call_id
- The unique identifier of the call to query
§Returns
Returns Ok(true)
if the microphone is muted, Ok(false)
if unmuted,
or ClientError::CallNotFound
if the call doesn’t exist.
§Examples
// Check microphone state
let call_id: CallId = Uuid::new_v4();
println!("Would check microphone mute state for call {}", call_id);
// Conditional logic based on mute state
let is_muted = false; // Simulated state
if is_muted {
println!("Microphone is currently muted");
} else {
println!("Microphone is active");
}
// UI indicator logic
let call_id: CallId = Uuid::new_v4();
let mute_state = false; // Would get actual state
let indicator = if mute_state { "🔇" } else { "🔊" };
println!("Microphone status for call {}: {}", call_id, indicator);
Sourcepub async fn get_speaker_mute_state(
&self,
call_id: &CallId,
) -> ClientResult<bool>
pub async fn get_speaker_mute_state( &self, call_id: &CallId, ) -> ClientResult<bool>
Get the current speaker mute state for a call
Retrieves the current mute state of the speaker (audio output) for the specified call. This state is maintained in the call’s metadata and reflects whether remote audio playback is currently enabled (false) or disabled (true).
§Arguments
call_id
- The unique identifier of the call to query
§Returns
Returns Ok(true)
if the speaker is muted, Ok(false)
if unmuted,
or ClientError::CallNotFound
if the call doesn’t exist.
§Examples
// Check speaker state
let call_id: CallId = Uuid::new_v4();
println!("Would check speaker mute state for call {}", call_id);
// Audio feedback prevention
let speaker_muted = true; // Simulated state
if speaker_muted {
println!("Safe to use speakerphone mode");
} else {
println!("May cause audio feedback");
}
// Privacy status check
let call_id: CallId = Uuid::new_v4();
let mic_muted = true;
let speaker_muted = true;
if mic_muted && speaker_muted {
println!("Call {} is in full privacy mode", call_id);
}
Sourcepub async fn get_supported_audio_codecs(&self) -> Vec<AudioCodecInfo>
pub async fn get_supported_audio_codecs(&self) -> Vec<AudioCodecInfo>
Get supported audio codecs with comprehensive information
Returns a complete list of audio codecs supported by this client implementation.
This is an alias for get_available_codecs()
provided for API consistency.
Each codec includes detailed information about capabilities, quality ratings,
and technical specifications.
§Returns
A vector of AudioCodecInfo
structures containing:
- Codec name and payload type
- Sample rate and channel configuration
- Quality rating (1-5 scale)
- Human-readable description
§Examples
// Simulate codec information
let codec = AudioCodecInfo {
name: "OPUS".to_string(),
payload_type: 111,
clock_rate: 48000,
channels: 2,
description: "High quality codec".to_string(),
quality_rating: 5,
};
println!("Codec: {} (Quality: {}/5)", codec.name, codec.quality_rating);
// Filter high-quality codecs
let quality_threshold = 4;
println!("Looking for codecs with quality >= {}", quality_threshold);
println!("Would filter codec list by quality rating");
Get supported audio codecs (alias for get_available_codecs)
Sourcepub async fn get_available_codecs(&self) -> Vec<AudioCodecInfo>
pub async fn get_available_codecs(&self) -> Vec<AudioCodecInfo>
Get list of available audio codecs with detailed information
Returns a comprehensive list of audio codecs supported by the client, including payload types, sample rates, quality ratings, and descriptions. This information can be used for codec selection, capability negotiation, and display in user interfaces.
§Returns
A vector of AudioCodecInfo
structures, each containing:
- Codec name and standard designation
- RTP payload type number
- Audio sampling rate and channel count
- Human-readable description
- Quality rating (1-5 scale, 5 being highest)
§Examples
// Example codec information
let codecs = vec![
AudioCodecInfo {
name: "OPUS".to_string(),
payload_type: 111,
clock_rate: 48000,
channels: 2,
description: "High quality codec".to_string(),
quality_rating: 5,
},
AudioCodecInfo {
name: "G722".to_string(),
payload_type: 9,
clock_rate: 8000,
channels: 1,
description: "Wideband audio".to_string(),
quality_rating: 4,
}
];
for codec in &codecs {
println!("Codec: {} (PT: {}, Rate: {}Hz, Quality: {}/5)",
codec.name, codec.payload_type, codec.clock_rate, codec.quality_rating);
println!(" Description: {}", codec.description);
}
// Find high-quality codecs
let high_quality: Vec<_> = codecs
.into_iter()
.filter(|c| c.quality_rating >= 4)
.collect();
println!("Found {} high-quality codecs", high_quality.len());
Sourcepub async fn get_call_codec_info(
&self,
call_id: &CallId,
) -> ClientResult<Option<AudioCodecInfo>>
pub async fn get_call_codec_info( &self, call_id: &CallId, ) -> ClientResult<Option<AudioCodecInfo>>
Get codec information for a specific active call
Retrieves detailed information about the audio codec currently being used
for the specified call. This includes technical specifications, quality ratings,
and capabilities of the negotiated codec. Returns None
if no codec has been
negotiated yet or if the call doesn’t have active media.
§Arguments
call_id
- The unique identifier of the call to query
§Returns
Returns Ok(Some(AudioCodecInfo))
with codec details if available,
Ok(None)
if no codec is negotiated, or ClientError
if the call is not found
or media information cannot be retrieved.
§Examples
// Check call codec
let call_id: CallId = Uuid::new_v4();
println!("Would get codec info for call {}", call_id);
// Example codec info handling
let codec_info = Some(AudioCodecInfo {
name: "G722".to_string(),
payload_type: 9,
clock_rate: 8000,
channels: 1,
description: "Wideband audio".to_string(),
quality_rating: 4,
});
match codec_info {
Some(codec) => println!("Using codec: {} ({})", codec.name, codec.description),
None => println!("No codec negotiated yet"),
}
// Quality assessment
let call_id: CallId = Uuid::new_v4();
println!("Assessing call quality for call {}", call_id);
let quality_rating = 4; // Simulated rating
match quality_rating {
5 => println!("Excellent audio quality"),
4 => println!("Good audio quality"),
3 => println!("Acceptable audio quality"),
_ => println!("Poor audio quality"),
}
Sourcepub async fn set_preferred_codecs(
&self,
codec_names: Vec<String>,
) -> ClientResult<()>
pub async fn set_preferred_codecs( &self, codec_names: Vec<String>, ) -> ClientResult<()>
Set preferred codec order for future calls
Configures the preferred order of audio codecs for use in future call negotiations. The client will attempt to negotiate codecs in the specified order, with the first codec in the list being the most preferred. This setting affects SDP generation and codec negotiation during call establishment.
§Arguments
codec_names
- Vector of codec names in order of preference (e.g., [“OPUS”, “G722”, “PCMU”])
§Returns
Returns Ok(())
on success. Currently always succeeds as this stores preferences
for future use rather than validating codec availability immediately.
§Examples
// Set high-quality codec preference
let high_quality_codecs = vec![
"OPUS".to_string(),
"G722".to_string(),
"PCMU".to_string(),
];
println!("Would set codec preference: {:?}", high_quality_codecs);
// Low bandwidth preference
let low_bandwidth_codecs = vec![
"G729".to_string(),
"PCMU".to_string(),
"PCMA".to_string(),
];
println!("Low bandwidth codec order: {:?}", low_bandwidth_codecs);
// Enterprise compatibility preference
let enterprise_codecs = vec![
"G722".to_string(), // Good quality, widely supported
"PCMU".to_string(), // Universal compatibility
"PCMA".to_string(), // European preference
];
println!("Enterprise codec preference: {:?}", enterprise_codecs);
§Implementation Notes
This setting will be applied to future call negotiations. Active calls will
continue using their currently negotiated codecs. The codec names should match
those returned by get_available_codecs()
.
Sourcepub async fn start_audio_transmission(
&self,
call_id: &CallId,
) -> ClientResult<()>
pub async fn start_audio_transmission( &self, call_id: &CallId, ) -> ClientResult<()>
Start audio transmission for a call in pass-through mode (default)
Starts audio transmission for the specified call using the default pass-through mode, which allows RTP audio packets to flow between endpoints without automatic audio generation. This is the recommended mode for most production use cases.
§Arguments
call_id
- The unique identifier of the call to start audio transmission for
§Returns
Returns Ok(())
on success, or a ClientError
if:
- The call is not found
- The call is not in the Connected state
- The underlying media session fails to start transmission
§Examples
// Start audio transmission
let call_id: CallId = Uuid::new_v4();
println!("Would start audio transmission for call {}", call_id);
println!("RTP audio packets would begin flowing");
§Side Effects
- Updates call metadata with transmission status and timestamp
- Emits a
MediaEventType::AudioStarted
event - Begins RTP packet transmission through session-core
§State Requirements
The call must be in Connected
state. Calls that are terminated, failed,
or cancelled cannot have audio transmission started.
Sourcepub async fn start_audio_transmission_with_tone(
&self,
call_id: &CallId,
) -> ClientResult<()>
pub async fn start_audio_transmission_with_tone( &self, call_id: &CallId, ) -> ClientResult<()>
Start audio transmission for a call with tone generation
Starts audio transmission for the specified call using tone generation mode, which generates a 440Hz sine wave for testing purposes. This is useful for testing audio connectivity without requiring external audio sources.
§Arguments
call_id
- The unique identifier of the call to start audio transmission for
§Returns
Returns Ok(())
on success, or a ClientError
if:
- The call is not found
- The call is not in the Connected state
- The underlying media session fails to start transmission
§Examples
// Start audio transmission with test tone
let call_id: CallId = Uuid::new_v4();
println!("Would start 440Hz test tone for call {}", call_id);
Sourcepub async fn start_audio_transmission_with_custom_audio(
&self,
call_id: &CallId,
samples: Vec<u8>,
repeat: bool,
) -> ClientResult<()>
pub async fn start_audio_transmission_with_custom_audio( &self, call_id: &CallId, samples: Vec<u8>, repeat: bool, ) -> ClientResult<()>
Start audio transmission for a call with custom audio samples
Starts audio transmission for the specified call using custom audio samples. The samples must be in G.711 μ-law format (8-bit samples at 8kHz). This allows playing back custom audio files or any audio data during the call.
§Arguments
call_id
- The unique identifier of the call to start audio transmission forsamples
- The audio samples in G.711 μ-law formatrepeat
- Whether to repeat the audio samples when they finish
§Returns
Returns Ok(())
on success, or a ClientError
if:
- The call is not found
- The call is not in the Connected state
- The samples vector is empty
- The underlying media session fails to start transmission
§Examples
// Start audio transmission with custom audio
let call_id: CallId = Uuid::new_v4();
let audio_samples = vec![0x7F, 0x80, 0x7F, 0x80]; // Example μ-law samples
println!("Would start custom audio transmission for call {} ({} samples)",
call_id, audio_samples.len());
Sourcepub async fn set_custom_audio(
&self,
call_id: &CallId,
samples: Vec<u8>,
repeat: bool,
) -> ClientResult<()>
pub async fn set_custom_audio( &self, call_id: &CallId, samples: Vec<u8>, repeat: bool, ) -> ClientResult<()>
Set custom audio samples for an active transmission session
Updates the audio samples for an already active transmission session. This allows changing the audio content during an ongoing call without stopping and restarting the transmission.
§Arguments
call_id
- The unique identifier of the callsamples
- The new audio samples in G.711 μ-law formatrepeat
- Whether to repeat the audio samples when they finish
§Returns
Returns Ok(())
on success, or a ClientError
if:
- The call is not found
- Audio transmission is not active for this call
- The samples vector is empty
§Examples
// Switch to different audio during call
let call_id: CallId = Uuid::new_v4();
let new_samples = vec![0x7F, 0x80, 0x7F, 0x80]; // New audio content
println!("Would update audio for call {} with {} new samples",
call_id, new_samples.len());
Sourcepub async fn set_tone_generation(
&self,
call_id: &CallId,
frequency: f64,
amplitude: f64,
) -> ClientResult<()>
pub async fn set_tone_generation( &self, call_id: &CallId, frequency: f64, amplitude: f64, ) -> ClientResult<()>
Set tone generation parameters for an active transmission session
Updates the tone generation parameters for an already active transmission session. This allows changing from custom audio or pass-through mode to tone generation during an ongoing call.
§Arguments
call_id
- The unique identifier of the callfrequency
- The tone frequency in Hz (e.g., 440.0 for A4)amplitude
- The tone amplitude (0.0 to 1.0)
§Returns
Returns Ok(())
on success, or a ClientError
if:
- The call is not found
- Audio transmission is not active for this call
- Invalid frequency or amplitude values
Sourcepub async fn set_pass_through_mode(&self, call_id: &CallId) -> ClientResult<()>
pub async fn set_pass_through_mode(&self, call_id: &CallId) -> ClientResult<()>
Enable pass-through mode for an active transmission session
Switches an active transmission session to pass-through mode, which stops any audio generation (tones or custom audio) and allows normal RTP audio flow between endpoints.
§Arguments
call_id
- The unique identifier of the call
§Returns
Returns Ok(())
on success, or a ClientError
if:
- The call is not found
- Audio transmission is not active for this call
Sourcepub async fn stop_audio_transmission(
&self,
call_id: &CallId,
) -> ClientResult<()>
pub async fn stop_audio_transmission( &self, call_id: &CallId, ) -> ClientResult<()>
Stop audio transmission for a call
Stops audio transmission for the specified call, halting the flow of RTP audio packets between the local client and the remote endpoint. This is typically used when putting a call on hold or during call termination.
§Arguments
call_id
- The unique identifier of the call to stop audio transmission for
§Returns
Returns Ok(())
on success, or a ClientError
if:
- The call is not found
- The underlying media session fails to stop transmission
§Examples
// Stop audio transmission
let call_id: CallId = Uuid::new_v4();
println!("Would stop audio transmission for call {}", call_id);
println!("RTP audio packets would stop flowing");
// Put call on hold
let call_id: CallId = Uuid::new_v4();
println!("Putting call {} on hold", call_id);
println!("Audio transmission would be stopped");
// Emergency stop
let call_id: CallId = Uuid::new_v4();
println!("Emergency stop of audio for call {}", call_id);
println!("Immediate halt of RTP transmission");
§Side Effects
- Updates call metadata with transmission status and timestamp
- Emits a
MediaEventType::AudioStopped
event - Stops RTP packet transmission through session-core
§Use Cases
- Putting calls on hold
- Call termination procedures
- Emergency audio cutoff
- Bandwidth conservation
Sourcepub async fn is_audio_transmission_active(
&self,
call_id: &CallId,
) -> ClientResult<bool>
pub async fn is_audio_transmission_active( &self, call_id: &CallId, ) -> ClientResult<bool>
Check if audio transmission is active for a call
Determines whether audio transmission is currently active for the specified call. This status is tracked in the call’s metadata and reflects the current state of RTP audio packet transmission.
§Arguments
call_id
- The unique identifier of the call to check
§Returns
Returns Ok(true)
if audio transmission is active, Ok(false)
if inactive,
or ClientError::CallNotFound
if the call doesn’t exist.
§Examples
// Check transmission status
let call_id: CallId = Uuid::new_v4();
let is_active = true; // Simulated state
if is_active {
println!("Call {} has active audio transmission", call_id);
} else {
println!("Call {} audio transmission is stopped", call_id);
}
// Conditional UI display
let call_id: CallId = Uuid::new_v4();
let transmission_active = false; // Simulated
let status_icon = if transmission_active { "🔊" } else { "⏸️" };
println!("Audio status for call {}: {}", call_id, status_icon);
Sourcepub async fn update_call_media(
&self,
call_id: &CallId,
new_sdp: &str,
) -> ClientResult<()>
pub async fn update_call_media( &self, call_id: &CallId, new_sdp: &str, ) -> ClientResult<()>
Update call media configuration with new SDP
Updates the media configuration for an existing call using a new Session Description Protocol (SDP) description. This is typically used for handling re-INVITE scenarios, media parameter changes, or call modifications during an active session.
§Arguments
call_id
- The unique identifier of the call to updatenew_sdp
- The new SDP description to apply
§Returns
Returns Ok(())
on success, or a ClientError
if:
- The call is not found
- The SDP is empty or invalid
- The session-core fails to apply the media update
§Examples
// Update media configuration
let call_id: CallId = Uuid::new_v4();
let new_sdp = "v=0\r\no=example 123 456 IN IP4 192.168.1.1\r\n";
println!("Would update media for call {} with new SDP", call_id);
println!("SDP length: {} bytes", new_sdp.len());
// Re-INVITE scenario
let call_id: CallId = Uuid::new_v4();
println!("Processing re-INVITE for call {}", call_id);
println!("Would update call media parameters");
§Use Cases
- Handling SIP re-INVITE messages
- Updating codec parameters mid-call
- Changing media endpoints
- Modifying bandwidth allocations
Sourcepub async fn get_media_capabilities(&self) -> MediaCapabilities
pub async fn get_media_capabilities(&self) -> MediaCapabilities
Get comprehensive media capabilities of the client
Returns a detailed description of the media capabilities supported by this client, including available codecs, supported features, and operational limits. This information is useful for capability negotiation, feature detection, and system configuration.
§Returns
Returns a MediaCapabilities
struct containing:
- List of supported audio codecs with full details
- Feature support flags (hold, mute, DTMF, transfer)
- Protocol support indicators (SDP, RTP, RTCP)
- Operational limits (max concurrent calls)
- Supported media types
§Examples
// Check client capabilities
let capabilities = MediaCapabilities {
supported_codecs: vec![
AudioCodecInfo {
name: "OPUS".to_string(),
payload_type: 111,
clock_rate: 48000,
channels: 2,
description: "High quality".to_string(),
quality_rating: 5,
}
],
can_hold: true,
can_mute_microphone: true,
can_mute_speaker: true,
can_send_dtmf: true,
can_transfer: true,
supports_sdp_offer_answer: true,
supports_rtp: true,
supports_rtcp: true,
max_concurrent_calls: 10,
supported_media_types: vec!["audio".to_string()],
};
println!("Client supports {} codecs", capabilities.supported_codecs.len());
println!("Max concurrent calls: {}", capabilities.max_concurrent_calls);
// Feature detection
let can_hold = true;
let can_transfer = true;
if can_hold && can_transfer {
println!("Advanced call control features available");
}
// Protocol support check
let supports_rtp = true;
let supports_rtcp = true;
match (supports_rtp, supports_rtcp) {
(true, true) => println!("Full RTP/RTCP support"),
(true, false) => println!("RTP only support"),
_ => println!("Limited media support"),
}
§Use Cases
- Client capability advertisement
- Feature availability checking before operations
- System configuration and limits planning
- Interoperability assessment
Sourcepub async fn generate_sdp_offer(&self, call_id: &CallId) -> ClientResult<String>
pub async fn generate_sdp_offer(&self, call_id: &CallId) -> ClientResult<String>
Generate SDP offer for a call using session-core
Creates a Session Description Protocol (SDP) offer for the specified call, which describes the media capabilities and parameters that this client is willing to negotiate. The offer includes codec preferences, RTP port assignments, and other media configuration details required for establishing the call.
§Arguments
call_id
- The unique identifier of the call to generate an SDP offer for
§Returns
Returns the SDP offer as a string, or a ClientError
if:
- The call is not found
- The call is not in an appropriate state (must be Initiating or Connected)
- The underlying session-core fails to generate the SDP
§Examples
// Generate SDP offer for outgoing call
let call_id: CallId = Uuid::new_v4();
println!("Would generate SDP offer for call {}", call_id);
// Example SDP structure
let sdp_example = "v=0\r\no=- 123456 654321 IN IP4 192.168.1.1\r\n";
println!("SDP offer would be {} bytes", sdp_example.len());
// SIP call flow context
let call_id: CallId = Uuid::new_v4();
println!("Generating SDP offer for INVITE to call {}", call_id);
println!("This SDP will be included in the SIP INVITE message");
§Side Effects
- Updates call metadata with the generated SDP offer and timestamp
- Emits a
MediaEventType::SdpOfferGenerated
event - Coordinates with session-core for media session setup
§Use Cases
- Initiating outbound calls
- Re-INVITE scenarios for call modifications
- Media renegotiation during active calls
Sourcepub async fn process_sdp_answer(
&self,
call_id: &CallId,
sdp_answer: &str,
) -> ClientResult<()>
pub async fn process_sdp_answer( &self, call_id: &CallId, sdp_answer: &str, ) -> ClientResult<()>
Process SDP answer for a call using session-core
Processes a Session Description Protocol (SDP) answer received from the remote party, completing the media negotiation process. This function validates the SDP answer, updates the media session parameters, and establishes the agreed-upon media flow configuration for the call.
§Arguments
call_id
- The unique identifier of the call to process the SDP answer forsdp_answer
- The SDP answer string received from the remote party
§Returns
Returns Ok(())
on successful processing, or a ClientError
if:
- The call is not found
- The SDP answer is empty or malformed
- The underlying session-core fails to process the SDP
§Examples
// Process SDP answer from 200 OK response
let call_id: CallId = Uuid::new_v4();
let sdp_answer = "v=0\r\no=remote 456789 987654 IN IP4 192.168.1.2\r\n";
println!("Would process SDP answer for call {}", call_id);
println!("SDP answer size: {} bytes", sdp_answer.len());
// Media negotiation completion
let call_id: CallId = Uuid::new_v4();
println!("Completing media negotiation for call {}", call_id);
println!("Would establish RTP flow based on negotiated parameters");
§Side Effects
- Updates call metadata with the processed SDP answer and timestamp
- Emits a
MediaEventType::SdpAnswerProcessed
event - Establishes media flow parameters with session-core
- Enables RTP packet transmission/reception
§Use Cases
- Processing 200 OK responses to INVITE requests
- Handling SDP answers in re-INVITE scenarios
- Completing media renegotiation processes
Sourcepub async fn stop_media_session(&self, call_id: &CallId) -> ClientResult<()>
pub async fn stop_media_session(&self, call_id: &CallId) -> ClientResult<()>
Stop media session for a call
Terminates the media session for the specified call, stopping all audio transmission and reception. This function cleanly shuts down the RTP flows, releases media resources, and updates the call state to reflect that media is no longer active.
§Arguments
call_id
- The unique identifier of the call to stop media session for
§Returns
Returns Ok(())
on successful termination, or a ClientError
if:
- The call is not found
- The underlying media session fails to stop cleanly
§Examples
// Stop media session during call termination
let call_id: CallId = Uuid::new_v4();
println!("Would stop media session for call {}", call_id);
println!("RTP flows would be terminated");
// Cleanup during error handling
let call_id: CallId = Uuid::new_v4();
println!("Emergency media session cleanup for call {}", call_id);
println!("Would release all media resources");
§Side Effects
- Updates call metadata to mark media session as inactive
- Emits a
MediaEventType::MediaSessionStopped
event - Releases RTP ports and media resources
- Stops all audio transmission and reception
§Use Cases
- Call termination procedures
- Error recovery and cleanup
- Media session reinitiation prep
Sourcepub async fn start_media_session(
&self,
call_id: &CallId,
) -> ClientResult<MediaSessionInfo>
pub async fn start_media_session( &self, call_id: &CallId, ) -> ClientResult<MediaSessionInfo>
Start media session for a call
Initiates a new media session for the specified call, creating the necessary RTP flows and establishing audio transmission capabilities. This function coordinates with session-core to set up media parameters and returns detailed information about the created media session.
§Arguments
call_id
- The unique identifier of the call to start media session for
§Returns
Returns MediaSessionInfo
containing detailed session information, or a ClientError
if:
- The call is not found
- The call is not in Connected state
- The underlying session-core fails to create the media session
- Media information cannot be retrieved after session creation
§Examples
// Start media session for connected call
let call_id: CallId = Uuid::new_v4();
println!("Would start media session for call {}", call_id);
// Example session info
let session_info = MediaSessionInfo {
call_id,
session_id: rvoip_session_core::api::SessionId("session-123".to_string()),
media_session_id: "media-123".to_string(),
local_rtp_port: Some(12000),
remote_rtp_port: Some(12001),
codec: Some("OPUS".to_string()),
media_direction: AudioDirection::SendReceive,
quality_metrics: None,
is_active: true,
created_at: Utc::now(),
};
println!("Media session {} created on port {}",
session_info.media_session_id,
session_info.local_rtp_port.unwrap_or(0));
// Enterprise call setup
let call_id: CallId = Uuid::new_v4();
println!("Establishing enterprise media session for call {}", call_id);
println!("Would configure high-quality codecs and QoS parameters");
§Side Effects
- Creates a new media session in session-core
- Updates call metadata with media session details
- Emits a
MediaEventType::MediaSessionStarted
event - Allocates RTP ports and resources
§State Requirements
The call must be in Connected
state before starting a media session.
Sourcepub async fn is_media_session_active(
&self,
call_id: &CallId,
) -> ClientResult<bool>
pub async fn is_media_session_active( &self, call_id: &CallId, ) -> ClientResult<bool>
Check if media session is active for a call
Determines whether a media session is currently active for the specified call. A media session is considered active if it has been started and not yet stopped, meaning RTP flows are established and audio can be transmitted/received.
§Arguments
call_id
- The unique identifier of the call to check
§Returns
Returns Ok(true)
if media session is active, Ok(false)
if inactive,
or ClientError::CallNotFound
if the call doesn’t exist.
§Examples
// Check media session status
let call_id: CallId = Uuid::new_v4();
let is_active = true; // Simulated state
if is_active {
println!("Call {} has active media session", call_id);
} else {
println!("Call {} media session is inactive", call_id);
}
// Conditional media operations
let call_id: CallId = Uuid::new_v4();
let media_active = false; // Simulated
if !media_active {
println!("Need to start media session for call {}", call_id);
}
Sourcepub async fn get_media_session_info(
&self,
call_id: &CallId,
) -> ClientResult<Option<MediaSessionInfo>>
pub async fn get_media_session_info( &self, call_id: &CallId, ) -> ClientResult<Option<MediaSessionInfo>>
Get detailed media session information for a call
Retrieves comprehensive information about the media session for the specified call,
including session identifiers, RTP port assignments, codec details, media direction,
and session timestamps. Returns None
if no active media session exists.
§Arguments
call_id
- The unique identifier of the call to query
§Returns
Returns Ok(Some(MediaSessionInfo))
with session details if active,
Ok(None)
if no media session is active, or ClientError
if the call
is not found or media information cannot be retrieved.
§Examples
// Get current media session info
let call_id: CallId = Uuid::new_v4();
println!("Would get media session info for call {}", call_id);
// Example session info structure
let session_info = Some(MediaSessionInfo {
call_id,
session_id: rvoip_session_core::api::SessionId("session-456".to_string()),
media_session_id: "media-456".to_string(),
local_rtp_port: Some(13000),
remote_rtp_port: Some(13001),
codec: Some("G722".to_string()),
media_direction: AudioDirection::SendReceive,
quality_metrics: None,
is_active: true,
created_at: Utc::now(),
});
match session_info {
Some(info) => println!("Active session: {} using codec {}",
info.media_session_id,
info.codec.unwrap_or("Unknown".to_string())),
None => println!("No active media session"),
}
// Session diagnostics
let call_id: CallId = Uuid::new_v4();
println!("Gathering diagnostic info for call {}", call_id);
println!("Would include ports, codecs, and quality metrics");
Sourcepub async fn update_media_session(
&self,
call_id: &CallId,
new_sdp: &str,
) -> ClientResult<()>
pub async fn update_media_session( &self, call_id: &CallId, new_sdp: &str, ) -> ClientResult<()>
Update media session for a call (e.g., for re-INVITE)
Updates an existing media session with new parameters, typically used during SIP re-INVITE scenarios where call parameters need to be modified mid-call. This can include codec changes, hold/unhold operations, or other media modifications.
§Arguments
call_id
- The unique identifier of the call to updatenew_sdp
- The new SDP description with updated media parameters
§Returns
Returns Ok(())
on successful update, or a ClientError
if:
- The call is not found
- The new SDP is empty or invalid
- The session-core fails to apply the media update
§Examples
// Handle re-INVITE with new media parameters
let call_id: CallId = Uuid::new_v4();
let new_sdp = "v=0\r\no=updated 789 012 IN IP4 192.168.1.3\r\n";
println!("Would update media session for call {}", call_id);
println!("New SDP size: {} bytes", new_sdp.len());
// Codec change during call
let call_id: CallId = Uuid::new_v4();
println!("Updating codec for call {} due to network conditions", call_id);
println!("Would switch to lower bandwidth codec");
§Use Cases
- Processing SIP re-INVITE requests
- Codec switching for quality adaptation
- Hold/unhold operations
- Media parameter renegotiation
Sourcepub async fn get_negotiated_media_params(
&self,
call_id: &CallId,
) -> ClientResult<Option<NegotiatedMediaParams>>
pub async fn get_negotiated_media_params( &self, call_id: &CallId, ) -> ClientResult<Option<NegotiatedMediaParams>>
Get negotiated media parameters for a call
Retrieves the final negotiated media parameters that resulted from the SDP offer/answer exchange. This includes the agreed-upon codec, ports, bandwidth limits, and other media configuration details that both parties have accepted.
§Arguments
call_id
- The unique identifier of the call to get negotiated parameters for
§Returns
Returns Ok(Some(NegotiatedMediaParams))
with negotiated parameters if available,
Ok(None)
if negotiation is incomplete, or ClientError
if the call is not found
or parameters cannot be retrieved.
§Examples
// Check negotiated parameters
let call_id: CallId = Uuid::new_v4();
println!("Would get negotiated media parameters for call {}", call_id);
// Example negotiated parameters
let params = Some(NegotiatedMediaParams {
call_id,
negotiated_codec: Some("G722".to_string()),
local_rtp_port: Some(14000),
remote_rtp_port: Some(14001),
audio_direction: AudioDirection::SendReceive,
local_sdp: "v=0\r\no=local...".to_string(),
remote_sdp: "v=0\r\no=remote...".to_string(),
negotiated_at: Utc::now(),
supports_dtmf: true,
supports_hold: true,
bandwidth_kbps: Some(64),
encryption_enabled: false,
});
match params {
Some(p) => println!("Negotiated: {} at {}kbps",
p.negotiated_codec.unwrap_or("Unknown".to_string()),
p.bandwidth_kbps.unwrap_or(0)),
None => println!("Negotiation not complete"),
}
// Compatibility check
let call_id: CallId = Uuid::new_v4();
println!("Checking feature compatibility for call {}", call_id);
let supports_dtmf = true; // From negotiated params
let supports_hold = true;
println!("DTMF support: {}", if supports_dtmf { "Yes" } else { "No" });
println!("Hold support: {}", if supports_hold { "Yes" } else { "No" });
§Use Cases
- Verifying successful media negotiation
- Feature availability checking
- Quality monitoring and optimization
- Debugging media setup issues
Sourcepub async fn get_enhanced_media_capabilities(&self) -> EnhancedMediaCapabilities
pub async fn get_enhanced_media_capabilities(&self) -> EnhancedMediaCapabilities
Get enhanced media capabilities with advanced features
Returns an extended set of media capabilities that includes advanced features like session lifecycle management, SDP renegotiation support, early media, and encryption capabilities. This provides a more detailed view of the client’s media processing capabilities compared to the basic capabilities.
§Returns
Returns EnhancedMediaCapabilities
containing:
- Basic media capabilities (codecs, mute, hold, etc.)
- Advanced SDP features (offer/answer, renegotiation)
- Session lifecycle management capabilities
- Encryption and security features
- Transport protocol support
- Performance and scalability limits
§Examples
// Check advanced capabilities
let basic_caps = MediaCapabilities {
supported_codecs: vec![],
can_hold: true,
can_mute_microphone: true,
can_mute_speaker: true,
can_send_dtmf: true,
can_transfer: true,
supports_sdp_offer_answer: true,
supports_rtp: true,
supports_rtcp: true,
max_concurrent_calls: 10,
supported_media_types: vec!["audio".to_string()],
};
let enhanced_caps = EnhancedMediaCapabilities {
basic_capabilities: basic_caps,
supports_sdp_offer_answer: true,
supports_media_session_lifecycle: true,
supports_sdp_renegotiation: true,
supports_early_media: true,
supports_media_session_updates: true,
supports_codec_negotiation: true,
supports_bandwidth_management: false,
supports_encryption: false,
supported_sdp_version: "0".to_string(),
max_media_sessions: 10,
preferred_rtp_port_range: (10000, 20000),
supported_transport_protocols: vec!["RTP/AVP".to_string()],
};
println!("SDP renegotiation: {}", enhanced_caps.supports_sdp_renegotiation);
println!("Early media: {}", enhanced_caps.supports_early_media);
println!("Max sessions: {}", enhanced_caps.max_media_sessions);
// Feature availability matrix
let supports_renegotiation = true;
let supports_early_media = true;
let supports_encryption = false;
println!("Advanced Features:");
println!(" SDP Renegotiation: {}", if supports_renegotiation { "✓" } else { "✗" });
println!(" Early Media: {}", if supports_early_media { "✓" } else { "✗" });
println!(" Encryption: {}", if supports_encryption { "✓" } else { "✗" });
§Use Cases
- Advanced capability negotiation
- Enterprise feature planning
- Integration compatibility assessment
- Performance planning and sizing
Sourcepub async fn generate_sdp_answer(
&self,
call_id: &CallId,
offer: &str,
) -> ClientResult<String>
pub async fn generate_sdp_answer( &self, call_id: &CallId, offer: &str, ) -> ClientResult<String>
Generate SDP answer for an incoming call
Creates a Session Description Protocol (SDP) answer in response to an incoming SDP offer, typically from a SIP INVITE request. The answer describes the media capabilities and parameters that this client accepts and configures the media session based on the negotiated parameters.
§Arguments
call_id
- The unique identifier of the incoming calloffer
- The SDP offer string received from the remote party
§Returns
Returns the SDP answer as a string, or a ClientError
if:
- The call is not found
- The SDP offer is empty or malformed
- The underlying session-core fails to generate the SDP answer
§Examples
// Generate SDP answer for incoming call
let call_id: CallId = Uuid::new_v4();
let offer = "v=0\r\no=caller 123456 654321 IN IP4 192.168.1.10\r\n";
println!("Would generate SDP answer for call {}", call_id);
println!("Processing offer of {} bytes", offer.len());
// SIP call flow context
let call_id: CallId = Uuid::new_v4();
println!("Processing INVITE for call {}", call_id);
println!("Generating SDP answer for 200 OK response");
// Media negotiation
let call_id: CallId = Uuid::new_v4();
let offer = "v=0\r\nm=audio 5004 RTP/AVP 0 8\r\n";
println!("Negotiating media for call {}", call_id);
println!("Offer contains audio on port 5004");
println!("Would respond with compatible audio configuration");
§Side Effects
- Applies media configuration preferences to the generated SDP
- Updates call metadata with the generated SDP answer and timestamp
- Coordinates with session-core for media session configuration
- May modify SDP with custom attributes, bandwidth limits, and timing preferences
§Use Cases
- Responding to incoming SIP INVITE requests
- Completing media negotiation for incoming calls
- Auto-answering systems and IVR applications
- Conference bridge incoming call handling
Sourcepub async fn establish_media(
&self,
call_id: &CallId,
remote_addr: &str,
) -> ClientResult<()>
pub async fn establish_media( &self, call_id: &CallId, remote_addr: &str, ) -> ClientResult<()>
Establish media flow to a remote address
Establishes the actual media flow (RTP streams) between the local client and a specified remote address. This function configures the media session to begin transmitting and receiving audio packets to/from the designated endpoint. This is typically called after SDP negotiation is complete.
§Arguments
call_id
- The unique identifier of the call to establish media flow forremote_addr
- The remote address (IP:port) to establish media flow with
§Returns
Returns Ok(())
on successful establishment, or a ClientError
if:
- The call is not found
- The remote address is invalid or unreachable
- The underlying session-core fails to establish the media flow
§Examples
// Establish media flow after SDP negotiation
let call_id: CallId = Uuid::new_v4();
let remote_addr = "192.168.1.20:5004";
println!("Would establish media flow for call {}", call_id);
println!("Target remote address: {}", remote_addr);
// Direct media establishment for P2P calls
let call_id: CallId = Uuid::new_v4();
let peer_endpoint = "10.0.1.100:12000";
println!("Establishing direct P2P media to {}", peer_endpoint);
println!("Call ID: {}", call_id);
// Media relay configuration
let call_id: CallId = Uuid::new_v4();
let relay_address = "relay.example.com:8000";
println!("Configuring media relay for call {}", call_id);
println!("Relay endpoint: {}", relay_address);
println!("Would establish RTP flows through media relay");
§Side Effects
- Updates call metadata with media flow status and remote address
- Initiates RTP packet transmission/reception
- Configures network routing for media streams
- May trigger firewall/NAT traversal procedures
§Use Cases
- Completing call setup after SDP negotiation
- Direct peer-to-peer media establishment
- Media relay and proxy configurations
- Network topology adaptation
- Quality of Service (QoS) path establishment
§Network Considerations
The remote address should be reachable and the specified port should be available for RTP traffic. This function may trigger network discovery and NAT traversal procedures if required by the network topology.
Sourcepub async fn get_rtp_statistics(
&self,
call_id: &CallId,
) -> ClientResult<Option<RtpSessionStats>>
pub async fn get_rtp_statistics( &self, call_id: &CallId, ) -> ClientResult<Option<RtpSessionStats>>
Get RTP statistics for a call
Retrieves detailed Real-time Transport Protocol (RTP) statistics for the specified call, including packet counts, byte counts, jitter measurements, and packet loss metrics. This information is crucial for monitoring call quality and diagnosing network issues.
§Arguments
call_id
- The unique identifier of the call to get RTP statistics for
§Returns
Returns Ok(Some(RtpSessionStats))
with detailed RTP metrics if available,
Ok(None)
if no RTP session exists, or ClientError
if the call is not found
or statistics cannot be retrieved.
§Examples
// Monitor call quality
let call_id: CallId = Uuid::new_v4();
println!("Would get RTP statistics for call {}", call_id);
// Example statistics evaluation
let packets_sent = 1000u64;
let packets_lost = 5u64;
let loss_rate = (packets_lost as f64 / packets_sent as f64) * 100.0;
if loss_rate > 5.0 {
println!("High packet loss detected: {:.2}%", loss_rate);
} else {
println!("Good quality: {:.2}% packet loss", loss_rate);
}
// Network diagnostics
let call_id: CallId = Uuid::new_v4();
println!("Running network diagnostics for call {}", call_id);
println!("Would analyze jitter, latency, and throughput");
§Use Cases
- Real-time call quality monitoring
- Network performance analysis
- Troubleshooting audio issues
- Quality of Service (QoS) reporting
Sourcepub async fn get_media_statistics(
&self,
call_id: &CallId,
) -> ClientResult<Option<MediaSessionStats>>
pub async fn get_media_statistics( &self, call_id: &CallId, ) -> ClientResult<Option<MediaSessionStats>>
Get comprehensive media statistics for a call
Retrieves complete media session statistics including RTP/RTCP metrics, quality measurements, and performance indicators. This provides a holistic view of the media session’s health and performance characteristics.
§Arguments
call_id
- The unique identifier of the call to get media statistics for
§Returns
Returns Ok(Some(MediaSessionStats))
with comprehensive media metrics if available,
Ok(None)
if no media session exists, or ClientError
if the call is not found
or statistics cannot be retrieved.
§Examples
// Comprehensive call analysis
let call_id: CallId = Uuid::new_v4();
println!("Would get comprehensive media statistics for call {}", call_id);
// Example quality assessment
let audio_quality_score = 4.2; // Out of 5.0
let network_quality = "Good"; // Based on metrics
println!("Audio Quality: {:.1}/5.0", audio_quality_score);
println!("Network Quality: {}", network_quality);
// Performance monitoring dashboard
let call_id: CallId = Uuid::new_v4();
println!("Updating performance dashboard for call {}", call_id);
println!("Would include RTP, RTCP, jitter, and codec metrics");
§Use Cases
- Call quality dashboards
- Performance monitoring systems
- Troubleshooting complex media issues
- Historical call quality analysis
Sourcepub async fn get_call_statistics(
&self,
call_id: &CallId,
) -> ClientResult<Option<CallStatistics>>
pub async fn get_call_statistics( &self, call_id: &CallId, ) -> ClientResult<Option<CallStatistics>>
Get comprehensive call statistics for a call
Retrieves complete call statistics encompassing all aspects of the call including RTP metrics, quality measurements, call duration, and detailed performance data. This provides the most comprehensive view of call performance and quality.
§Arguments
call_id
- The unique identifier of the call to get complete statistics for
§Returns
Returns Ok(Some(CallStatistics))
with complete call metrics if available,
Ok(None)
if no call statistics exist, or ClientError
if the call is not found
or statistics cannot be retrieved.
§Examples
// Complete call analysis
let call_id: CallId = Uuid::new_v4();
println!("Would get complete call statistics for call {}", call_id);
// Example comprehensive metrics
let call_duration = Duration::from_secs(300); // 5 minutes
let avg_jitter = 15; // milliseconds
let packet_loss = 0.8; // percent
println!("Call Duration: {:?}", call_duration);
println!("Average Jitter: {}ms", avg_jitter);
println!("Packet Loss: {:.1}%", packet_loss);
// Call quality reporting
let call_id: CallId = Uuid::new_v4();
println!("Generating call quality report for call {}", call_id);
println!("Would include all RTP, quality, and performance metrics");
§Use Cases
- Post-call quality reports
- Billing and usage analytics
- Network performance analysis
- Customer experience metrics
- SLA compliance monitoring
Sourcepub async fn subscribe_to_audio_frames(
&self,
call_id: &CallId,
) -> ClientResult<AudioFrameSubscriber>
pub async fn subscribe_to_audio_frames( &self, call_id: &CallId, ) -> ClientResult<AudioFrameSubscriber>
Subscribe to audio frames from a call for real-time playback
Returns a subscriber that receives decoded audio frames from the RTP stream for the specified call. These frames can be played through speakers or processed for audio analysis.
§Arguments
call_id
- The unique identifier of the call to subscribe to
§Returns
Returns an AudioFrameSubscriber
that can be used to receive audio frames.
§Examples
// Subscribe to incoming audio frames
let subscriber = client.subscribe_to_audio_frames(&call_id).await?;
// Process frames in a background task
tokio::spawn(async move {
while let Ok(frame) = subscriber.recv() {
// Play frame through speakers or process it
println!("Received audio frame: {} samples at {}Hz",
frame.samples.len(), frame.sample_rate);
}
});
Sourcepub async fn send_audio_frame(
&self,
call_id: &CallId,
audio_frame: AudioFrame,
) -> ClientResult<()>
pub async fn send_audio_frame( &self, call_id: &CallId, audio_frame: AudioFrame, ) -> ClientResult<()>
Send an audio frame for encoding and transmission
Sends an audio frame to be encoded and transmitted via RTP for the specified call. This is typically used for microphone input or generated audio content.
§Arguments
call_id
- The unique identifier of the call to send audio onaudio_frame
- The audio frame to send
§Returns
Returns Ok(())
if the frame was sent successfully.
§Examples
// Create an audio frame (typically from microphone)
let samples = vec![0; 160]; // 20ms of silence at 8kHz
let frame = AudioFrame::new(samples, 8000, 1, 12345);
// Send the frame for transmission
client.send_audio_frame(&call_id, frame).await?;
Sourcepub async fn get_audio_stream_config(
&self,
call_id: &CallId,
) -> ClientResult<Option<AudioStreamConfig>>
pub async fn get_audio_stream_config( &self, call_id: &CallId, ) -> ClientResult<Option<AudioStreamConfig>>
Get current audio stream configuration for a call
Returns the current audio streaming configuration for the specified call, including sample rate, channels, codec, and processing settings.
§Arguments
call_id
- The unique identifier of the call
§Returns
Returns the current AudioStreamConfig
or None
if no stream is configured.
§Examples
if let Some(config) = client.get_audio_stream_config(&call_id).await? {
println!("Audio stream: {}Hz, {} channels, codec: {}",
config.sample_rate, config.channels, config.codec);
} else {
println!("No audio stream configured for call");
}
Sourcepub async fn set_audio_stream_config(
&self,
call_id: &CallId,
config: AudioStreamConfig,
) -> ClientResult<()>
pub async fn set_audio_stream_config( &self, call_id: &CallId, config: AudioStreamConfig, ) -> ClientResult<()>
Set audio stream configuration for a call
Configures the audio streaming parameters for the specified call, including sample rate, channels, codec preferences, and audio processing settings.
§Arguments
call_id
- The unique identifier of the callconfig
- The audio stream configuration to apply
§Returns
Returns Ok(())
if the configuration was applied successfully.
§Examples
// Configure high-quality audio stream
let config = AudioStreamConfig {
sample_rate: 48000,
channels: 1,
codec: "Opus".to_string(),
frame_size_ms: 20,
enable_aec: true,
enable_agc: true,
enable_vad: true,
};
client.set_audio_stream_config(&call_id, config).await?;
Sourcepub async fn start_audio_stream(&self, call_id: &CallId) -> ClientResult<()>
pub async fn start_audio_stream(&self, call_id: &CallId) -> ClientResult<()>
Start audio streaming for a call
Begins the audio streaming pipeline for the specified call, enabling real-time audio frame processing. This must be called before audio frames can be sent or received.
§Arguments
call_id
- The unique identifier of the call
§Returns
Returns Ok(())
if the audio stream started successfully.
§Examples
// Configure and start audio streaming
let config = AudioStreamConfig {
sample_rate: 8000,
channels: 1,
codec: "PCMU".to_string(),
frame_size_ms: 20,
enable_aec: true,
enable_agc: true,
enable_vad: true,
};
client.set_audio_stream_config(&call_id, config).await?;
client.start_audio_stream(&call_id).await?;
println!("Audio streaming started for call {}", call_id);
Sourcepub async fn stop_audio_stream(&self, call_id: &CallId) -> ClientResult<()>
pub async fn stop_audio_stream(&self, call_id: &CallId) -> ClientResult<()>
Stop audio streaming for a call
Stops the audio streaming pipeline for the specified call, disabling real-time audio frame processing. This cleans up resources and stops audio transmission.
§Arguments
call_id
- The unique identifier of the call
§Returns
Returns Ok(())
if the audio stream stopped successfully.
§Examples
// Stop audio streaming
client.stop_audio_stream(&call_id).await?;
println!("Audio streaming stopped for call {}", call_id);