Add mute toggling controls
This commit is contained in:
parent
3e3079b513
commit
e58f0ac72f
5 changed files with 189 additions and 61 deletions
|
@ -154,8 +154,8 @@ impl Room {
|
||||||
|
|
||||||
Some(LiveKitRoom {
|
Some(LiveKitRoom {
|
||||||
room,
|
room,
|
||||||
screen_track: Track::None,
|
screen_track: LocalTrack::None,
|
||||||
microphone_track: Track::None,
|
microphone_track: LocalTrack::None,
|
||||||
next_publish_id: 0,
|
next_publish_id: 0,
|
||||||
_maintain_room,
|
_maintain_room,
|
||||||
_maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks],
|
_maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks],
|
||||||
|
@ -985,13 +985,13 @@ impl Room {
|
||||||
|
|
||||||
pub fn is_screen_sharing(&self) -> bool {
|
pub fn is_screen_sharing(&self) -> bool {
|
||||||
self.live_kit.as_ref().map_or(false, |live_kit| {
|
self.live_kit.as_ref().map_or(false, |live_kit| {
|
||||||
!matches!(live_kit.screen_track, Track::None)
|
!matches!(live_kit.screen_track, LocalTrack::None)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_sharing_mic(&self) -> bool {
|
pub fn is_sharing_mic(&self) -> bool {
|
||||||
self.live_kit.as_ref().map_or(false, |live_kit| {
|
self.live_kit.as_ref().map_or(false, |live_kit| {
|
||||||
!matches!(live_kit.microphone_track, Track::None)
|
!matches!(live_kit.microphone_track, LocalTrack::None)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1004,7 +1004,10 @@ impl Room {
|
||||||
|
|
||||||
let publish_id = if let Some(live_kit) = self.live_kit.as_mut() {
|
let publish_id = if let Some(live_kit) = self.live_kit.as_mut() {
|
||||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||||
live_kit.microphone_track = Track::Pending { publish_id };
|
live_kit.microphone_track = LocalTrack::Pending {
|
||||||
|
publish_id,
|
||||||
|
muted: false,
|
||||||
|
};
|
||||||
cx.notify();
|
cx.notify();
|
||||||
publish_id
|
publish_id
|
||||||
} else {
|
} else {
|
||||||
|
@ -1034,13 +1037,14 @@ impl Room {
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?;
|
.ok_or_else(|| anyhow!("live-kit was not initialized"))?;
|
||||||
|
|
||||||
let canceled = if let Track::Pending {
|
let (canceled, muted) = if let LocalTrack::Pending {
|
||||||
publish_id: cur_publish_id,
|
publish_id: cur_publish_id,
|
||||||
|
muted
|
||||||
} = &live_kit.microphone_track
|
} = &live_kit.microphone_track
|
||||||
{
|
{
|
||||||
*cur_publish_id != publish_id
|
(*cur_publish_id != publish_id, *muted)
|
||||||
} else {
|
} else {
|
||||||
true
|
(true, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
match publication {
|
match publication {
|
||||||
|
@ -1048,7 +1052,13 @@ impl Room {
|
||||||
if canceled {
|
if canceled {
|
||||||
live_kit.room.unpublish_track(publication);
|
live_kit.room.unpublish_track(publication);
|
||||||
} else {
|
} else {
|
||||||
live_kit.microphone_track = Track::Published(publication);
|
if muted {
|
||||||
|
cx.background().spawn(publication.mute()).detach();
|
||||||
|
}
|
||||||
|
live_kit.microphone_track = LocalTrack::Published {
|
||||||
|
track_publication: publication,
|
||||||
|
muted
|
||||||
|
};
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1057,7 +1067,7 @@ impl Room {
|
||||||
if canceled {
|
if canceled {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
live_kit.microphone_track = Track::None;
|
live_kit.microphone_track = LocalTrack::None;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
Err(error)
|
Err(error)
|
||||||
}
|
}
|
||||||
|
@ -1076,7 +1086,10 @@ impl Room {
|
||||||
|
|
||||||
let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
|
let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
|
||||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||||
live_kit.screen_track = Track::Pending { publish_id };
|
live_kit.screen_track = LocalTrack::Pending {
|
||||||
|
publish_id,
|
||||||
|
muted: false,
|
||||||
|
};
|
||||||
cx.notify();
|
cx.notify();
|
||||||
(live_kit.room.display_sources(), publish_id)
|
(live_kit.room.display_sources(), publish_id)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1110,13 +1123,14 @@ impl Room {
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?;
|
.ok_or_else(|| anyhow!("live-kit was not initialized"))?;
|
||||||
|
|
||||||
let canceled = if let Track::Pending {
|
let (canceled, muted) = if let LocalTrack::Pending {
|
||||||
publish_id: cur_publish_id,
|
publish_id: cur_publish_id,
|
||||||
|
muted,
|
||||||
} = &live_kit.screen_track
|
} = &live_kit.screen_track
|
||||||
{
|
{
|
||||||
*cur_publish_id != publish_id
|
(*cur_publish_id != publish_id, *muted)
|
||||||
} else {
|
} else {
|
||||||
true
|
(true, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
match publication {
|
match publication {
|
||||||
|
@ -1124,7 +1138,13 @@ impl Room {
|
||||||
if canceled {
|
if canceled {
|
||||||
live_kit.room.unpublish_track(publication);
|
live_kit.room.unpublish_track(publication);
|
||||||
} else {
|
} else {
|
||||||
live_kit.screen_track = Track::Published(publication);
|
if muted {
|
||||||
|
cx.background().spawn(publication.mute()).detach();
|
||||||
|
}
|
||||||
|
live_kit.screen_track = LocalTrack::Published {
|
||||||
|
track_publication: publication,
|
||||||
|
muted,
|
||||||
|
};
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1133,7 +1153,7 @@ impl Room {
|
||||||
if canceled {
|
if canceled {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
live_kit.screen_track = Track::None;
|
live_kit.screen_track = LocalTrack::None;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
Err(error)
|
Err(error)
|
||||||
}
|
}
|
||||||
|
@ -1143,13 +1163,33 @@ impl Room {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
|
||||||
// https://docs.livekit.io/client/publish/
|
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||||
// Should be acessible from local participant / publication
|
match &mut live_kit.microphone_track {
|
||||||
todo!();
|
LocalTrack::None => Err(anyhow!("microphone was not shared")),
|
||||||
|
LocalTrack::Pending { muted, .. } => {
|
||||||
|
*muted = !*muted;
|
||||||
|
Ok(Task::Ready(Some(Ok(()))))
|
||||||
|
}
|
||||||
|
LocalTrack::Published {
|
||||||
|
track_publication,
|
||||||
|
muted,
|
||||||
|
} => {
|
||||||
|
*muted = !*muted;
|
||||||
|
|
||||||
|
if *muted {
|
||||||
|
Ok(cx.background().spawn(track_publication.mute()))
|
||||||
|
} else {
|
||||||
|
Ok(cx.background().spawn(track_publication.unmute()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("LiveKit not started"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
pub fn toggle_deafen(&mut self, _cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
// iterate through publications and mute (?????)
|
// iterate through publications and mute (?????)
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
|
@ -1164,13 +1204,15 @@ impl Room {
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?;
|
.ok_or_else(|| anyhow!("live-kit was not initialized"))?;
|
||||||
match mem::take(&mut live_kit.screen_track) {
|
match mem::take(&mut live_kit.screen_track) {
|
||||||
Track::None => Err(anyhow!("screen was not shared")),
|
LocalTrack::None => Err(anyhow!("screen was not shared")),
|
||||||
Track::Pending { .. } => {
|
LocalTrack::Pending { .. } => {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Track::Published(track) => {
|
LocalTrack::Published {
|
||||||
live_kit.room.unpublish_track(track);
|
track_publication, ..
|
||||||
|
} => {
|
||||||
|
live_kit.room.unpublish_track(track_publication);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1189,20 +1231,26 @@ impl Room {
|
||||||
|
|
||||||
struct LiveKitRoom {
|
struct LiveKitRoom {
|
||||||
room: Arc<live_kit_client::Room>,
|
room: Arc<live_kit_client::Room>,
|
||||||
screen_track: Track,
|
screen_track: LocalTrack,
|
||||||
microphone_track: Track,
|
microphone_track: LocalTrack,
|
||||||
next_publish_id: usize,
|
next_publish_id: usize,
|
||||||
_maintain_room: Task<()>,
|
_maintain_room: Task<()>,
|
||||||
_maintain_tracks: [Task<()>; 2],
|
_maintain_tracks: [Task<()>; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Track {
|
enum LocalTrack {
|
||||||
None,
|
None,
|
||||||
Pending { publish_id: usize },
|
Pending {
|
||||||
Published(LocalTrackPublication),
|
publish_id: usize,
|
||||||
|
muted: bool,
|
||||||
|
},
|
||||||
|
Published {
|
||||||
|
track_publication: LocalTrackPublication,
|
||||||
|
muted: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Track {
|
impl Default for LocalTrack {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::None
|
Self::None
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,10 @@ mod notifications;
|
||||||
mod project_shared_notification;
|
mod project_shared_notification;
|
||||||
mod sharing_status_indicator;
|
mod sharing_status_indicator;
|
||||||
|
|
||||||
use call::ActiveCall;
|
use call::{ActiveCall, Room};
|
||||||
pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu};
|
pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu};
|
||||||
use gpui::{actions, AppContext, Task};
|
use gpui::{actions, AppContext, Task};
|
||||||
|
use util::ResultExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
|
||||||
|
@ -46,20 +47,12 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
|
||||||
|
|
||||||
pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
|
pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
|
||||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||||
let toggle_mut = room.update(cx, |room, cx| {
|
room.update(cx, Room::toggle_mute).map(Task::detach).log_err();
|
||||||
if room.is_sharing_mic() {
|
|
||||||
room.toggle_mute(cx)
|
|
||||||
} else {
|
|
||||||
room.share_mic(cx)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
toggle_mut.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
||||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||||
let toggle_deafan = room.update(cx, |room, cx| room.toggle_deafen(cx));
|
room.update(cx, Room::toggle_deafen).detach_and_log_err(cx);
|
||||||
toggle_deafan.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,19 +201,6 @@ public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer)
|
||||||
return Unmanaged.passRetained(track).toOpaque()
|
return Unmanaged.passRetained(track).toOpaque()
|
||||||
}
|
}
|
||||||
|
|
||||||
@_cdecl("LKRemoteAudioTrackStart")
|
|
||||||
public func LKRemoteAudioTrackStart(track: UnsafeRawPointer, onStart: @escaping @convention(c) (UnsafeRawPointer, Bool) -> Void, callbackData: UnsafeRawPointer) {
|
|
||||||
let track = Unmanaged<Track>.fromOpaque(track).takeUnretainedValue() as! RemoteAudioTrack
|
|
||||||
|
|
||||||
track.start().then { success in
|
|
||||||
onStart(callbackData, success)
|
|
||||||
}
|
|
||||||
.catch { _ in
|
|
||||||
onStart(callbackData, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@_cdecl("LKVideoRendererCreate")
|
@_cdecl("LKVideoRendererCreate")
|
||||||
public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer {
|
public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer {
|
||||||
Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque()
|
Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque()
|
||||||
|
@ -247,3 +234,34 @@ public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @conven
|
||||||
callback(data, nil, error.localizedDescription as CFString)
|
callback(data, nil, error.localizedDescription as CFString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_cdecl("LKLocalTrackPublicationMute")
|
||||||
|
public func LKLocalTrackPublicationMute(
|
||||||
|
publication: UnsafeRawPointer,
|
||||||
|
on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void,
|
||||||
|
callback_data: UnsafeRawPointer
|
||||||
|
) {
|
||||||
|
let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue()
|
||||||
|
|
||||||
|
publication.mute().then {
|
||||||
|
on_complete(callback_data, nil)
|
||||||
|
}.catch { error in
|
||||||
|
on_complete(callback_data, error.localizedDescription as CFString)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@_cdecl("LKLocalTrackPublicationUnmute")
|
||||||
|
public func LKLocalTrackPublicationUnmute(
|
||||||
|
publication: UnsafeRawPointer,
|
||||||
|
on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void,
|
||||||
|
callback_data: UnsafeRawPointer
|
||||||
|
) {
|
||||||
|
let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue()
|
||||||
|
|
||||||
|
publication.unmute().then {
|
||||||
|
on_complete(callback_data, nil)
|
||||||
|
}.catch { error in
|
||||||
|
on_complete(callback_data, error.localizedDescription as CFString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -84,12 +84,6 @@ extern "C" {
|
||||||
) -> *const c_void;
|
) -> *const c_void;
|
||||||
|
|
||||||
fn LKRemoteAudioTrackGetSid(track: *const c_void) -> CFStringRef;
|
fn LKRemoteAudioTrackGetSid(track: *const c_void) -> CFStringRef;
|
||||||
// fn LKRemoteAudioTrackStart(
|
|
||||||
// track: *const c_void,
|
|
||||||
// callback: extern "C" fn(*mut c_void, bool),
|
|
||||||
// callback_data: *mut c_void
|
|
||||||
// );
|
|
||||||
|
|
||||||
fn LKVideoTrackAddRenderer(track: *const c_void, renderer: *const c_void);
|
fn LKVideoTrackAddRenderer(track: *const c_void, renderer: *const c_void);
|
||||||
fn LKRemoteVideoTrackGetSid(track: *const c_void) -> CFStringRef;
|
fn LKRemoteVideoTrackGetSid(track: *const c_void) -> CFStringRef;
|
||||||
|
|
||||||
|
@ -103,6 +97,17 @@ extern "C" {
|
||||||
);
|
);
|
||||||
fn LKCreateScreenShareTrackForDisplay(display: *const c_void) -> *const c_void;
|
fn LKCreateScreenShareTrackForDisplay(display: *const c_void) -> *const c_void;
|
||||||
fn LKLocalAudioTrackCreateTrack() -> *const c_void;
|
fn LKLocalAudioTrackCreateTrack() -> *const c_void;
|
||||||
|
|
||||||
|
fn LKLocalTrackPublicationMute(
|
||||||
|
publication: *const c_void,
|
||||||
|
on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
);
|
||||||
|
fn LKLocalTrackPublicationUnmute(
|
||||||
|
publication: *const c_void,
|
||||||
|
on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Sid = String;
|
pub type Sid = String;
|
||||||
|
@ -525,6 +530,56 @@ impl Drop for LocalVideoTrack {
|
||||||
|
|
||||||
pub struct LocalTrackPublication(*const c_void);
|
pub struct LocalTrackPublication(*const c_void);
|
||||||
|
|
||||||
|
impl LocalTrackPublication {
|
||||||
|
pub fn mute(&self) -> impl Future<Output = Result<()>> {
|
||||||
|
let (tx, rx) = futures::channel::oneshot::channel();
|
||||||
|
|
||||||
|
extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
|
||||||
|
let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
|
||||||
|
if error.is_null() {
|
||||||
|
tx.send(Ok(())).ok();
|
||||||
|
} else {
|
||||||
|
let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
|
||||||
|
tx.send(Err(anyhow!(error))).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
LKLocalTrackPublicationMute(
|
||||||
|
self.0,
|
||||||
|
complete_callback,
|
||||||
|
Box::into_raw(Box::new(tx)) as *mut c_void,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async move { rx.await.unwrap() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmute(&self) -> impl Future<Output = Result<()>> {
|
||||||
|
let (tx, rx) = futures::channel::oneshot::channel();
|
||||||
|
|
||||||
|
extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
|
||||||
|
let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
|
||||||
|
if error.is_null() {
|
||||||
|
tx.send(Ok(())).ok();
|
||||||
|
} else {
|
||||||
|
let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
|
||||||
|
tx.send(Err(anyhow!(error))).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
LKLocalTrackPublicationUnmute(
|
||||||
|
self.0,
|
||||||
|
complete_callback,
|
||||||
|
Box::into_raw(Box::new(tx)) as *mut c_void,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async move { rx.await.unwrap() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for LocalTrackPublication {
|
impl Drop for LocalTrackPublication {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe { CFRelease(self.0) }
|
unsafe { CFRelease(self.0) }
|
||||||
|
|
|
@ -475,6 +475,20 @@ impl Drop for Room {
|
||||||
|
|
||||||
pub struct LocalTrackPublication;
|
pub struct LocalTrackPublication;
|
||||||
|
|
||||||
|
impl LocalTrackPublication {
|
||||||
|
pub fn mute(&self) -> impl Future<Output = Result<()>> {
|
||||||
|
async {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmute(&self) -> impl Future<Output = Result<()>> {
|
||||||
|
async {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LocalVideoTrack {
|
pub struct LocalVideoTrack {
|
||||||
frames_rx: async_broadcast::Receiver<Frame>,
|
frames_rx: async_broadcast::Receiver<Frame>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue