Add component NotificationFrame & CaptureAudio parts for testing (#36081)

Adds component NotificationFrame. It implements a subset of MessageNotification as a Component and refactors MessageNotification to use NotificationFrame. Having some notification UI Component is nice as it allows us to easily build new types of notifications.

Uses the new NotificationFrame component for CaptureAudioNotification. 

Adds a CaptureAudio action in the dev namespace (not meant for
end-users). It records 10 seconds of audio and saves that to a wav file.

Release Notes:

- N/A

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
David Kleingeld 2025-08-15 12:10:52 +02:00 committed by GitHub
parent a3dcc76687
commit 4f0b00b0d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 448 additions and 127 deletions

View file

@ -1,7 +1,13 @@
use anyhow::Context as _;
use collections::HashMap;
mod remote_video_track_view;
use cpal::traits::HostTrait as _;
pub use remote_video_track_view::{RemoteVideoTrackView, RemoteVideoTrackViewEvent};
use rodio::DeviceTrait as _;
mod record;
pub use record::CaptureInput;
#[cfg(not(any(
test,
@ -18,6 +24,8 @@ mod livekit_client;
)))]
pub use livekit_client::*;
// If you need proper LSP in livekit_client you've got to comment out
// the mocks and test
#[cfg(any(
test,
feature = "test-support",
@ -168,3 +176,59 @@ pub enum RoomEvent {
Reconnecting,
Reconnected,
}
pub(crate) fn default_device(
input: bool,
) -> anyhow::Result<(cpal::Device, cpal::SupportedStreamConfig)> {
let device;
let config;
if input {
device = cpal::default_host()
.default_input_device()
.context("no audio input device available")?;
config = device
.default_input_config()
.context("failed to get default input config")?;
} else {
device = cpal::default_host()
.default_output_device()
.context("no audio output device available")?;
config = device
.default_output_config()
.context("failed to get default output config")?;
}
Ok((device, config))
}
pub(crate) fn get_sample_data(
sample_format: cpal::SampleFormat,
data: &cpal::Data,
) -> anyhow::Result<Vec<i16>> {
match sample_format {
cpal::SampleFormat::I8 => Ok(convert_sample_data::<i8, i16>(data)),
cpal::SampleFormat::I16 => Ok(data.as_slice::<i16>().unwrap().to_vec()),
cpal::SampleFormat::I24 => Ok(convert_sample_data::<cpal::I24, i16>(data)),
cpal::SampleFormat::I32 => Ok(convert_sample_data::<i32, i16>(data)),
cpal::SampleFormat::I64 => Ok(convert_sample_data::<i64, i16>(data)),
cpal::SampleFormat::U8 => Ok(convert_sample_data::<u8, i16>(data)),
cpal::SampleFormat::U16 => Ok(convert_sample_data::<u16, i16>(data)),
cpal::SampleFormat::U32 => Ok(convert_sample_data::<u32, i16>(data)),
cpal::SampleFormat::U64 => Ok(convert_sample_data::<u64, i16>(data)),
cpal::SampleFormat::F32 => Ok(convert_sample_data::<f32, i16>(data)),
cpal::SampleFormat::F64 => Ok(convert_sample_data::<f64, i16>(data)),
_ => anyhow::bail!("Unsupported sample format"),
}
}
pub(crate) fn convert_sample_data<
TSource: cpal::SizedSample,
TDest: cpal::SizedSample + cpal::FromSample<TSource>,
>(
data: &cpal::Data,
) -> Vec<TDest> {
data.as_slice::<TSource>()
.unwrap()
.iter()
.map(|e| e.to_sample::<TDest>())
.collect()
}