use std::sync::Arc; use anyhow::{Context as _, Result};use collections::HashMap;use futures::{SinkExt, channel::mpsc};use gpui::{App, AsyncApp, ScreenCaptureSource, ScreenCaptureStream, Task};use gpui_tokio::Tokio;use playback::capture_local_video_track; mod playback; use crate::{LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication};pub use playback::AudioStream;pub(crate) use playback::{RemoteVideoFrame, play_remote_video_track}; #[derive(Clone, Debug)]pub struct RemoteVideoTrack(livekit::track::RemoteVideoTrack);#[derive(Clone, Debug)]pub struct RemoteAudioTrack(livekit::track::RemoteAudioTrack);#[derive(Clone, Debug)]pub struct RemoteTrackPublication(livekit::publication::RemoteTrackPublication);#[derive(Clone, Debug)]pub struct RemoteParticipant(livekit::participant::RemoteParticipant); #[derive(Clone, Debug)]pub struct LocalVideoTrack(livekit::track::LocalVideoTrack);#[derive(Clone, Debug)]pub struct LocalAudioTrack(livekit::track::LocalAudioTrack);#[derive(Clone, Debug)]pub struct LocalTrackPublication(livekit::publication::LocalTrackPublication);#[derive(Clone, Debug)]pub struct LocalParticipant(livekit::participant::LocalParticipant); pub struct Room { room: livekit::Room, _task: Task<()>, playback: playback::AudioStack,} pub type TrackSid = livekit::id::TrackSid;pub type ConnectionState = livekit::ConnectionState;#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]pub struct ParticipantIdentity(pub String); impl Room { pub async fn connect( url: String, token: String, cx: &mut AsyncApp, ) -> Result<(Self, mpsc::UnboundedReceiver<RoomEvent>)> { let connector = tokio_tungstenite::Connector::Rustls(Arc::new(http_client_tls::tls_config())); let mut config = livekit::RoomOptions::default(); config.connector = Some(connector); let (room, mut events) = Tokio::spawn(cx, async move { livekit::Room::connect(&url, &token, config).await })? .await??; let (mut tx, rx) = mpsc::unbounded(); let task = cx.background_executor().spawn(async move { while let Some(event) = events.recv().await { if let Some(event) = room_event_from_livekit(event) { tx.send(event).await.ok(); } } }); Ok(( Self { room, _task: task, playback: playback::AudioStack::new(cx.background_executor().clone()), }, rx, )) } pub fn local_participant(&self) -> LocalParticipant { LocalParticipant(self.room.local_participant()) } pub fn remote_participants(&self) -> HashMap<ParticipantIdentity, RemoteParticipant> { self.room .remote_participants() .into_iter() .map(|(k, v)| (ParticipantIdentity(k.0), RemoteParticipant(v))) .collect() } pub fn connection_state(&self) -> ConnectionState { self.room.connection_state() } pub async fn publish_local_microphone_track( &self, cx: &mut AsyncApp, ) -> Result<(LocalTrackPublication, playback::AudioStream)> { let (track, stream) = self.playback.capture_local_microphone_track()?; let publication = self .local_participant() .publish_track( livekit::track::LocalTrack::Audio(track.0), livekit::options::TrackPublishOptions { source: livekit::track::TrackSource::Microphone, ..Default::default() }, cx, ) .await?; Ok((publication, stream)) } pub async fn unpublish_local_track( &self, sid: TrackSid, cx: &mut AsyncApp, ) -> Result<LocalTrackPublication> { self.local_participant().unpublish_track(sid, cx).await } pub fn play_remote_audio_track( &self, track: &RemoteAudioTrack, _cx: &App, ) -> Result<playback::AudioStream> { Ok(self.playback.play_remote_audio_track(&track.0)) }} impl LocalParticipant { pub async fn publish_screenshare_track( &self, source: &dyn ScreenCaptureSource, cx: &mut AsyncApp, ) -> Result<(LocalTrackPublication, Box<dyn ScreenCaptureStream>)> { let (track, stream) = capture_local_video_track(source, cx).await?; let options = livekit::options::TrackPublishOptions { source: livekit::track::TrackSource::Screenshare, video_codec: livekit::options::VideoCodec::VP8, ..Default::default() }; let publication = self .publish_track(livekit::track::LocalTrack::Video(track.0), options, cx) .await?; Ok((publication, stream)) } async fn publish_track( &self, track: livekit::track::LocalTrack, options: livekit::options::TrackPublishOptions, cx: &mut AsyncApp, ) -> Result<LocalTrackPublication> { let participant = self.0.clone(); Tokio::spawn(cx, async move { participant.publish_track(track, options).await })? .await? .map(LocalTrackPublication) .context("publishing a track") } pub async fn unpublish_track( &self, sid: TrackSid, cx: &mut AsyncApp, ) -> Result<LocalTrackPublication> { let participant = self.0.clone(); Tokio::spawn(cx, async move { participant.unpublish_track(&sid).await })? .await? .map(LocalTrackPublication) .context("unpublishing a track") }} impl LocalTrackPublication { pub fn mute(&self, cx: &App) { let track = self.0.clone(); Tokio::spawn(cx, async move { track.mute(); }) .detach(); } pub fn unmute(&self, cx: &App) { let track = self.0.clone(); Tokio::spawn(cx, async move { track.unmute(); }) .detach(); } pub fn sid(&self) -> TrackSid { self.0.sid() } pub fn is_muted(&self) -> bool { self.0.is_muted() }} impl RemoteParticipant { pub fn identity(&self) -> ParticipantIdentity { ParticipantIdentity(self.0.identity().0) } pub fn track_publications(&self) -> HashMap<TrackSid, RemoteTrackPublication> { self.0 .track_publications() .into_iter() .map(|(sid, publication)| (sid, RemoteTrackPublication(publication))) .collect() }} impl RemoteAudioTrack { pub fn sid(&self) -> TrackSid { self.0.sid() }} impl RemoteVideoTrack { pub fn sid(&self) -> TrackSid { self.0.sid() }} impl RemoteTrackPublication { pub fn is_muted(&self) -> bool { self.0.is_muted() } pub fn is_enabled(&self) -> bool { self.0.is_enabled() } pub fn track(&self) -> Option<RemoteTrack> { self.0.track().map(remote_track_from_livekit) } pub fn is_audio(&self) -> bool { self.0.kind() == livekit::track::TrackKind::Audio } pub fn set_enabled(&self, enabled: bool, cx: &App) { let track = self.0.clone(); Tokio::spawn(cx, async move { track.set_enabled(enabled) }).detach(); } pub fn sid(&self) -> TrackSid { self.0.sid() }} impl Participant { pub fn identity(&self) -> ParticipantIdentity { match self { Participant::Local(local_participant) => { ParticipantIdentity(local_participant.0.identity().0) } Participant::Remote(remote_participant) => { ParticipantIdentity(remote_participant.0.identity().0) } } }} fn participant_from_livekit(participant: livekit::participant::Participant) -> Participant { match participant { livekit::participant::Participant::Local(local) => { Participant::Local(LocalParticipant(local)) } livekit::participant::Participant::Remote(remote) => { Participant::Remote(RemoteParticipant(remote)) } }} fn publication_from_livekit( publication: livekit::publication::TrackPublication,) -> TrackPublication { match publication { livekit::publication::TrackPublication::Local(local) => { TrackPublication::Local(LocalTrackPublication(local)) } livekit::publication::TrackPublication::Remote(remote) => { TrackPublication::Remote(RemoteTrackPublication(remote)) } }} fn remote_track_from_livekit(track: livekit::track::RemoteTrack) -> RemoteTrack { match track { livekit::track::RemoteTrack::Audio(audio) => RemoteTrack::Audio(RemoteAudioTrack(audio)), livekit::track::RemoteTrack::Video(video) => RemoteTrack::Video(RemoteVideoTrack(video)), }} fn local_track_from_livekit(track: livekit::track::LocalTrack) -> LocalTrack { match track { livekit::track::LocalTrack::Audio(audio) => LocalTrack::Audio(LocalAudioTrack(audio)), livekit::track::LocalTrack::Video(video) => LocalTrack::Video(LocalVideoTrack(video)), }}fn room_event_from_livekit(event: livekit::RoomEvent) -> Option<RoomEvent> { let event = match event { livekit::RoomEvent::ParticipantConnected(remote_participant) => { RoomEvent::ParticipantConnected(RemoteParticipant(remote_participant)) } livekit::RoomEvent::ParticipantDisconnected(remote_participant) => { RoomEvent::ParticipantDisconnected(RemoteParticipant(remote_participant)) } livekit::RoomEvent::LocalTrackPublished { publication, track, participant, } => RoomEvent::LocalTrackPublished { publication: LocalTrackPublication(publication), track: local_track_from_livekit(track), participant: LocalParticipant(participant), }, livekit::RoomEvent::LocalTrackUnpublished { publication, participant, } => RoomEvent::LocalTrackUnpublished { publication: LocalTrackPublication(publication), participant: LocalParticipant(participant), }, livekit::RoomEvent::LocalTrackSubscribed { track } => RoomEvent::LocalTrackSubscribed { track: local_track_from_livekit(track), }, livekit::RoomEvent::TrackSubscribed { track, publication, participant, } => RoomEvent::TrackSubscribed { track: remote_track_from_livekit(track), publication: RemoteTrackPublication(publication), participant: RemoteParticipant(participant), }, livekit::RoomEvent::TrackUnsubscribed { track, publication, participant, } => RoomEvent::TrackUnsubscribed { track: remote_track_from_livekit(track), publication: RemoteTrackPublication(publication), participant: RemoteParticipant(participant), }, livekit::RoomEvent::TrackSubscriptionFailed { participant, error: _, track_sid, } => RoomEvent::TrackSubscriptionFailed { participant: RemoteParticipant(participant), track_sid, }, livekit::RoomEvent::TrackPublished { publication, participant, } => RoomEvent::TrackPublished { publication: RemoteTrackPublication(publication), participant: RemoteParticipant(participant), }, livekit::RoomEvent::TrackUnpublished { publication, participant, } => RoomEvent::TrackUnpublished { publication: RemoteTrackPublication(publication), participant: RemoteParticipant(participant), }, livekit::RoomEvent::TrackMuted { participant, publication, } => RoomEvent::TrackMuted { publication: publication_from_livekit(publication), participant: participant_from_livekit(participant), }, livekit::RoomEvent::TrackUnmuted { participant, publication, } => RoomEvent::TrackUnmuted { publication: publication_from_livekit(publication), participant: participant_from_livekit(participant), }, livekit::RoomEvent::RoomMetadataChanged { old_metadata, metadata, } => RoomEvent::RoomMetadataChanged { old_metadata, metadata, }, livekit::RoomEvent::ParticipantMetadataChanged { participant, old_metadata, metadata, } => RoomEvent::ParticipantMetadataChanged { participant: participant_from_livekit(participant), old_metadata, metadata, }, livekit::RoomEvent::ParticipantNameChanged { participant, old_name, name, } => RoomEvent::ParticipantNameChanged { participant: participant_from_livekit(participant), old_name, name, }, livekit::RoomEvent::ParticipantAttributesChanged { participant, changed_attributes, } => RoomEvent::ParticipantAttributesChanged { participant: participant_from_livekit(participant), changed_attributes: changed_attributes.into_iter().collect(), }, livekit::RoomEvent::ActiveSpeakersChanged { speakers } => { RoomEvent::ActiveSpeakersChanged { speakers: speakers.into_iter().map(participant_from_livekit).collect(), } } livekit::RoomEvent::Connected { participants_with_tracks, } => RoomEvent::Connected { participants_with_tracks: participants_with_tracks .into_iter() .map({ |(p, t)| { ( RemoteParticipant(p), t.into_iter().map(RemoteTrackPublication).collect(), ) } }) .collect(), }, livekit::RoomEvent::Disconnected { reason } => RoomEvent::Disconnected { reason: reason.as_str_name(), }, livekit::RoomEvent::Reconnecting => RoomEvent::Reconnecting, livekit::RoomEvent::Reconnected => RoomEvent::Reconnected, _ => { log::trace!("dropping livekit event: {:?}", event); return None; } }; Some(event)}
use std::sync::Arc;
use anyhow::{Context as _, Result};
use collections::HashMap;
use futures::{SinkExt, channel::mpsc};
use gpui::{App, AsyncApp, ScreenCaptureSource, ScreenCaptureStream, Task};
use gpui_tokio::Tokio;
use playback::capture_local_video_track;
mod playback;
use crate::{LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication};
pub use playback::AudioStream;
pub(crate) use playback::{RemoteVideoFrame, play_remote_video_track};
#[derive(Clone, Debug)]
pub struct RemoteVideoTrack(livekit::track::RemoteVideoTrack);
#[derive(Clone, Debug)]
pub struct RemoteAudioTrack(livekit::track::RemoteAudioTrack);
#[derive(Clone, Debug)]
pub struct RemoteTrackPublication(livekit::publication::RemoteTrackPublication);
#[derive(Clone, Debug)]
pub struct RemoteParticipant(livekit::participant::RemoteParticipant);
#[derive(Clone, Debug)]
pub struct LocalVideoTrack(livekit::track::LocalVideoTrack);
#[derive(Clone, Debug)]
pub struct LocalAudioTrack(livekit::track::LocalAudioTrack);
#[derive(Clone, Debug)]
pub struct LocalTrackPublication(livekit::publication::LocalTrackPublication);
#[derive(Clone, Debug)]
pub struct LocalParticipant(livekit::participant::LocalParticipant);
pub struct Room {
room: livekit::Room,
_task: Task<()>,
playback: playback::AudioStack,
}
pub type TrackSid = livekit::id::TrackSid;
pub type ConnectionState = livekit::ConnectionState;
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct ParticipantIdentity(pub String);
impl Room {
pub async fn connect(
url: String,
token: String,
cx: &mut AsyncApp,
) -> Result<(Self, mpsc::UnboundedReceiver<RoomEvent>)> {
let connector =
tokio_tungstenite::Connector::Rustls(Arc::new(http_client_tls::tls_config()));
let mut config = livekit::RoomOptions::default();
config.connector = Some(connector);
let (room, mut events) = Tokio::spawn(cx, async move {
livekit::Room::connect(&url, &token, config).await
})?
.await??;
let (mut tx, rx) = mpsc::unbounded();
let task = cx.background_executor().spawn(async move {
while let Some(event) = events.recv().await {
if let Some(event) = room_event_from_livekit(event) {
tx.send(event).await.ok();
}
}
});
Ok((
Self {
room,
_task: task,
playback: playback::AudioStack::new(cx.background_executor().clone()),
},
rx,
))
}
pub fn local_participant(&self) -> LocalParticipant {
LocalParticipant(self.room.local_participant())
}
pub fn remote_participants(&self) -> HashMap<ParticipantIdentity, RemoteParticipant> {
self.room
.remote_participants()
.into_iter()
.map(|(k, v)| (ParticipantIdentity(k.0), RemoteParticipant(v)))
.collect()
}
pub fn connection_state(&self) -> ConnectionState {
self.room.connection_state()
}
pub async fn publish_local_microphone_track(
&self,
cx: &mut AsyncApp,
) -> Result<(LocalTrackPublication, playback::AudioStream)> {
let (track, stream) = self.playback.capture_local_microphone_track()?;
let publication = self
.local_participant()
.publish_track(
livekit::track::LocalTrack::Audio(track.0),
livekit::options::TrackPublishOptions {
source: livekit::track::TrackSource::Microphone,
..Default::default()
},
cx,
)
.await?;
Ok((publication, stream))
}
pub async fn unpublish_local_track(
&self,
sid: TrackSid,
cx: &mut AsyncApp,
) -> Result<LocalTrackPublication> {
self.local_participant().unpublish_track(sid, cx).await
}
pub fn play_remote_audio_track(
&self,
track: &RemoteAudioTrack,
_cx: &App,
) -> Result<playback::AudioStream> {
Ok(self.playback.play_remote_audio_track(&track.0))
}
}
impl LocalParticipant {
pub async fn publish_screenshare_track(
&self,
source: &dyn ScreenCaptureSource,
cx: &mut AsyncApp,
) -> Result<(LocalTrackPublication, Box<dyn ScreenCaptureStream>)> {
let (track, stream) = capture_local_video_track(source, cx).await?;
let options = livekit::options::TrackPublishOptions {
source: livekit::track::TrackSource::Screenshare,
video_codec: livekit::options::VideoCodec::VP8,
..Default::default()
};
let publication = self
.publish_track(livekit::track::LocalTrack::Video(track.0), options, cx)
.await?;
Ok((publication, stream))
}
async fn publish_track(
&self,
track: livekit::track::LocalTrack,
options: livekit::options::TrackPublishOptions,
cx: &mut AsyncApp,
) -> Result<LocalTrackPublication> {
let participant = self.0.clone();
Tokio::spawn(cx, async move {
participant.publish_track(track, options).await
})?
.await?
.map(LocalTrackPublication)
.context("publishing a track")
}
pub async fn unpublish_track(
&self,
sid: TrackSid,
cx: &mut AsyncApp,
) -> Result<LocalTrackPublication> {
let participant = self.0.clone();
Tokio::spawn(cx, async move { participant.unpublish_track(&sid).await })?
.await?
.map(LocalTrackPublication)
.context("unpublishing a track")
}
}
impl LocalTrackPublication {
pub fn mute(&self, cx: &App) {
let track = self.0.clone();
Tokio::spawn(cx, async move {
track.mute();
})
.detach();
}
pub fn unmute(&self, cx: &App) {
let track = self.0.clone();
Tokio::spawn(cx, async move {
track.unmute();
})
.detach();
}
pub fn sid(&self) -> TrackSid {
self.0.sid()
}
pub fn is_muted(&self) -> bool {
self.0.is_muted()
}
}
impl RemoteParticipant {
pub fn identity(&self) -> ParticipantIdentity {
ParticipantIdentity(self.0.identity().0)
}
pub fn track_publications(&self) -> HashMap<TrackSid, RemoteTrackPublication> {
self.0
.track_publications()
.into_iter()
.map(|(sid, publication)| (sid, RemoteTrackPublication(publication)))
.collect()
}
}
impl RemoteAudioTrack {
pub fn sid(&self) -> TrackSid {
self.0.sid()
}
}
impl RemoteVideoTrack {
pub fn sid(&self) -> TrackSid {
self.0.sid()
}
}
impl RemoteTrackPublication {
pub fn is_muted(&self) -> bool {
self.0.is_muted()
}
pub fn is_enabled(&self) -> bool {
self.0.is_enabled()
}
pub fn track(&self) -> Option<RemoteTrack> {
self.0.track().map(remote_track_from_livekit)
}
pub fn is_audio(&self) -> bool {
self.0.kind() == livekit::track::TrackKind::Audio
}
pub fn set_enabled(&self, enabled: bool, cx: &App) {
let track = self.0.clone();
Tokio::spawn(cx, async move { track.set_enabled(enabled) }).detach();
}
pub fn sid(&self) -> TrackSid {
self.0.sid()
}
}
impl Participant {
pub fn identity(&self) -> ParticipantIdentity {
match self {
Participant::Local(local_participant) => {
ParticipantIdentity(local_participant.0.identity().0)
}
Participant::Remote(remote_participant) => {
ParticipantIdentity(remote_participant.0.identity().0)
}
}
}
}
fn participant_from_livekit(participant: livekit::participant::Participant) -> Participant {
match participant {
livekit::participant::Participant::Local(local) => {
Participant::Local(LocalParticipant(local))
}
livekit::participant::Participant::Remote(remote) => {
Participant::Remote(RemoteParticipant(remote))
}
}
}
fn publication_from_livekit(
publication: livekit::publication::TrackPublication,
) -> TrackPublication {
match publication {
livekit::publication::TrackPublication::Local(local) => {
TrackPublication::Local(LocalTrackPublication(local))
}
livekit::publication::TrackPublication::Remote(remote) => {
TrackPublication::Remote(RemoteTrackPublication(remote))
}
}
}
fn remote_track_from_livekit(track: livekit::track::RemoteTrack) -> RemoteTrack {
match track {
livekit::track::RemoteTrack::Audio(audio) => RemoteTrack::Audio(RemoteAudioTrack(audio)),
livekit::track::RemoteTrack::Video(video) => RemoteTrack::Video(RemoteVideoTrack(video)),
}
}
fn local_track_from_livekit(track: livekit::track::LocalTrack) -> LocalTrack {
match track {
livekit::track::LocalTrack::Audio(audio) => LocalTrack::Audio(LocalAudioTrack(audio)),
livekit::track::LocalTrack::Video(video) => LocalTrack::Video(LocalVideoTrack(video)),
}
}
fn room_event_from_livekit(event: livekit::RoomEvent) -> Option<RoomEvent> {
let event = match event {
livekit::RoomEvent::ParticipantConnected(remote_participant) => {
RoomEvent::ParticipantConnected(RemoteParticipant(remote_participant))
}
livekit::RoomEvent::ParticipantDisconnected(remote_participant) => {
RoomEvent::ParticipantDisconnected(RemoteParticipant(remote_participant))
}
livekit::RoomEvent::LocalTrackPublished {
publication,
track,
participant,
} => RoomEvent::LocalTrackPublished {
publication: LocalTrackPublication(publication),
track: local_track_from_livekit(track),
participant: LocalParticipant(participant),
},
livekit::RoomEvent::LocalTrackUnpublished {
publication,
participant,
} => RoomEvent::LocalTrackUnpublished {
publication: LocalTrackPublication(publication),
participant: LocalParticipant(participant),
},
livekit::RoomEvent::LocalTrackSubscribed { track } => RoomEvent::LocalTrackSubscribed {
track: local_track_from_livekit(track),
},
livekit::RoomEvent::TrackSubscribed {
track,
publication,
participant,
} => RoomEvent::TrackSubscribed {
track: remote_track_from_livekit(track),
publication: RemoteTrackPublication(publication),
participant: RemoteParticipant(participant),
},
livekit::RoomEvent::TrackUnsubscribed {
track,
publication,
participant,
} => RoomEvent::TrackUnsubscribed {
track: remote_track_from_livekit(track),
publication: RemoteTrackPublication(publication),
participant: RemoteParticipant(participant),
},
livekit::RoomEvent::TrackSubscriptionFailed {
participant,
error: _,
track_sid,
} => RoomEvent::TrackSubscriptionFailed {
participant: RemoteParticipant(participant),
track_sid,
},
livekit::RoomEvent::TrackPublished {
publication,
participant,
} => RoomEvent::TrackPublished {
publication: RemoteTrackPublication(publication),
participant: RemoteParticipant(participant),
},
livekit::RoomEvent::TrackUnpublished {
publication,
participant,
} => RoomEvent::TrackUnpublished {
publication: RemoteTrackPublication(publication),
participant: RemoteParticipant(participant),
},
livekit::RoomEvent::TrackMuted {
participant,
publication,
} => RoomEvent::TrackMuted {
publication: publication_from_livekit(publication),
participant: participant_from_livekit(participant),
},
livekit::RoomEvent::TrackUnmuted {
participant,
publication,
} => RoomEvent::TrackUnmuted {
publication: publication_from_livekit(publication),
participant: participant_from_livekit(participant),
},
livekit::RoomEvent::RoomMetadataChanged {
old_metadata,
metadata,
} => RoomEvent::RoomMetadataChanged {
old_metadata,
metadata,
},
livekit::RoomEvent::ParticipantMetadataChanged {
participant,
old_metadata,
metadata,
} => RoomEvent::ParticipantMetadataChanged {
participant: participant_from_livekit(participant),
old_metadata,
metadata,
},
livekit::RoomEvent::ParticipantNameChanged {
participant,
old_name,
name,
} => RoomEvent::ParticipantNameChanged {
participant: participant_from_livekit(participant),
old_name,
name,
},
livekit::RoomEvent::ParticipantAttributesChanged {
participant,
changed_attributes,
} => RoomEvent::ParticipantAttributesChanged {
participant: participant_from_livekit(participant),
changed_attributes: changed_attributes.into_iter().collect(),
},
livekit::RoomEvent::ActiveSpeakersChanged { speakers } => {
RoomEvent::ActiveSpeakersChanged {
speakers: speakers.into_iter().map(participant_from_livekit).collect(),
}
}
livekit::RoomEvent::Connected {
participants_with_tracks,
} => RoomEvent::Connected {
participants_with_tracks: participants_with_tracks
.into_iter()
.map({
|(p, t)| {
(
RemoteParticipant(p),
t.into_iter().map(RemoteTrackPublication).collect(),
)
}
})
.collect(),
},
livekit::RoomEvent::Disconnected { reason } => RoomEvent::Disconnected {
reason: reason.as_str_name(),
},
livekit::RoomEvent::Reconnecting => RoomEvent::Reconnecting,
livekit::RoomEvent::Reconnected => RoomEvent::Reconnected,
_ => {
log::trace!("dropping livekit event: {:?}", event);
return None;
}
};
Some(event)
}