diff --git a/Cargo.lock b/Cargo.lock index 0efa3eaf..6bef7acf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,6 +1010,8 @@ dependencies = [ "livekit-protocol", "log", "parking_lot", + "serde", + "serde_json", "thiserror", "tokio", "tokio-stream", @@ -1076,7 +1078,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.3.9" +version = "0.3.10" dependencies = [ "console-subscriber", "dashmap", diff --git a/examples/wgpu_room/src/app.rs b/examples/wgpu_room/src/app.rs index 1d6ea0c1..eff7d07c 100644 --- a/examples/wgpu_room/src/app.rs +++ b/examples/wgpu_room/src/app.rs @@ -160,6 +160,12 @@ impl LkApp { let _ = self.service.send(AsyncCmd::ToggleSine); } }); + + ui.menu_button("Debug", |ui| { + if ui.button("Refresh stats").clicked() { + let _ = self.service.send(AsyncCmd::RefreshStats); + } + }); }); } diff --git a/examples/wgpu_room/src/service.rs b/examples/wgpu_room/src/service.rs index e7bd6dfe..ad47ef85 100644 --- a/examples/wgpu_room/src/service.rs +++ b/examples/wgpu_room/src/service.rs @@ -33,6 +33,7 @@ pub enum AsyncCmd { publication: RemoteTrackPublication, }, E2eeKeyRatchet, + RefreshStats, } #[derive(Debug)] @@ -199,6 +200,11 @@ async fn service_task(inner: Arc, mut cmd_rx: mpsc::UnboundedRecei } } } + AsyncCmd::RefreshStats => { + if let Some(state) = running_state.as_ref() { + state.room.get_stats(); + } + } } } } diff --git a/libwebrtc/Cargo.toml b/libwebrtc/Cargo.toml index 867a7488..f68ba88f 100644 --- a/libwebrtc/Cargo.toml +++ b/libwebrtc/Cargo.toml @@ -10,6 +10,8 @@ repository = "https://github.com/livekit/client-sdk-rust" [dependencies] livekit-protocol = { path = "../livekit-protocol", version = "0.2.0" } log = "0.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" thiserror = "1.0" [target.'cfg(target_os = "android")'.dependencies] diff --git a/libwebrtc/src/data_channel.rs b/libwebrtc/src/data_channel.rs index 0ff57cae..49ca4db9 100644 --- a/libwebrtc/src/data_channel.rs +++ b/libwebrtc/src/data_channel.rs @@ -13,6 +13,7 @@ // limitations under the License. use crate::{imp::data_channel as dc_imp, rtp_parameters::Priority}; +use serde::Deserialize; use std::{fmt::Debug, str::Utf8Error}; use thiserror::Error; @@ -49,8 +50,9 @@ pub enum DataChannelError { Utf8(#[from] Utf8Error), } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum DataState { +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum DataChannelState { Connecting, Open, Closing, @@ -63,7 +65,7 @@ pub struct DataBuffer<'a> { pub binary: bool, } -pub type OnStateChange = Box; +pub type OnStateChange = Box; pub type OnMessage = Box; pub type OnBufferedAmountChange = Box; @@ -85,7 +87,7 @@ impl DataChannel { self.handle.label() } - pub fn state(&self) -> DataState { + pub fn state(&self) -> DataChannelState { self.handle.state() } diff --git a/libwebrtc/src/lib.rs b/libwebrtc/src/lib.rs index bb097b79..4e954f52 100644 --- a/libwebrtc/src/lib.rs +++ b/libwebrtc/src/lib.rs @@ -56,6 +56,7 @@ pub mod rtp_receiver; pub mod rtp_sender; pub mod rtp_transceiver; pub mod session_description; +pub mod stats; pub mod video_frame; pub mod video_source; pub mod video_stream; diff --git a/libwebrtc/src/native/peer_connection.rs b/libwebrtc/src/native/peer_connection.rs index 66a24fef..d7abe9c1 100644 --- a/libwebrtc/src/native/peer_connection.rs +++ b/libwebrtc/src/native/peer_connection.rs @@ -38,11 +38,13 @@ use crate::rtp_receiver::RtpReceiver; use crate::rtp_sender::RtpSender; use crate::rtp_transceiver::RtpTransceiver; use crate::rtp_transceiver::RtpTransceiverInit; +use crate::stats::RtcStats; use crate::MediaType; use crate::RtcErrorType; use crate::{session_description::SessionDescription, RtcError}; use cxx::SharedPtr; use parking_lot::Mutex; +use std::default; use std::sync::Arc; use tokio::sync::mpsc; use tokio::sync::oneshot; @@ -440,6 +442,17 @@ impl PeerConnection { .map_err(|e| unsafe { sys_err::ffi::RtcError::from(e.what()).into() }) } + pub async fn get_stats(&self) -> Result, RtcError> { + let (tx, rx) = oneshot::channel::, RtcError>>(); + let ctx = Box::new(sys_pc::AsyncContext(Box::new(tx))); + + self.sys_handle.get_stats(ctx, |ctx, stats| { + log::info!("Received stats {}", stats); + }); + + Ok(Default::default()) + } + pub fn senders(&self) -> Vec { self.sys_handle .get_senders() diff --git a/libwebrtc/src/peer_connection.rs b/libwebrtc/src/peer_connection.rs index 648010c0..15ba3d2e 100644 --- a/libwebrtc/src/peer_connection.rs +++ b/libwebrtc/src/peer_connection.rs @@ -22,6 +22,7 @@ use crate::rtp_receiver::RtpReceiver; use crate::rtp_sender::RtpSender; use crate::rtp_transceiver::{RtpTransceiver, RtpTransceiverInit}; use crate::session_description::SessionDescription; +use crate::stats::RtcStats; use crate::{MediaType, RtcError}; use std::fmt::Debug; @@ -157,6 +158,10 @@ impl PeerConnection { self.handle.remove_track(sender) } + pub async fn get_stats(&self) -> Result, RtcError> { + self.handle.get_stats().await + } + pub fn add_transceiver( &self, track: MediaStreamTrack, @@ -172,6 +177,7 @@ impl PeerConnection { ) -> Result { self.handle.add_transceiver_for_media(media_type, init) } + pub fn close(&self) { self.handle.close() } diff --git a/libwebrtc/src/stats.rs b/libwebrtc/src/stats.rs new file mode 100644 index 00000000..1376b042 --- /dev/null +++ b/libwebrtc/src/stats.rs @@ -0,0 +1,515 @@ +// Structs from https://www.w3.org/TR/webrtc-stats/ +// serde will handle the magic of correctly deserializing the json into these structs + +use crate::data_channel::DataChannelState; +use serde::Deserialize; +use std::collections::HashMap; + +// RtcStats enum (the json is flattened + tagged) +#[derive(Debug, Deserialize)] +#[serde(tag = "type")] +pub enum RtcStats { + Codec { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + codec: CodecStats, + }, + InboundRtp { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + stream: RtpStreamStats, + + #[serde(flatten)] + received: ReceivedRtpStreamStats, + + #[serde(flatten)] + inbound: InboundRtpStreamStats, + }, + OutboundRtp { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + stream: RtpStreamStats, + + #[serde(flatten)] + sent: SentRtpStreamStats, + + #[serde(flatten)] + outbound: OutboundRtpStreamStats, + }, + RemoteInboundRtp { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + stream: RtpStreamStats, + + #[serde(flatten)] + received: ReceivedRtpStreamStats, + + #[serde(flatten)] + remote_inbound: RemoteInboundRtpStreamStats, + }, + RemoteOutboundRtp { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + stream: RtpStreamStats, + + #[serde(flatten)] + sent: SentRtpStreamStats, + + #[serde(flatten)] + remote_outbound: RemoteOutboundRtpStreamStats, + }, + MediaSource { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + source: MediaSourceStats, + + #[serde(flatten)] + audio: AudioSourceStats, + + #[serde(flatten)] + video: VideoSourceStats, + }, + MediaPlayout { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + audio_playout: AudioPlayoutStats, + }, + PeerConnection { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + pc: PeerConnectionStats, + }, + DataChannel { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + dc: DataChannelStats, + }, + Transport { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + transport: TransportStats, + }, + CandidatePair { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + candidate_pair: CandidatePairStats, + }, + LocalCandidate { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + candidate: IceCandidateStats, + }, + RemoteCandidate { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + candidate: IceCandidateStats, + }, + Certificate { + #[serde(flatten)] + rtc: RtcStatsData, + + #[serde(flatten)] + certificate: CertificateStats, + }, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum QualityLimitationReason { + None, + CPU, + Bandwidth, + Other, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum IceRole { + Unknown, + Controlling, + Controlled, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum DtlsTransportState { + New, + Connecting, + Connected, + Closed, + Failed, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum IceTransportState { + New, + Checking, + Connected, + Completed, + Disconnected, + Failed, + Closed, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum DtlsRole { + Client, + Server, + Unknown, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum IceCandidatePairState { + Frozen, + Waiting, + InProgress, // in-progress + Failed, + Succeeded, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum IceCandidateType { + Host, + Srflx, + Prflx, + Relay, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum IceServerTransportProtocol { + Udp, + Tcp, + Tls, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum IceTcpCandidateType { + Active, + Passive, + So, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RtcStatsData { + pub id: String, + pub r#type: String, + pub timestamp: i64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CodecStats { + pub payload_type: u32, + pub transport_id: String, + pub mime_type: String, + pub clock_rate: u32, + pub channels: u32, + pub sdp_fmtp_line: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RtpStreamStats { + pub ssrc: u32, + pub kind: String, + pub transport_id: String, + pub codec_id: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ReceivedRtpStreamStats { + pub packets_received: u64, + pub packets_lost: i64, + pub jitter: f64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InboundRtpStreamStats { + pub track_identifier: String, + pub mid: String, + pub remote_id: String, + pub frames_decoded: u32, + pub key_frames_decoded: u32, + pub frames_rendered: u32, + pub frames_dropped: u32, + pub frame_width: u32, + pub frame_height: u32, + pub frames_per_second: f64, + pub qp_sum: u64, + pub total_decode_time: f64, + pub total_inter_frame_delay: f64, + pub total_squared_inter_frame_delay: f64, + pub pause_count: u32, + pub total_pause_duration: f64, + pub freeze_count: u32, + pub total_freeze_duration: f64, + pub last_packet_received_timestamp: f64, + pub header_bytes_received: u64, + pub packets_discarded: u64, + pub fec_bytes_received: u64, + pub fec_packets_received: u64, + pub fec_packets_discarded: u64, + pub bytes_received: u64, + pub nack_count: u32, + pub fir_count: u32, + pub pli_count: u32, + pub total_processing_delay: f64, + pub estimated_playout_timestamp: f64, + pub jitter_buffer_delay: f64, + pub jitter_buffer_target_delay: f64, + pub jitter_buffer_emitted_count: u64, + pub jitter_buffer_minimum_delay: f64, + pub total_samples_received: u64, + pub concealed_samples: u64, + pub silent_concealed_samples: u64, + pub concealment_events: u64, + pub inserted_samples_for_deceleration: u64, + pub removed_samples_for_acceleration: u64, + pub audio_level: f64, + pub total_audio_energy: f64, + pub total_samples_duration: f64, + pub frames_received: u64, + pub decoder_implementation: String, + pub playout_id: String, + pub power_efficient_decoder: bool, + pub frames_assembled_from_multiple_packets: u64, + pub total_assembly_time: f64, + pub retransmitted_packets_received: u64, + pub retransmitted_bytes_received: u64, + pub rtx_ssrc: u32, + pub fec_ssrc: u32, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SentRtpStreamStats { + pub packets_sent: u64, + pub bytes_sent: u64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OutboundRtpStreamStats { + pub mid: String, + pub media_source_id: String, + pub remote_id: String, + pub rid: String, + pub header_bytes_sent: u64, + pub retransmitted_packets_sent: u64, + pub retransmitted_bytes_sent: u64, + pub rtx_ssrc: u32, + pub target_bitrate: f64, + pub total_encoded_bytes_target: u64, + pub frame_width: u32, + pub frame_height: u32, + pub frames_per_second: f64, + pub frames_sent: u32, + pub huge_frames_sent: u32, + pub frames_encoded: u32, + pub key_frames_encoded: u32, + pub qp_sum: u64, + pub total_encode_time: f64, + pub total_packet_send_delay: f64, + pub quality_limitation_reason: QualityLimitationReason, + pub quality_limitation_durations: HashMap, + pub quality_limitation_resolution_changes: u32, + pub nack_count: u32, + pub fir_count: u32, + pub pli_count: u32, + pub encoder_implementation: String, + pub power_efficient_encoder: bool, + pub active: bool, + pub scalibility_mode: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RemoteInboundRtpStreamStats { + pub local_id: String, + pub round_trip_time: f64, + pub total_round_trip_time: f64, + pub fraction_lost: f64, + pub round_trip_time_measurements: u64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RemoteOutboundRtpStreamStats { + pub local_id: String, + pub remote_timestamp: f64, + pub reports_sent: u64, + pub round_trip_time: f64, + pub total_round_trip_time: f64, + pub round_trip_time_measurements: u64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MediaSourceStats { + pub track_identifier: String, + pub kind: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AudioSourceStats { + pub audio_level: f64, + pub total_audio_energy: f64, + pub total_samples_duration: f64, + pub echo_return_loss: f64, + pub echo_return_loss_enhancement: f64, + pub dropped_samples_duration: f64, + pub dropped_samples_events: u32, + pub total_capture_delay: f64, + pub total_samples_captured: u64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VideoSourceStats { + pub width: u32, + pub height: u32, + pub frames: u32, + pub frames_per_second: f64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AudioPlayoutStats { + pub kind: String, + pub synthesized_samples_duration: f64, + pub synthesized_samples_events: u32, + pub total_samples_duration: f64, + pub total_playout_delay: f64, + pub total_samples_count: u64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PeerConnectionStats { + pub data_channels_opened: u32, + pub data_channels_closed: u32, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DataChannelStats { + pub label: String, + pub protocol: String, + pub data_channel_identifier: u16, + pub state: DataChannelState, + pub messages_sent: u32, + pub bytes_sent: u64, + pub messages_received: u32, + pub bytes_received: u64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransportStats { + pub packets_sent: u64, + pub packets_received: u64, + pub bytes_sent: u64, + pub bytes_received: u64, + pub ice_role: IceRole, + pub ice_local_username_fragment: String, + pub dtls_state: DtlsTransportState, + pub ice_state: IceTransportState, + pub selected_candidate_pair_id: String, + pub local_certificate_id: String, + pub remote_certificate_id: String, + pub tls_version: String, + pub dtls_cipher: String, + pub dtls_role: DtlsRole, + pub srtp_cipher: String, + pub selected_candidate_pair_changes: u32, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CandidatePairStats { + pub transport_id: String, + pub local_candidate_id: String, + pub remote_candidate_id: String, + pub state: IceCandidatePairState, + pub nominated: bool, + pub packets_sent: u64, + pub packets_received: u64, + pub bytes_sent: u64, + pub bytes_received: u64, + pub last_packet_sent_timestamp: f64, + pub last_packet_received_timestamp: f64, + pub total_round_trip_time: f64, + pub current_round_trip_time: f64, + pub available_outgoing_bitrate: f64, + pub available_incoming_bitrate: f64, + pub requests_received: u64, + pub requests_sent: u64, + pub responses_received: u64, + pub responses_sent: u64, + pub consent_requests_sent: u64, + pub packets_discarded_on_send: u32, + pub bytes_discarded_on_send: u64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct IceCandidateStats { + pub transport_id: String, + pub address: String, + pub port: i32, + pub protocol: String, + pub candidate_type: IceCandidateType, + pub priority: i32, + pub url: String, + pub relay_protocol: IceServerTransportProtocol, + pub foundation: String, + pub related_address: String, + pub related_port: i32, + pub username_fragment: String, + pub tcp_type: IceTcpCandidateType, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CertificateStats { + pub fingerprint: String, + pub fingerprint_algorithm: String, + pub base64_certificate: String, + pub issuer_certificate_id: String, +} diff --git a/livekit/src/room/mod.rs b/livekit/src/room/mod.rs index 3be5a3e0..d7e9daa4 100644 --- a/livekit/src/room/mod.rs +++ b/livekit/src/room/mod.rs @@ -449,6 +449,10 @@ impl Room { self.inner.participants.read().0.clone() } + pub fn get_stats(&self) { + self.inner.get_stats() + } + pub async fn simulate_scenario(&self, scenario: SimulateScenario) -> EngineResult<()> { self.inner.rtc_engine.simulate_scenario(scenario).await } @@ -474,7 +478,6 @@ pub(crate) struct RoomSession { local_participant: LocalParticipant, participants: RwLock<( // Keep track of participants by sid and identity - // Ideally we would just need identity HashMap, HashMap, )>, @@ -591,6 +594,26 @@ impl RoomSession { true } + pub fn get_stats(self: &Arc) { + let inner = self.clone(); + tokio::spawn(async move { + let _ = inner + .rtc_engine + .session() + .publisher() + .peer_connection() + .get_stats() + .await; + let _ = inner + .rtc_engine + .session() + .subscriber() + .peer_connection() + .get_stats() + .await; + }); + } + /// Update the participants inside a Room. /// It'll create, update or remove a participant /// It also update the participant tracks. diff --git a/webrtc-sys/include/livekit/jsep.h b/webrtc-sys/include/livekit/jsep.h index 826d4ac2..059835f6 100644 --- a/webrtc-sys/include/livekit/jsep.h +++ b/webrtc-sys/include/livekit/jsep.h @@ -22,7 +22,9 @@ #include "api/ref_counted_base.h" #include "api/set_local_description_observer_interface.h" #include "api/set_remote_description_observer_interface.h" +#include "api/stats/rtc_stats_collector_callback.h" #include "livekit/rtc_error.h" +#include "rtc_base/ref_count.h" #include "rust/cxx.h" namespace livekit { @@ -129,4 +131,17 @@ class NativeSetRemoteSdpObserver rust::Fn, RtcError)> on_complete_; }; +class NativeRtcStatsCollector : public webrtc::RTCStatsCollectorCallback { + public: + NativeRtcStatsCollector(rust::Box ctx, + rust::Fn, rust::String)> on_stats); + + void OnStatsDelivered( + const rtc::scoped_refptr &report) override; + + private: + rust::Box ctx_; + rust::Fn, rust::String)> on_stats_; +}; + } // namespace livekit diff --git a/webrtc-sys/include/livekit/peer_connection.h b/webrtc-sys/include/livekit/peer_connection.h index a6988184..4c5f8b13 100644 --- a/webrtc-sys/include/livekit/peer_connection.h +++ b/webrtc-sys/include/livekit/peer_connection.h @@ -92,6 +92,20 @@ class PeerConnection { void remove_track(std::shared_ptr sender) const; + void get_stats( + rust::Box ctx, + rust::Fn, rust::String)> on_stats) const; + + void get_sender_stats( + rust::Box ctx, + std::shared_ptr sender, + rust::Fn, rust::String)> on_stats) const; + + void get_receiver_stats( + rust::Box ctx, + std::shared_ptr sender, + rust::Fn, rust::String)> on_stats) const; + void restart_ice() const; std::shared_ptr add_transceiver( diff --git a/webrtc-sys/src/jsep.cpp b/webrtc-sys/src/jsep.cpp index 06b3f770..a4a338a6 100644 --- a/webrtc-sys/src/jsep.cpp +++ b/webrtc-sys/src/jsep.cpp @@ -149,4 +149,16 @@ void NativeSetRemoteSdpObserver::OnSetRemoteDescriptionComplete( on_complete_(std::move(ctx_), to_error(error)); } +NativeRtcStatsCollector::NativeRtcStatsCollector( + rust::Box ctx, + rust::Fn, rust::String)> on_stats) + : ctx_(std::move(ctx)), on_stats_(on_stats) {} + + +void NativeRtcStatsCollector::OnStatsDelivered( + const rtc::scoped_refptr& report) { + on_stats_(std::move(ctx_), report->ToJson()); +} + + } // namespace livekit diff --git a/webrtc-sys/src/peer_connection.cpp b/webrtc-sys/src/peer_connection.cpp index c7bd0dd1..18d067e3 100644 --- a/webrtc-sys/src/peer_connection.cpp +++ b/webrtc-sys/src/peer_connection.cpp @@ -190,6 +190,32 @@ void PeerConnection::remove_track(std::shared_ptr sender) const { throw std::runtime_error(serialize_error(to_error(error))); } +void PeerConnection::get_stats( + rust::Box ctx, + rust::Fn, rust::String)> on_stats) const { + rtc::scoped_refptr observer = + rtc::make_ref_counted(std::move(ctx), on_stats); + peer_connection_->GetStats(observer.get()); +} + +void PeerConnection::get_sender_stats( + rust::Box ctx, + std::shared_ptr sender, + rust::Fn, rust::String)> on_stats) const { + rtc::scoped_refptr observer = + rtc::make_ref_counted(std::move(ctx), on_stats); + peer_connection_->GetStats(sender->rtc_sender(), observer); +} + +void PeerConnection::get_receiver_stats( + rust::Box ctx, + std::shared_ptr receiver, + rust::Fn, rust::String)> on_stats) const { + rtc::scoped_refptr observer = + rtc::make_ref_counted(std::move(ctx), on_stats); + peer_connection_->GetStats(receiver->rtc_receiver(), observer); +} + std::shared_ptr PeerConnection::add_transceiver( std::shared_ptr track, RtpTransceiverInit init) const { diff --git a/webrtc-sys/src/peer_connection.rs b/webrtc-sys/src/peer_connection.rs index 526f9107..b7f76c27 100644 --- a/webrtc-sys/src/peer_connection.rs +++ b/webrtc-sys/src/peer_connection.rs @@ -76,6 +76,20 @@ pub mod ffi { IceGatheringComplete, } + #[repr(i32)] + pub enum ContinualGatheringPolicy { + GatherOnce, + GatherContinually, + } + + #[repr(i32)] + pub enum IceTransportsType { + None, + Relay, + NoHost, + All, + } + pub struct RtcOfferAnswerOptions { offer_to_receive_video: i32, offer_to_receive_audio: i32, @@ -93,20 +107,6 @@ pub mod ffi { pub password: String, } - #[repr(i32)] - pub enum ContinualGatheringPolicy { - GatherOnce, - GatherContinually, - } - - #[repr(i32)] - pub enum IceTransportsType { - None, - Relay, - NoHost, - All, - } - pub struct RtcConfiguration { pub ice_servers: Vec, pub continual_gathering_policy: ContinualGatheringPolicy, @@ -148,13 +148,13 @@ pub mod ffi { unsafe extern "C++" { include!("livekit/peer_connection.h"); - type PeerConnection; - // The reason we still expose NativePeerConnectionObserver is because cxx doeesn't support Rust type alias // So we can't share NativePeerConnectionWrapper in peer_connection_factory.rs // (It is technically possible to get the Opaque C++ Type, but in this case, we can't use Box) // We can delete create_native_peer_connection_observer once cxx supports Rust type alias type NativePeerConnectionObserver; + type PeerConnection; + fn create_native_peer_connection_observer( observer: Box, ) -> UniquePtr; @@ -193,6 +193,23 @@ pub mod ffi { stream_ids: &Vec, ) -> Result>; fn remove_track(self: &PeerConnection, sender: SharedPtr) -> Result<()>; + fn get_stats( + self: &PeerConnection, + ctx: Box, + on_stats: fn(ctx: Box, json: String), + ); + fn get_sender_stats( + self: &PeerConnection, + ctx: Box, + sender: SharedPtr, + on_stats: fn(ctx: Box, json: String), + ); + fn get_receiver_stats( + self: &PeerConnection, + ctx: Box, + receiver: SharedPtr, + on_stats: fn(ctx: Box, json: String), + ); fn add_transceiver( self: &PeerConnection, track: SharedPtr,