Rodio audio (#37786)
Adds input to the experimental rodio_audio pipeline.
Enable with:
```json
"audio": {
"experimental.rodio_audio": true
}
```
Additionally enables automatic volume
control for incoming audio:
```json
"audio": {
"experimental.control_output_volume": true
}
```
Release Notes:
- N/A
This commit is contained in:
+56
-76
@@ -13,6 +13,7 @@ use agent_ui::{AgentDiffToolbar, AgentPanelDelegate};
|
||||
use anyhow::Context as _;
|
||||
pub use app_menus::*;
|
||||
use assets::Assets;
|
||||
use audio::{AudioSettings, REPLAY_DURATION};
|
||||
use breadcrumbs::Breadcrumbs;
|
||||
use client::zed_urls;
|
||||
use collections::VecDeque;
|
||||
@@ -59,7 +60,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,8 +129,10 @@ actions!(
|
||||
actions!(
|
||||
dev,
|
||||
[
|
||||
/// Record 10s of audio from your current microphone
|
||||
CaptureAudio
|
||||
/// 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,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -910,8 +913,8 @@ 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);
|
||||
});
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
@@ -1845,50 +1848,39 @@ fn open_settings_file(
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn capture_audio(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Workspace>) {
|
||||
#[derive(Default)]
|
||||
enum State {
|
||||
Recording(livekit_client::CaptureInput),
|
||||
Failed(String),
|
||||
Finished(PathBuf),
|
||||
// Used during state switch. Should never occur naturally.
|
||||
#[default]
|
||||
Invalid,
|
||||
}
|
||||
|
||||
struct CaptureAudioNotification {
|
||||
fn capture_recent_audio(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Workspace>) {
|
||||
struct CaptureRecentAudioNotification {
|
||||
focus_handle: gpui::FocusHandle,
|
||||
start_time: Instant,
|
||||
state: State,
|
||||
save_result: Option<Result<(PathBuf, Duration), anyhow::Error>>,
|
||||
_save_task: Task<anyhow::Result<()>>,
|
||||
}
|
||||
|
||||
impl gpui::EventEmitter<DismissEvent> for CaptureAudioNotification {}
|
||||
impl gpui::EventEmitter<SuppressEvent> for CaptureAudioNotification {}
|
||||
impl gpui::Focusable for CaptureAudioNotification {
|
||||
impl gpui::EventEmitter<DismissEvent> for CaptureRecentAudioNotification {}
|
||||
impl gpui::EventEmitter<SuppressEvent> for CaptureRecentAudioNotification {}
|
||||
impl gpui::Focusable for CaptureRecentAudioNotification {
|
||||
fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
impl workspace::notifications::Notification for CaptureAudioNotification {}
|
||||
impl workspace::notifications::Notification for CaptureRecentAudioNotification {}
|
||||
|
||||
const AUDIO_RECORDING_TIME_SECS: u64 = 10;
|
||||
|
||||
impl Render for CaptureAudioNotification {
|
||||
impl Render for CaptureRecentAudioNotification {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> 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,
|
||||
let message = match &self.save_result {
|
||||
None => format!(
|
||||
"Saving up to {} seconds of recent audio",
|
||||
REPLAY_DURATION.as_secs(),
|
||||
),
|
||||
State::Failed(e) => format!("Error capturing audio: {e}"),
|
||||
State::Finished(path) => format!("Audio recorded to {}", path.display()),
|
||||
State::Invalid => "Error invalid state".to_string(),
|
||||
Some(Ok((path, duration))) => format!(
|
||||
"Saved {} seconds of all audio to {}",
|
||||
duration.as_secs(),
|
||||
path.display(),
|
||||
),
|
||||
Some(Err(e)) => format!("Error saving audio replays: {e:?}"),
|
||||
};
|
||||
|
||||
NotificationFrame::new()
|
||||
.with_title(Some("Recording Audio"))
|
||||
.with_title(Some("Saved Audio"))
|
||||
.show_suppress_button(false)
|
||||
.on_close(cx.listener(|_, _, _, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
@@ -1897,53 +1889,41 @@ fn capture_audio(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Wor
|
||||
}
|
||||
}
|
||||
|
||||
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()),
|
||||
impl CaptureRecentAudioNotification {
|
||||
fn new(cx: &mut Context<Self>) -> Self {
|
||||
if AudioSettings::get_global(cx).rodio_audio {
|
||||
let executor = cx.background_executor().clone();
|
||||
let save_task = cx.default_global::<audio::Audio>().save_replays(executor);
|
||||
let _save_task = cx.spawn(async move |this, cx| {
|
||||
let res = save_task.await;
|
||||
this.update(cx, |this, cx| {
|
||||
this.save_result = Some(res);
|
||||
cx.notify();
|
||||
})
|
||||
});
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
_save_task,
|
||||
save_result: None,
|
||||
}
|
||||
} else {
|
||||
state
|
||||
};
|
||||
}
|
||||
|
||||
fn new(cx: &mut Context<Self>) -> 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();
|
||||
})?;
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
_save_task: Task::ready(Ok(())),
|
||||
save_result: Some(Err(anyhow::anyhow!(
|
||||
"Capturing recent audio is only supported on the experimental rodio audio pipeline"
|
||||
))),
|
||||
}
|
||||
|
||||
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::<CaptureAudio>(), cx, |cx| {
|
||||
cx.new(CaptureAudioNotification::new)
|
||||
});
|
||||
workspace.show_notification(
|
||||
NotificationId::unique::<CaptureRecentAudioNotification>(),
|
||||
cx,
|
||||
|cx| cx.new(CaptureRecentAudioNotification::new),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user