diff --git a/.config/hakari.toml b/.config/hakari.toml index 93e696ca6f1ae876a0be4b905bd3840ee32fdc60..b1e2954743b404f088c71c28aad1d6a699a22aeb 100644 --- a/.config/hakari.toml +++ b/.config/hakari.toml @@ -26,7 +26,7 @@ third-party = [ # build of remote_server should not include scap / its x11 dependency { name = "scap", git = "https://github.com/zed-industries/scap", rev = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7" }, # build of remote_server should not need to include on libalsa through rodio - # { name = "rodio" }, + { name = "rodio", git = "https://github.com/RustAudio/rodio", branch = "better_wav_output"}, ] [final-excludes] diff --git a/Cargo.lock b/Cargo.lock index 0b283601d1c182d83f5f7164c7bd545e6f80eb9c..43bcbf9672475253b976fbd7d01c0b2eb697e612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1388,7 +1388,6 @@ dependencies = [ "async-tar", "collections", "crossbeam", - "futures 0.3.31", "gpui", "libwebrtc", "log", @@ -2618,6 +2617,7 @@ dependencies = [ "audio", "client", "collections", + "feature_flags", "fs", "futures 0.3.31", "gpui", @@ -9418,7 +9418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -9672,6 +9672,7 @@ dependencies = [ "scap", "serde", "serde_json", + "serde_urlencoded", "settings", "sha2", "simplelog", @@ -13882,7 +13883,6 @@ dependencies = [ "rtrb", "symphonia", "thiserror 2.0.12", - "tracing", ] [[package]] @@ -18960,7 +18960,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -19997,6 +19997,7 @@ dependencies = [ "libsqlite3-sys", "linux-raw-sys 0.4.15", "linux-raw-sys 0.9.4", + "livekit-runtime", "log", "lyon", "lyon_path", @@ -20551,7 +20552,6 @@ dependencies = [ "languages", "libc", "line_ending_selector", - "livekit_client", "log", "markdown", "markdown_preview", diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index ce5f7da0f93a4c2dd40a70f33af3ba9cefcdb970..08e0df424dcdaa15cfd78fddaf5758fb9b8d7e0b 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -19,9 +19,8 @@ collections.workspace = true crossbeam.workspace = true gpui.workspace = true log.workspace = true -futures.workspace = true parking_lot.workspace = true -rodio = { workspace = true, features = [ "wav", "playback" ] } +rodio = { workspace = true, features = [ "wav", "playback", "wav_output" ] } schemars.workspace = true serde.workspace = true settings.workspace = true diff --git a/crates/audio/src/audio.rs b/crates/audio/src/audio.rs index ae5489b6cb11169ca717c2a18591856bc04745b9..511d00671ae99789610bac1f7e30b63ca29ac480 100644 --- a/crates/audio/src/audio.rs +++ b/crates/audio/src/audio.rs @@ -1,8 +1,7 @@ use anyhow::{Context as _, Result}; use collections::HashMap; -use futures::channel::mpsc::UnboundedSender; use gpui::{App, AsyncApp, BackgroundExecutor, BorrowAppContext, Global}; -use libwebrtc::{native::apm, prelude::AudioFrame}; +use libwebrtc::native::apm; use log::info; use parking_lot::Mutex; use rodio::{ @@ -14,18 +13,13 @@ use rodio::{ }; use settings::Settings; use std::{ - borrow::Cow, io::Cursor, num::NonZero, path::PathBuf, - sync::{ - Arc, - mpsc::{TryRecvError, channel}, - }, - thread, + sync::{Arc, atomic::Ordering}, time::Duration, }; -use util::{ResultExt, debug_panic}; +use util::ResultExt; mod audio_settings; mod replays; @@ -33,6 +27,8 @@ mod rodio_ext; pub use audio_settings::AudioSettings; pub use rodio_ext::RodioExt; +use crate::audio_settings::LIVE_SETTINGS; + // NOTE: We used to use WebRTC's mixer which only supported // 16kHz, 32kHz and 48kHz. As 48 is the most common "next step up" // for audio output devices like speakers/bluetooth, we just hard-code @@ -42,13 +38,14 @@ pub use rodio_ext::RodioExt; // that in the future. pub const SAMPLE_RATE: NonZero = nz!(48000); pub const CHANNEL_COUNT: NonZero = nz!(2); -const BUFFER_SIZE: usize = // echo canceller and livekit want 10ms of audio +pub const BUFFER_SIZE: usize = // echo canceller and livekit want 10ms of audio (SAMPLE_RATE.get() as usize / 100) * CHANNEL_COUNT.get() as usize; pub const REPLAY_DURATION: Duration = Duration::from_secs(30); pub fn init(cx: &mut App) { AudioSettings::register(cx); + LIVE_SETTINGS.initialize(cx); } #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] @@ -142,96 +139,72 @@ impl Audio { self.replays.replays_to_tar(executor) } - pub fn open_microphone( - cx: AsyncApp, - frame_tx: UnboundedSender>, - ) -> anyhow::Result<()> { - let (apm, mut replays) = cx.try_read_default_global::(|audio, _| { - (Arc::clone(&audio.echo_canceller), audio.replays.clone()) - })?; - - let (stream_error_tx, stream_error_rx) = channel(); - thread::spawn(move || { - let stream = rodio::microphone::MicrophoneBuilder::new() - .default_device()? - .default_config()? - .prefer_sample_rates([SAMPLE_RATE, SAMPLE_RATE.saturating_mul(nz!(2))]) - .prefer_channel_counts([nz!(1), nz!(2)]) - .prefer_buffer_sizes(512..) - .open_stream()?; - info!("Opened microphone: {:?}", stream.config()); - - let stream = UniformSourceIterator::new(stream, CHANNEL_COUNT, SAMPLE_RATE) - .limit(LimitSettings::live_performance()) - .process_buffer::(|buffer| { - let mut int_buffer: [i16; _] = buffer.map(|s| s.to_sample()); - if let Err(e) = apm - .lock() - .process_stream( - &mut int_buffer, - SAMPLE_RATE.get() as i32, - CHANNEL_COUNT.get() as i32, - ) - .context("livekit audio processor error") - { - let _ = stream_error_tx.send(e); - } else { - for (sample, processed) in buffer.iter_mut().zip(&int_buffer) { - *sample = (*processed).to_sample(); - } - } - }) - .automatic_gain_control(1.0, 4.0, 0.0, 5.0) - .periodic_access(Duration::from_millis(100), move |agc_source| { - agc_source.set_enabled(true); // todo dvdsk how to get settings in here? - }); - - // todo dvdsk keep the above here, move the rest back to livekit? - let (replay, mut stream) = stream.replayable(REPLAY_DURATION); - replays.add_voip_stream("local microphone".to_string(), replay); - - loop { - let sampled: Vec<_> = stream - .by_ref() - .take(BUFFER_SIZE) - .map(|s| s.to_sample()) - .collect(); + pub fn open_microphone(voip_parts: VoipParts) -> anyhow::Result { + let stream = rodio::microphone::MicrophoneBuilder::new() + .default_device()? + .default_config()? + .prefer_sample_rates([SAMPLE_RATE, SAMPLE_RATE.saturating_mul(nz!(2))]) + .prefer_channel_counts([nz!(1), nz!(2)]) + .prefer_buffer_sizes(512..) + .open_stream()?; + info!("Opened microphone: {:?}", stream.config()); - match stream_error_rx.try_recv() { - Ok(apm_error) => return Err::<(), _>(apm_error), - Err(TryRecvError::Disconnected) => { - debug_panic!("Stream should end on its own without sending an error") + let (replay, stream) = UniformSourceIterator::new(stream, CHANNEL_COUNT, SAMPLE_RATE) + .limit(LimitSettings::live_performance()) + .process_buffer::(move |buffer| { + let mut int_buffer: [i16; _] = buffer.map(|s| s.to_sample()); + if voip_parts + .echo_canceller + .lock() + .process_stream( + &mut int_buffer, + SAMPLE_RATE.get() as i32, + CHANNEL_COUNT.get() as i32, + ) + .context("livekit audio processor error") + .log_err() + .is_some() + { + for (sample, processed) in buffer.iter_mut().zip(&int_buffer) { + *sample = (*processed).to_sample(); } - Err(TryRecvError::Empty) => (), } + }) + .automatic_gain_control(1.0, 4.0, 0.0, 5.0) + .periodic_access(Duration::from_millis(100), move |agc_source| { + agc_source.set_enabled(LIVE_SETTINGS.control_input_volume.load(Ordering::Relaxed)); + }) + .replayable(REPLAY_DURATION) + .expect("REPLAY_DURATION is longer then 100ms"); - frame_tx - .unbounded_send(AudioFrame { - sample_rate: SAMPLE_RATE.get(), - num_channels: CHANNEL_COUNT.get() as u32, - samples_per_channel: sampled.len() as u32 / CHANNEL_COUNT.get() as u32, - data: Cow::Owned(sampled), - }) - .context("Failed to send audio frame")? - } - }); - - Ok(()) + voip_parts + .replays + .add_voip_stream("local microphone".to_string(), replay); + Ok(stream) } pub fn play_voip_stream( - stream_source: impl rodio::Source + Send + 'static, - stream_name: String, + source: impl rodio::Source + Send + 'static, + speaker_name: String, + is_staff: bool, cx: &mut App, ) -> anyhow::Result<()> { - let (replay_source, source) = stream_source.replayable(REPLAY_DURATION); + let (replay_source, source) = source + .automatic_gain_control(1.0, 4.0, 0.0, 5.0) + .periodic_access(Duration::from_millis(100), move |agc_source| { + agc_source.set_enabled(LIVE_SETTINGS.control_input_volume.load(Ordering::Relaxed)); + }) + .replayable(REPLAY_DURATION) + .expect("REPLAY_DURATION is longer then 100ms"); cx.update_default_global(|this: &mut Self, _cx| { let output_mixer = this .ensure_output_exists() .context("Could not get output mixer")?; output_mixer.add(source); - this.replays.add_voip_stream(stream_name, replay_source); + if is_staff { + this.replays.add_voip_stream(speaker_name, replay_source); + } Ok(()) }) } @@ -275,3 +248,21 @@ impl Audio { Ok(source) } } + +pub struct VoipParts { + echo_canceller: Arc>, + replays: replays::Replays, +} + +impl VoipParts { + pub fn new(cx: &AsyncApp) -> anyhow::Result { + let (apm, replays) = cx.try_read_default_global::(|audio, _| { + (Arc::clone(&audio.echo_canceller), audio.replays.clone()) + })?; + + Ok(Self { + echo_canceller: apm, + replays, + }) + } +} diff --git a/crates/audio/src/audio_settings.rs b/crates/audio/src/audio_settings.rs index 08ac40c96092b829c270f09fcea892147c4e59ef..43edb8d60d96122d5515ec7274a6b5725b247ca0 100644 --- a/crates/audio/src/audio_settings.rs +++ b/crates/audio/src/audio_settings.rs @@ -1,17 +1,29 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + use anyhow::Result; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsKey, SettingsSources, SettingsUi}; +use settings::{Settings, SettingsKey, SettingsSources, SettingsStore, SettingsUi}; #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] pub struct AudioSettings { /// Opt into the new audio system. #[serde(rename = "experimental.rodio_audio", default)] pub rodio_audio: bool, // default is false - /// Opt into the new audio systems automatic gain control - #[serde(rename = "experimental.automatic_volume", default)] - pub automatic_volume: bool, + /// Requires 'rodio_audio: true' + /// + /// Use the new audio systems automatic gain control for your microphone. + /// This affects how loud you sound to others. + #[serde(rename = "experimental.control_input_volume", default)] + pub control_input_volume: bool, + /// Requires 'rodio_audio: true' + /// + /// Use the new audio systems automatic gain control on everyone in the + /// call. This makes call members who are too quite louder and those who are + /// too loud quieter. This only affects how things sound for you. + #[serde(rename = "experimental.control_output_volume", default)] + pub control_output_volume: bool, } /// Configuration of audio in Zed. @@ -19,13 +31,22 @@ pub struct AudioSettings { #[serde(default)] #[settings_key(key = "audio")] pub struct AudioSettingsContent { - /// Whether to use the experimental audio system + /// Opt into the new audio system. #[serde(rename = "experimental.rodio_audio", default)] - pub rodio_audio: bool, - /// Whether the experimental audio systems should automatically - /// manage the volume of calls - #[serde(rename = "experimental.automatic_volume", default)] - pub automatic_volume: bool, + pub rodio_audio: bool, // default is false + /// Requires 'rodio_audio: true' + /// + /// Use the new audio systems automatic gain control for your microphone. + /// This affects how loud you sound to others. + #[serde(rename = "experimental.control_input_volume", default)] + pub control_input_volume: bool, + /// Requires 'rodio_audio: true' + /// + /// Use the new audio systems automatic gain control on everyone in the + /// call. This makes call members who are too quite louder and those who are + /// too loud quieter. This only affects how things sound for you. + #[serde(rename = "experimental.control_output_volume", default)] + pub control_output_volume: bool, } impl Settings for AudioSettings { @@ -37,3 +58,39 @@ impl Settings for AudioSettings { fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} } + +pub(crate) struct LiveSettings { + pub(crate) control_input_volume: AtomicBool, + pub(crate) control_output_volume: AtomicBool, +} + +impl LiveSettings { + pub(crate) fn initialize(&self, cx: &mut App) { + cx.observe_global::(move |cx| { + LIVE_SETTINGS.control_input_volume.store( + AudioSettings::get_global(cx).control_input_volume, + Ordering::Relaxed, + ); + LIVE_SETTINGS.control_output_volume.store( + AudioSettings::get_global(cx).control_output_volume, + Ordering::Relaxed, + ); + }) + .detach(); + + let init_settings = AudioSettings::get_global(cx); + LIVE_SETTINGS + .control_input_volume + .store(init_settings.control_input_volume, Ordering::Relaxed); + LIVE_SETTINGS + .control_output_volume + .store(init_settings.control_output_volume, Ordering::Relaxed); + } +} + +/// Allows access to settings from the audio thread. Updated by +/// observer of SettingsStore. +pub(crate) static LIVE_SETTINGS: LiveSettings = LiveSettings { + control_input_volume: AtomicBool::new(true), + control_output_volume: AtomicBool::new(true), +}; diff --git a/crates/audio/src/replays.rs b/crates/audio/src/replays.rs index ae32696bfa9d9cafb96ed810278d442915a672da..bb21df51e5642bf633d068d544690cb26a239151 100644 --- a/crates/audio/src/replays.rs +++ b/crates/audio/src/replays.rs @@ -14,12 +14,9 @@ use crate::{REPLAY_DURATION, rodio_ext::Replay}; pub(crate) struct Replays(Arc>>); impl Replays { - pub(crate) fn add_voip_stream(&mut self, stream_name: String, source: Replay) { + pub(crate) fn add_voip_stream(&self, stream_name: String, source: Replay) { let mut map = self.0.lock(); map.retain(|_, replay| replay.source_is_active()); - // on the old pipeline all the streams are named microphone - // make sure names dont collide in that case by adding a number. - let stream_name = stream_name + &map.len().to_string(); map.insert(stream_name, source); } diff --git a/crates/audio/src/rodio_ext.rs b/crates/audio/src/rodio_ext.rs index c23e48a5592cd97cfc19bc72f84c7f34954acafc..4e9430a0b9462448b879f653f9ddcb06ef892cdb 100644 --- a/crates/audio/src/rodio_ext.rs +++ b/crates/audio/src/rodio_ext.rs @@ -1,7 +1,7 @@ use std::{ sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + atomic::{AtomicBool, Ordering}, }, time::Duration, }; @@ -9,6 +9,9 @@ use std::{ use crossbeam::queue::ArrayQueue; use rodio::{ChannelCount, Sample, SampleRate, Source}; +#[derive(Debug)] +pub struct ReplayDurationTooShort; + pub trait RodioExt: Source + Sized { fn process_buffer(self, callback: F) -> ProcessBuffer where @@ -16,7 +19,11 @@ pub trait RodioExt: Source + Sized { fn inspect_buffer(self, callback: F) -> InspectBuffer where F: FnMut(&[Sample; N]); - fn replayable(self, duration: Duration) -> (Replay, Replayable); + fn replayable( + self, + duration: Duration, + ) -> Result<(Replay, Replayable), ReplayDurationTooShort>; + fn take_samples(self, n: usize) -> TakeSamples; } impl RodioExt for S { @@ -42,32 +49,100 @@ impl RodioExt for S { free: 0, } } - fn replayable(self, duration: Duration) -> (Replay, Replayable) { - let samples_per_second = self.sample_rate().get() * self.channels().get() as u32; + /// Maintains a live replay with a history of at least `duration` seconds. + /// + /// Note: + /// History can be 100ms longer if the source drops before or while the + /// replay is being read + /// + /// # Errors + /// If duration is smaller then 100ms + fn replayable( + self, + duration: Duration, + ) -> Result<(Replay, Replayable), ReplayDurationTooShort> { + if duration < Duration::from_millis(100) { + return Err(ReplayDurationTooShort); + } + + let samples_per_second = self.sample_rate().get() as usize * self.channels().get() as usize; let samples_to_queue = duration.as_secs_f64() * samples_per_second as f64; let samples_to_queue = (samples_to_queue as usize).next_multiple_of(self.channels().get().into()); let chunk_size = - samples_to_queue.min(1000usize.next_multiple_of(self.channels().get().into())); + (samples_per_second.div_ceil(10)).next_multiple_of(self.channels().get() as usize); let chunks_to_queue = samples_to_queue.div_ceil(chunk_size); + let is_active = Arc::new(AtomicBool::new(true)); let queue = Arc::new(ReplayQueue::new(chunks_to_queue, chunk_size)); - ( + Ok(( Replay { rx: Arc::clone(&queue), buffer: Vec::new().into_iter(), sleep_duration: duration / 2, sample_rate: self.sample_rate(), channel_count: self.channels(), + source_is_active: is_active.clone(), }, Replayable { tx: queue, inner: self, buffer: Vec::with_capacity(chunk_size), chunk_size, + is_active, }, - ) + )) + } + fn take_samples(self, n: usize) -> TakeSamples { + TakeSamples { + inner: self, + left_to_take: n, + } + } +} + +pub struct TakeSamples { + inner: S, + left_to_take: usize, +} + +impl Iterator for TakeSamples { + type Item = Sample; + + fn next(&mut self) -> Option { + if self.left_to_take == 0 { + None + } else { + self.left_to_take -= 1; + self.inner.next() + } + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(self.left_to_take)) + } +} + +impl Source for TakeSamples { + fn current_span_len(&self) -> Option { + None // does not support spans + } + + fn channels(&self) -> ChannelCount { + self.inner.channels() + } + + fn sample_rate(&self) -> SampleRate { + self.inner.sample_rate() + } + + fn total_duration(&self) -> Option { + Some(Duration::from_secs_f64( + self.left_to_take as f64 + / self.sample_rate().get() as f64 + / self.channels().get() as f64, + )) } } @@ -79,7 +154,7 @@ struct ReplayQueue { /// the normal chunk size. This is always equal to the /// size of the last element in the queue. /// (so normally chunk_size) - last_chunk_len: AtomicUsize, + last_chunk: Mutex>, } impl ReplayQueue { @@ -87,21 +162,29 @@ impl ReplayQueue { Self { inner: ArrayQueue::new(queue_len), normal_chunk_len: chunk_size, - last_chunk_len: AtomicUsize::new(chunk_size), + last_chunk: Mutex::new(Vec::new()), } } + /// Returns the length in samples fn len(&self) -> usize { self.inner.len().saturating_sub(1) * self.normal_chunk_len - + self.last_chunk_len.load(Ordering::Acquire) + + self + .last_chunk + .lock() + .expect("Self::push_last can not poison this lock") + .len() } fn pop(&self) -> Option> { - self.inner.pop() + self.inner.pop() // removes element that was inserted first } - fn push_last(&self, samples: Vec) { - self.last_chunk_len.store(samples.len(), Ordering::Release); - let _pushed_out_of_ringbuf = self.inner.force_push(samples); + fn push_last(&self, mut samples: Vec) { + let mut last_chunk = self + .last_chunk + .lock() + .expect("Self::len can not poison this lock"); + std::mem::swap(&mut *last_chunk, &mut samples); } fn push_normal(&self, samples: Vec) { @@ -148,6 +231,10 @@ where self.next = 0; Some(self.buffer[0]) } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } } impl Source for ProcessBuffer @@ -156,7 +243,6 @@ where F: FnMut(&mut [Sample; N]), { fn current_span_len(&self) -> Option { - // TODO dvdsk this should be a spanless Source None } @@ -209,6 +295,10 @@ where Some(sample) } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } } impl Source for InspectBuffer @@ -217,7 +307,6 @@ where F: FnMut(&[Sample; N]), { fn current_span_len(&self) -> Option { - // TODO dvdsk this should be a spanless Source None } @@ -240,6 +329,7 @@ pub struct Replayable { buffer: Vec, chunk_size: usize, tx: Arc, + is_active: Arc, } impl Iterator for Replayable { @@ -255,14 +345,18 @@ impl Iterator for Replayable { } else { let last_chunk = std::mem::take(&mut self.buffer); self.tx.push_last(last_chunk); + self.is_active.store(false, Ordering::Relaxed); None } } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } } impl Source for Replayable { fn current_span_len(&self) -> Option { - // Todo dvdsk should be spanless too self.inner.current_span_len() } @@ -286,22 +380,28 @@ pub struct Replay { sleep_duration: Duration, sample_rate: SampleRate, channel_count: ChannelCount, + source_is_active: Arc, } impl Replay { pub fn source_is_active(&self) -> bool { - Arc::strong_count(&self.rx) == 2 + // - source could return None and not drop + // - source could be dropped before returning None + self.source_is_active.load(Ordering::Relaxed) && Arc::strong_count(&self.rx) < 2 } - /// Returns duration of what is in the buffer and - /// can be returned without blocking. + /// Duration of what is in the buffer and can be returned without blocking. pub fn duration_ready(&self) -> Duration { let samples_per_second = self.channels().get() as u32 * self.sample_rate().get(); - let samples_queued = self.rx.len() + self.buffer.len(); - let seconds_queued = samples_queued as f64 / samples_per_second as f64; + let seconds_queued = self.samples_ready() as f64 / samples_per_second as f64; Duration::from_secs_f64(seconds_queued) } + + /// Number of samples in the buffer and can be returned without blocking. + pub fn samples_ready(&self) -> usize { + self.rx.len() + self.buffer.len() + } } impl Iterator for Replay { @@ -325,6 +425,10 @@ impl Iterator for Replay { std::thread::sleep(self.sleep_duration); } } + + fn size_hint(&self) -> (usize, Option) { + ((self.rx.len() + self.buffer.len()), None) + } } impl Source for Replay { @@ -424,7 +528,9 @@ mod tests { fn continues_after_history() { let input = test_source(); - let (mut replay, mut source) = input.replayable(Duration::from_secs(3)); + let (mut replay, mut source) = input + .replayable(Duration::from_secs(3)) + .expect("longer then 100ms"); source.by_ref().take(3).count(); let yielded: Vec = replay.by_ref().take(3).collect(); @@ -439,31 +545,49 @@ mod tests { fn keeps_only_latest() { let input = test_source(); - let (mut replay, mut source) = input.replayable(Duration::from_secs(2)); + let (mut replay, mut source) = input + .replayable(Duration::from_secs(2)) + .expect("longer then 100ms"); source.by_ref().take(5).count(); // get all items but do not end the source let yielded: Vec = replay.by_ref().take(2).collect(); - // Note we do not get the last element, it has not been send yet - // due to buffering. - assert_eq!(&yielded, &SAMPLES[2..4]); - + assert_eq!(&yielded, &SAMPLES[3..5]); source.count(); // exhaust source - let yielded: Vec = replay.collect(); - assert_eq!(&yielded, &[SAMPLES[4]]); + assert_eq!(replay.next(), None); } #[test] fn keeps_correct_amount_of_seconds() { - let input = StaticSamplesBuffer::new(nz!(16_000), nz!(1), &[0.0; 40_000]); + let input = StaticSamplesBuffer::new(nz!(1), nz!(16_000), &[0.0; 40_000]); - let (replay, mut source) = input.replayable(Duration::from_secs(2)); + let (replay, mut source) = input + .replayable(Duration::from_secs(2)) + .expect("longer then 100ms"); - source.by_ref().count(); - let n_yielded = replay.count(); - assert_eq!( - n_yielded as u32, - source.sample_rate().get() * source.channels().get() as u32 * 2 - ); + // exhaust but do not yet end source + source.by_ref().take(40_000).count(); + + // take all samples we can without blocking + let ready = replay.samples_ready(); + let n_yielded = replay.take_samples(ready).count(); + + let max = source.sample_rate().get() * source.channels().get() as u32 * 2; + let margin = 16_000 / 10; // 100ms + assert!(n_yielded as u32 >= max - margin); + } + + #[test] + fn samples_ready() { + let input = StaticSamplesBuffer::new(nz!(1), nz!(16_000), &[0.0; 40_000]); + let (mut replay, source) = input + .replayable(Duration::from_secs(2)) + .expect("longer then 100ms"); + assert_eq!(replay.by_ref().samples_ready(), 0); + + source.take(8000).count(); // half a second + let margin = 16_000 / 10; // 100ms + let ready = replay.samples_ready(); + assert!(ready >= 8000 - margin); } } } diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index 30e2943af3fcb9e8d5141568b2602a8db9a69a6c..ad3d569d61482ad71ee98e636db8c20274d56820 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -29,6 +29,7 @@ client.workspace = true collections.workspace = true fs.workspace = true futures.workspace = true +feature_flags.workspace = true gpui = { workspace = true, features = ["screen-capture"] } language.workspace = true log.workspace = true diff --git a/crates/call/src/call_impl/room.rs b/crates/call/src/call_impl/room.rs index cb4802ce2699051916e6448f62ad7bc664755a6d..930846ab8ff37272f9b0fc0652319318c676f3f7 100644 --- a/crates/call/src/call_impl/room.rs +++ b/crates/call/src/call_impl/room.rs @@ -9,6 +9,7 @@ use client::{ proto::{self, PeerId}, }; use collections::{BTreeMap, HashMap, HashSet}; +use feature_flags::FeatureFlagAppExt; use fs::Fs; use futures::StreamExt; use gpui::{ @@ -1322,16 +1323,18 @@ impl Room { return Task::ready(Err(anyhow!("live-kit was not initialized"))); }; + let is_staff = cx.is_staff(); let user_name = self .user_store .read(cx) .current_user() - .map(|user| user.name.clone()) - .flatten() + .and_then(|user| user.name.clone()) .unwrap_or_else(|| "unknown".to_string()); cx.spawn(async move |this, cx| { - let publication = room.publish_local_microphone_track(&user_name, cx).await; + let publication = room + .publish_local_microphone_track(user_name, is_staff, cx) + .await; this.update(cx, |this, cx| { let live_kit = this .live_kit diff --git a/crates/livekit_client/Cargo.toml b/crates/livekit_client/Cargo.toml index 921918fdac076caa10828045f69d6a2e17092446..80e4960c0df31f6a3d8115bd4bd66c0de09b76f0 100644 --- a/crates/livekit_client/Cargo.toml +++ b/crates/livekit_client/Cargo.toml @@ -22,10 +22,10 @@ test-support = ["collections/test-support", "gpui/test-support"] [dependencies] anyhow.workspace = true async-trait.workspace = true +audio.workspace = true collections.workspace = true cpal.workspace = true futures.workspace = true -audio.workspace = true gpui = { workspace = true, features = ["screen-capture", "x11", "wayland", "windows-manifest"] } gpui_tokio.workspace = true http_client_tls.workspace = true @@ -35,14 +35,15 @@ log.workspace = true nanoid.workspace = true parking_lot.workspace = true postage.workspace = true -smallvec.workspace = true +rodio = { workspace = true, features = ["wav_output", "recording"] } +serde.workspace = true +serde_urlencoded.workspace = true settings.workspace = true +smallvec.workspace = true tokio-tungstenite.workspace = true util.workspace = true workspace-hack.workspace = true -rodio = { workspace = true, features = ["wav_output", "recording"] } - [target.'cfg(not(any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")))'.dependencies] libwebrtc = { rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d", git = "https://github.com/zed-industries/livekit-rust-sdks" } livekit = { rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d", git = "https://github.com/zed-industries/livekit-rust-sdks", features = [ diff --git a/crates/livekit_client/examples/test_app.rs b/crates/livekit_client/examples/test_app.rs index 58aa80ead462cc57e91b90ccde2139e235500e55..c99abb292ef6d99e8adc3ab9007f4c49eeb05be2 100644 --- a/crates/livekit_client/examples/test_app.rs +++ b/crates/livekit_client/examples/test_app.rs @@ -256,7 +256,7 @@ impl LivekitWindow { let room = self.room.clone(); cx.spawn_in(window, async move |this, cx| { let (publication, stream) = room - .publish_local_microphone_track("test_user", cx) + .publish_local_microphone_track("test_user".to_string(), false, cx) .await .unwrap(); this.update(cx, |this, cx| { diff --git a/crates/livekit_client/src/lib.rs b/crates/livekit_client/src/lib.rs index 71638df2ec0390ffaa2b30dd25aec04369e716d9..055aa3704e06f25a21c69294343539289d8acb49 100644 --- a/crates/livekit_client/src/lib.rs +++ b/crates/livekit_client/src/lib.rs @@ -9,19 +9,19 @@ 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" -// )))] +#[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" -// )))] +#[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 @@ -29,27 +29,27 @@ pub use livekit_client::*; // - 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::*; +#[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 { diff --git a/crates/livekit_client/src/livekit_client.rs b/crates/livekit_client/src/livekit_client.rs index b8ecc6f771ef1840d3a34c61178e1fa463715888..45e929cb2ec0bebf054497632d614af1975f6397 100644 --- a/crates/livekit_client/src/livekit_client.rs +++ b/crates/livekit_client/src/livekit_client.rs @@ -97,12 +97,13 @@ impl Room { pub async fn publish_local_microphone_track( &self, - user_name: &str, + user_name: String, + is_staff: bool, cx: &mut AsyncApp, ) -> Result<(LocalTrackPublication, playback::AudioStream)> { let (track, stream) = self .playback - .capture_local_microphone_track(user_name, &cx)?; + .capture_local_microphone_track(user_name, is_staff, &cx)?; let publication = self .local_participant() .publish_track( diff --git a/crates/livekit_client/src/livekit_client/playback.rs b/crates/livekit_client/src/livekit_client/playback.rs index 8b1105e16bd5daf2630ea964b98a0e13db9c128a..d1b2cee4aa1750ba4b8af3033e44b1fe9fbe78de 100644 --- a/crates/livekit_client/src/livekit_client/playback.rs +++ b/crates/livekit_client/src/livekit_client/playback.rs @@ -22,6 +22,7 @@ use livekit::webrtc::{ use log::info; use parking_lot::Mutex; use rodio::Source; +use serde::{Deserialize, Serialize}; use settings::Settings; use std::cell::RefCell; use std::sync::Weak; @@ -55,7 +56,13 @@ pub(crate) fn play_remote_audio_track( s.stop(); } }); - audio::Audio::play_voip_stream(stream, track.name(), cx).context("Could not play audio")?; + + let speaker: Speaker = serde_urlencoded::from_str(&track.name()).unwrap_or_else(|_| Speaker { + name: track.name(), + is_staff: false, + }); + audio::Audio::play_voip_stream(stream, speaker.name, speaker.is_staff, cx) + .context("Could not play audio")?; let on_drop = util::defer(move || { stop_handle_clone.store(true, Ordering::Relaxed); @@ -141,7 +148,8 @@ impl AudioStack { pub(crate) fn capture_local_microphone_track( &self, - user_name: &str, + user_name: String, + is_staff: bool, cx: &AsyncApp, ) -> Result<(crate::LocalAudioTrack, AudioStream)> { let source = NativeAudioSource::new( @@ -152,8 +160,14 @@ impl AudioStack { 10, ); + let track_name = serde_urlencoded::to_string(Speaker { + name: user_name, + is_staff, + }) + .context("Could not encode user information in track name")?; + let track = track::LocalAudioTrack::create_audio_track( - user_name, + &track_name, RtcAudioSource::Native(source.clone()), ); @@ -170,9 +184,14 @@ impl AudioStack { let rodio_pipeline = AudioSettings::try_read_global(cx, |setting| setting.rodio_audio).unwrap_or_default(); let capture_task = if rodio_pipeline { - // TODO global might not yet have been initialized info!("Using experimental.rodio_audio audio pipeline"); - audio::Audio::open_microphone(cx.clone(), frame_tx)?; + let voip_parts = audio::VoipParts::new(cx)?; + thread::spawn(move || { + // microphone is non send on mac + let microphone = audio::Audio::open_microphone(voip_parts)?; + send_to_livekit(frame_tx, microphone); + Ok::<(), anyhow::Error>(()) + }); Task::ready(Ok(())) } else { self.executor.spawn(async move { @@ -357,6 +376,36 @@ impl AudioStack { } } +#[derive(Serialize, Deserialize)] +struct Speaker { + name: String, + is_staff: bool, +} + +fn send_to_livekit(frame_tx: UnboundedSender>, mut microphone: impl Source) { + use cpal::Sample; + loop { + let sampled: Vec<_> = microphone + .by_ref() + .take(audio::BUFFER_SIZE) + .map(|s| s.to_sample()) + .collect(); + + if frame_tx + .unbounded_send(AudioFrame { + sample_rate: SAMPLE_RATE.get(), + num_channels: CHANNEL_COUNT.get() as u32, + samples_per_channel: sampled.len() as u32 / CHANNEL_COUNT.get() as u32, + data: Cow::Owned(sampled), + }) + .is_err() + { + // must rx has dropped or is not consuming + break; + } + } +} + use super::LocalVideoTrack; pub enum AudioStream { diff --git a/crates/livekit_client/src/test.rs b/crates/livekit_client/src/test.rs index 612b032895b42afcfdc09e8d24023fbd1fcdf5c1..fd3163598203ac26443cae1b733372b6c3bdf1d1 100644 --- a/crates/livekit_client/src/test.rs +++ b/crates/livekit_client/src/test.rs @@ -728,7 +728,8 @@ impl Room { pub async fn publish_local_microphone_track( &self, - _track_name: &str, + _track_name: String, + _is_staff: bool, cx: &mut AsyncApp, ) -> Result<(LocalTrackPublication, AudioStream)> { self.local_participant().publish_microphone_track(cx).await diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index bee6c87670c87a08945918a3dd49b26463a3a3ef..755e52e375b9a916833a6158375d4b311ebc7d80 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -85,7 +85,6 @@ inspector_ui.workspace = true install_cli.workspace = true jj_ui.workspace = true journal.workspace = true -livekit_client.workspace = true language.workspace = true language_extension.workspace = true language_model.workspace = true diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index ea2106593eb1e0788b3b8a46f7ae8c32ac75c1e4..b4934015b29ec2561e828eb789f24dddba7b2051 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -59,7 +59,7 @@ use settings::{ initial_local_debug_tasks_content, initial_project_settings_content, initial_tasks_content, update_settings_file, }; -use std::time::{Duration, Instant}; +use std::time::Duration; use std::{ borrow::Cow, path::{Path, PathBuf}, @@ -128,10 +128,9 @@ actions!( actions!( dev, [ - /// Record 10s of audio from your current microphone - CaptureAudio, - /// Stores last 30s of audio from everyone on the current call - /// in a tar file in the current working directory. + /// Stores last 30s of audio from zed staff using the experimental rodio + /// audio system (including yourself) on the current call in a tar file + /// in the current working directory. CaptureRecentAudio, ] ); @@ -927,9 +926,6 @@ fn register_actions( } } }) - .register_action(|workspace, _: &CaptureAudio, window, cx| { - capture_audio(workspace, window, cx); - }) .register_action(|workspace, _: &CaptureRecentAudio, window, cx| { capture_recent_audio(workspace, window, cx); }); @@ -1843,108 +1839,6 @@ fn open_settings_file( .detach_and_log_err(cx); } -fn capture_audio(workspace: &mut Workspace, _: &mut Window, cx: &mut Context) { - #[derive(Default)] - enum State { - Recording(livekit_client::CaptureInput), - Failed(String), - Finished(PathBuf), - // Used during state switch. Should never occur naturally. - #[default] - Invalid, - } - - struct CaptureAudioNotification { - focus_handle: gpui::FocusHandle, - start_time: Instant, - state: State, - } - - impl gpui::EventEmitter for CaptureAudioNotification {} - impl gpui::EventEmitter for CaptureAudioNotification {} - impl gpui::Focusable for CaptureAudioNotification { - fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle { - self.focus_handle.clone() - } - } - impl workspace::notifications::Notification for CaptureAudioNotification {} - - const AUDIO_RECORDING_TIME_SECS: u64 = 10; - - impl Render for CaptureAudioNotification { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let elapsed = self.start_time.elapsed().as_secs(); - let message = match &self.state { - State::Recording(capture) => format!( - "Recording {} seconds of audio from input: '{}'", - AUDIO_RECORDING_TIME_SECS - elapsed, - capture.name, - ), - State::Failed(e) => format!("Error capturing audio: {e}"), - State::Finished(path) => format!("Audio recorded to {}", path.display()), - State::Invalid => "Error invalid state".to_string(), - }; - - NotificationFrame::new() - .with_title(Some("Recording Audio")) - .show_suppress_button(false) - .on_close(cx.listener(|_, _, _, cx| { - cx.emit(DismissEvent); - })) - .with_content(message) - } - } - - impl CaptureAudioNotification { - fn finish(&mut self) { - let state = std::mem::take(&mut self.state); - self.state = if let State::Recording(capture) = state { - match capture.finish() { - Ok(path) => State::Finished(path), - Err(e) => State::Failed(e.to_string()), - } - } else { - state - }; - } - - fn new(cx: &mut Context) -> Self { - cx.spawn(async move |this, cx| { - for _ in 0..10 { - cx.background_executor().timer(Duration::from_secs(1)).await; - this.update(cx, |_, cx| { - cx.notify(); - })?; - } - - this.update(cx, |this, cx| { - this.finish(); - cx.notify(); - })?; - - anyhow::Ok(()) - }) - .detach(); - - let state = match livekit_client::CaptureInput::start() { - Ok(capture_input) => State::Recording(capture_input), - Err(err) => State::Failed(format!("Error starting audio capture: {}", err)), - }; - - Self { - focus_handle: cx.focus_handle(), - start_time: Instant::now(), - state, - } - } - } - - workspace.show_notification(NotificationId::unique::(), cx, |cx| { - cx.new(CaptureAudioNotification::new) - }); -} - -// TODO dvdsk Move this and capture audio somewhere else? fn capture_recent_audio(workspace: &mut Workspace, _: &mut Window, cx: &mut Context) { struct CaptureRecentAudioNotification { focus_handle: gpui::FocusHandle, diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index 9bcaabb8cc942818fab9b3a454a0858f70be6bf2..1393ac54f3bdcea23a98530c333b2d670559785d 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -47,6 +47,7 @@ clap_builder = { version = "4", default-features = false, features = ["cargo", " concurrent-queue = { version = "2" } cranelift-codegen = { version = "0.116", default-features = false, features = ["host-arch", "incremental-cache", "std", "timing", "unwind"] } crc32fast = { version = "1" } +crossbeam-channel = { version = "0.5" } crossbeam-epoch = { version = "0.9" } crossbeam-utils = { version = "0.8" } deranged = { version = "0.4", default-features = false, features = ["powerfmt", "serde", "std"] } @@ -176,6 +177,7 @@ clap_builder = { version = "4", default-features = false, features = ["cargo", " concurrent-queue = { version = "2" } cranelift-codegen = { version = "0.116", default-features = false, features = ["host-arch", "incremental-cache", "std", "timing", "unwind"] } crc32fast = { version = "1" } +crossbeam-channel = { version = "0.5" } crossbeam-epoch = { version = "0.9" } crossbeam-utils = { version = "0.8" } deranged = { version = "0.4", default-features = false, features = ["powerfmt", "serde", "std"] } @@ -291,6 +293,7 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } objc2 = { version = "0.6" } @@ -319,6 +322,7 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } objc2 = { version = "0.6" } @@ -348,6 +352,7 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } objc2 = { version = "0.6" } @@ -376,6 +381,7 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } naga = { version = "25", features = ["msl-out", "wgsl-in"] } nix-b73a96c0a5f6a7d9 = { package = "nix", version = "0.29", features = ["fs", "pthread", "signal", "user"] } objc2 = { version = "0.6" } @@ -413,6 +419,7 @@ inout = { version = "0.1", default-features = false, features = ["block-padding" itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } mio = { version = "1", features = ["net", "os-ext"] } naga = { version = "25", features = ["spv-out", "wgsl-in"] } nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "mman", "ptrace", "signal", "term", "user"] } @@ -453,6 +460,7 @@ inout = { version = "0.1", default-features = false, features = ["block-padding" itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } mio = { version = "1", features = ["net", "os-ext"] } naga = { version = "25", features = ["spv-out", "wgsl-in"] } nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "mman", "ptrace", "signal", "term", "user"] } @@ -491,6 +499,7 @@ inout = { version = "0.1", default-features = false, features = ["block-padding" itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } mio = { version = "1", features = ["net", "os-ext"] } naga = { version = "25", features = ["spv-out", "wgsl-in"] } nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "mman", "ptrace", "signal", "term", "user"] } @@ -531,6 +540,7 @@ inout = { version = "0.1", default-features = false, features = ["block-padding" itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } mio = { version = "1", features = ["net", "os-ext"] } naga = { version = "25", features = ["spv-out", "wgsl-in"] } nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "mman", "ptrace", "signal", "term", "user"] } @@ -560,6 +570,7 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event"] } scopeguard = { version = "1" } @@ -583,6 +594,7 @@ getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-f getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] } hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] } itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } ring = { version = "0.17", features = ["std"] } rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event"] } @@ -616,6 +628,7 @@ inout = { version = "0.1", default-features = false, features = ["block-padding" itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } mio = { version = "1", features = ["net", "os-ext"] } naga = { version = "25", features = ["spv-out", "wgsl-in"] } nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "mman", "ptrace", "signal", "term", "user"] } @@ -656,6 +669,7 @@ inout = { version = "0.1", default-features = false, features = ["block-padding" itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" } linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] } linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] } +livekit-runtime = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d" } mio = { version = "1", features = ["net", "os-ext"] } naga = { version = "25", features = ["spv-out", "wgsl-in"] } nix-1f5adca70f036a62 = { package = "nix", version = "0.28", features = ["fs", "mman", "ptrace", "signal", "term", "user"] }