use anyhow::Context as _; use collections::HashMap; mod remote_video_track_view; use cpal::traits::HostTrait as _; pub use remote_video_track_view::{RemoteVideoTrackView, RemoteVideoTrackViewEvent}; use rodio::DeviceTrait as _; mod record; pub use record::CaptureInput; #[cfg(not(any( test, feature = "test-support", all(target_os = "windows", target_env = "gnu"), target_os = "freebsd" )))] mod livekit_client; #[cfg(not(any( test, feature = "test-support", all(target_os = "windows", target_env = "gnu"), target_os = "freebsd" )))] pub use livekit_client::*; // If you need proper LSP in livekit_client you've got to comment // - the cfg blocks above // - the mods: mock_client & test and their conditional blocks // - the pub use mock_client::* and their conditional blocks #[cfg(any( test, feature = "test-support", all(target_os = "windows", target_env = "gnu"), target_os = "freebsd" ))] mod mock_client; #[cfg(any( test, feature = "test-support", all(target_os = "windows", target_env = "gnu"), target_os = "freebsd" ))] pub mod test; #[cfg(any( test, feature = "test-support", all(target_os = "windows", target_env = "gnu"), target_os = "freebsd" ))] pub use mock_client::*; #[derive(Debug, Clone)] pub enum Participant { Local(LocalParticipant), Remote(RemoteParticipant), } #[derive(Debug, Clone)] pub enum TrackPublication { Local(LocalTrackPublication), Remote(RemoteTrackPublication), } impl TrackPublication { pub fn sid(&self) -> TrackSid { match self { TrackPublication::Local(local) => local.sid(), TrackPublication::Remote(remote) => remote.sid(), } } pub fn is_muted(&self) -> bool { match self { TrackPublication::Local(local) => local.is_muted(), TrackPublication::Remote(remote) => remote.is_muted(), } } } #[derive(Clone, Debug)] pub enum RemoteTrack { Audio(RemoteAudioTrack), Video(RemoteVideoTrack), } impl RemoteTrack { pub fn sid(&self) -> TrackSid { match self { RemoteTrack::Audio(remote_audio_track) => remote_audio_track.sid(), RemoteTrack::Video(remote_video_track) => remote_video_track.sid(), } } } #[derive(Clone, Debug)] pub enum LocalTrack { Audio(LocalAudioTrack), Video(LocalVideoTrack), } #[derive(Clone, Debug)] #[non_exhaustive] pub enum RoomEvent { ParticipantConnected(RemoteParticipant), ParticipantDisconnected(RemoteParticipant), LocalTrackPublished { publication: LocalTrackPublication, track: LocalTrack, participant: LocalParticipant, }, LocalTrackUnpublished { publication: LocalTrackPublication, participant: LocalParticipant, }, LocalTrackSubscribed { track: LocalTrack, }, TrackSubscribed { track: RemoteTrack, publication: RemoteTrackPublication, participant: RemoteParticipant, }, TrackUnsubscribed { track: RemoteTrack, publication: RemoteTrackPublication, participant: RemoteParticipant, }, TrackSubscriptionFailed { participant: RemoteParticipant, // error: livekit::track::TrackError, track_sid: TrackSid, }, TrackPublished { publication: RemoteTrackPublication, participant: RemoteParticipant, }, TrackUnpublished { publication: RemoteTrackPublication, participant: RemoteParticipant, }, TrackMuted { participant: Participant, publication: TrackPublication, }, TrackUnmuted { participant: Participant, publication: TrackPublication, }, RoomMetadataChanged { old_metadata: String, metadata: String, }, ParticipantMetadataChanged { participant: Participant, old_metadata: String, metadata: String, }, ParticipantNameChanged { participant: Participant, old_name: String, name: String, }, ParticipantAttributesChanged { participant: Participant, changed_attributes: HashMap, }, ActiveSpeakersChanged { speakers: Vec, }, ConnectionStateChanged(ConnectionState), Connected { participants_with_tracks: Vec<(RemoteParticipant, Vec)>, }, Disconnected { reason: &'static str, }, Reconnecting, Reconnected, } pub(crate) fn default_device( input: bool, ) -> anyhow::Result<(cpal::Device, cpal::SupportedStreamConfig)> { let device; let config; if input { device = cpal::default_host() .default_input_device() .context("no audio input device available")?; config = device .default_input_config() .context("failed to get default input config")?; } else { device = cpal::default_host() .default_output_device() .context("no audio output device available")?; config = device .default_output_config() .context("failed to get default output config")?; } Ok((device, config)) } pub(crate) fn get_sample_data( sample_format: cpal::SampleFormat, data: &cpal::Data, ) -> anyhow::Result> { match sample_format { cpal::SampleFormat::I8 => Ok(convert_sample_data::(data)), cpal::SampleFormat::I16 => Ok(data.as_slice::().unwrap().to_vec()), cpal::SampleFormat::I24 => Ok(convert_sample_data::(data)), cpal::SampleFormat::I32 => Ok(convert_sample_data::(data)), cpal::SampleFormat::I64 => Ok(convert_sample_data::(data)), cpal::SampleFormat::U8 => Ok(convert_sample_data::(data)), cpal::SampleFormat::U16 => Ok(convert_sample_data::(data)), cpal::SampleFormat::U32 => Ok(convert_sample_data::(data)), cpal::SampleFormat::U64 => Ok(convert_sample_data::(data)), cpal::SampleFormat::F32 => Ok(convert_sample_data::(data)), cpal::SampleFormat::F64 => Ok(convert_sample_data::(data)), _ => anyhow::bail!("Unsupported sample format"), } } pub(crate) fn convert_sample_data< TSource: cpal::SizedSample, TDest: cpal::SizedSample + cpal::FromSample, >( data: &cpal::Data, ) -> Vec { data.as_slice::() .unwrap() .iter() .map(|e| e.to_sample::()) .collect() }