mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-21 03:18:47 +03:00
Make audio2
This commit is contained in:
parent
5fb8321ee0
commit
18930c33fe
@ -4,6 +4,7 @@ members = [
|
|||||||
"crates/ai",
|
"crates/ai",
|
||||||
"crates/assistant",
|
"crates/assistant",
|
||||||
"crates/audio",
|
"crates/audio",
|
||||||
|
"crates/audio2",
|
||||||
"crates/auto_update",
|
"crates/auto_update",
|
||||||
"crates/breadcrumbs",
|
"crates/breadcrumbs",
|
||||||
"crates/call",
|
"crates/call",
|
||||||
|
24
crates/audio2/Cargo.toml
Normal file
24
crates/audio2/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "audio2"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/audio2.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gpui2 = { path = "../gpui2" }
|
||||||
|
collections = { path = "../collections" }
|
||||||
|
util = { path = "../util" }
|
||||||
|
|
||||||
|
|
||||||
|
rodio ={version = "0.17.1", default-features=false, features = ["wav"]}
|
||||||
|
|
||||||
|
log.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
23
crates/audio2/audio/Cargo.toml
Normal file
23
crates/audio2/audio/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "audio"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/audio.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gpui = { path = "../gpui" }
|
||||||
|
collections = { path = "../collections" }
|
||||||
|
util = { path = "../util" }
|
||||||
|
|
||||||
|
rodio ={version = "0.17.1", default-features=false, features = ["wav"]}
|
||||||
|
|
||||||
|
log.workspace = true
|
||||||
|
|
||||||
|
anyhow.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
44
crates/audio2/audio/src/assets.rs
Normal file
44
crates/audio2/audio/src/assets.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use std::{io::Cursor, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui::{AppContext, AssetSource};
|
||||||
|
use rodio::{
|
||||||
|
source::{Buffered, SamplesConverter},
|
||||||
|
Decoder, Source,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Sound = Buffered<SamplesConverter<Decoder<Cursor<Vec<u8>>>, f32>>;
|
||||||
|
|
||||||
|
pub struct SoundRegistry {
|
||||||
|
cache: Arc<parking_lot::Mutex<HashMap<String, Sound>>>,
|
||||||
|
assets: Box<dyn AssetSource>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoundRegistry {
|
||||||
|
pub fn new(source: impl AssetSource) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
cache: Default::default(),
|
||||||
|
assets: Box::new(source),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||||
|
cx.global::<Arc<Self>>().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, name: &str) -> Result<impl Source<Item = f32>> {
|
||||||
|
if let Some(wav) = self.cache.lock().get(name) {
|
||||||
|
return Ok(wav.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = format!("sounds/{}.wav", name);
|
||||||
|
let bytes = self.assets.load(&path)?.into_owned();
|
||||||
|
let cursor = Cursor::new(bytes);
|
||||||
|
let source = Decoder::new(cursor)?.convert_samples::<f32>().buffered();
|
||||||
|
|
||||||
|
self.cache.lock().insert(name.to_string(), source.clone());
|
||||||
|
|
||||||
|
Ok(source)
|
||||||
|
}
|
||||||
|
}
|
81
crates/audio2/audio/src/audio.rs
Normal file
81
crates/audio2/audio/src/audio.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use assets::SoundRegistry;
|
||||||
|
use gpui::{AppContext, AssetSource};
|
||||||
|
use rodio::{OutputStream, OutputStreamHandle};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
|
mod assets;
|
||||||
|
|
||||||
|
pub fn init(source: impl AssetSource, cx: &mut AppContext) {
|
||||||
|
cx.set_global(SoundRegistry::new(source));
|
||||||
|
cx.set_global(Audio::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Sound {
|
||||||
|
Joined,
|
||||||
|
Leave,
|
||||||
|
Mute,
|
||||||
|
Unmute,
|
||||||
|
StartScreenshare,
|
||||||
|
StopScreenshare,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sound {
|
||||||
|
fn file(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Joined => "joined_call",
|
||||||
|
Self::Leave => "leave_call",
|
||||||
|
Self::Mute => "mute",
|
||||||
|
Self::Unmute => "unmute",
|
||||||
|
Self::StartScreenshare => "start_screenshare",
|
||||||
|
Self::StopScreenshare => "stop_screenshare",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Audio {
|
||||||
|
_output_stream: Option<OutputStream>,
|
||||||
|
output_handle: Option<OutputStreamHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Audio {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
_output_stream: None,
|
||||||
|
output_handle: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
|
||||||
|
if self.output_handle.is_none() {
|
||||||
|
let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
|
||||||
|
self.output_handle = output_handle;
|
||||||
|
self._output_stream = _output_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.output_handle.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_sound(sound: Sound, cx: &mut AppContext) {
|
||||||
|
if !cx.has_global::<Self>() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.update_global::<Self, _, _>(|this, cx| {
|
||||||
|
let output_handle = this.ensure_output_exists()?;
|
||||||
|
let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
|
||||||
|
output_handle.play_raw(source).log_err()?;
|
||||||
|
Some(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_call(cx: &mut AppContext) {
|
||||||
|
if !cx.has_global::<Self>() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.update_global::<Self, _, _>(|this, _| {
|
||||||
|
this._output_stream.take();
|
||||||
|
this.output_handle.take();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
44
crates/audio2/src/assets.rs
Normal file
44
crates/audio2/src/assets.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use std::{io::Cursor, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui2::{AppContext, AssetSource, SharedString};
|
||||||
|
use rodio::{
|
||||||
|
source::{Buffered, SamplesConverter},
|
||||||
|
Decoder, Source,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Sound = Buffered<SamplesConverter<Decoder<Cursor<Vec<u8>>>, f32>>;
|
||||||
|
|
||||||
|
pub struct SoundRegistry {
|
||||||
|
cache: Arc<parking_lot::Mutex<HashMap<String, Sound>>>,
|
||||||
|
assets: Box<dyn AssetSource>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoundRegistry {
|
||||||
|
pub fn new(source: impl AssetSource) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
cache: Default::default(),
|
||||||
|
assets: Box::new(source),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||||
|
cx.global::<Arc<Self>>().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, name: &str) -> Result<impl Source<Item = f32>> {
|
||||||
|
if let Some(wav) = self.cache.lock().get(name) {
|
||||||
|
return Ok(wav.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = format!("sounds/{}.wav", name);
|
||||||
|
let bytes = self.assets.load(SharedString::from(path))?.into_owned();
|
||||||
|
let cursor = Cursor::new(bytes);
|
||||||
|
let source = Decoder::new(cursor)?.convert_samples::<f32>().buffered();
|
||||||
|
|
||||||
|
self.cache.lock().insert(name.to_string(), source.clone());
|
||||||
|
|
||||||
|
Ok(source)
|
||||||
|
}
|
||||||
|
}
|
100
crates/audio2/src/audio2.rs
Normal file
100
crates/audio2/src/audio2.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use assets::SoundRegistry;
|
||||||
|
use futures::{channel::mpsc, StreamExt};
|
||||||
|
use gpui2::{AppContext, AssetSource, Executor};
|
||||||
|
use rodio::{OutputStream, OutputStreamHandle};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
|
mod assets;
|
||||||
|
|
||||||
|
pub fn init(source: impl AssetSource, cx: &mut AppContext) {
|
||||||
|
cx.set_global(Audio::new(cx.executor()));
|
||||||
|
cx.set_global(SoundRegistry::new(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Sound {
|
||||||
|
Joined,
|
||||||
|
Leave,
|
||||||
|
Mute,
|
||||||
|
Unmute,
|
||||||
|
StartScreenshare,
|
||||||
|
StopScreenshare,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sound {
|
||||||
|
fn file(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Joined => "joined_call",
|
||||||
|
Self::Leave => "leave_call",
|
||||||
|
Self::Mute => "mute",
|
||||||
|
Self::Unmute => "unmute",
|
||||||
|
Self::StartScreenshare => "start_screenshare",
|
||||||
|
Self::StopScreenshare => "stop_screenshare",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Audio {
|
||||||
|
tx: mpsc::UnboundedSender<Box<dyn FnOnce(&mut AudioState) + Send>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AudioState {
|
||||||
|
_output_stream: Option<OutputStream>,
|
||||||
|
output_handle: Option<OutputStreamHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioState {
|
||||||
|
fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
|
||||||
|
if self.output_handle.is_none() {
|
||||||
|
let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
|
||||||
|
self.output_handle = output_handle;
|
||||||
|
self._output_stream = _output_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.output_handle.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take(&mut self) {
|
||||||
|
self._output_stream.take();
|
||||||
|
self.output_handle.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Audio {
|
||||||
|
pub fn new(executor: &Executor) -> Self {
|
||||||
|
let (tx, mut rx) = mpsc::unbounded::<Box<dyn FnOnce(&mut AudioState) + Send>>();
|
||||||
|
executor
|
||||||
|
.spawn_on_main(|| async move {
|
||||||
|
let mut audio = AudioState {
|
||||||
|
_output_stream: None,
|
||||||
|
output_handle: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Some(f) = rx.next().await {
|
||||||
|
(f)(&mut audio);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
Self { tx }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_sound(&self, sound: Sound, cx: &mut AppContext) {
|
||||||
|
let Some(source) = SoundRegistry::global(cx).get(sound.file()).log_err() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tx
|
||||||
|
.unbounded_send(Box::new(move |state| {
|
||||||
|
if let Some(output_handle) = state.ensure_output_exists() {
|
||||||
|
output_handle.play_raw(source).log_err();
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_call(&self) {
|
||||||
|
self.tx
|
||||||
|
.unbounded_send(Box::new(move |state| state.take()))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user