audio.rs

  1use anyhow::{Context as _, Result, anyhow};
  2use collections::HashMap;
  3use gpui::{App, BorrowAppContext, Global};
  4use rodio::{Decoder, OutputStream, OutputStreamBuilder, Source, source::Buffered};
  5use settings::Settings;
  6use std::io::Cursor;
  7use util::ResultExt;
  8
  9mod audio_settings;
 10pub use audio_settings::AudioSettings;
 11
 12pub fn init(cx: &mut App) {
 13    AudioSettings::register(cx);
 14}
 15
 16#[derive(Copy, Clone, Eq, Hash, PartialEq)]
 17pub enum Sound {
 18    Joined,
 19    Leave,
 20    Mute,
 21    Unmute,
 22    StartScreenshare,
 23    StopScreenshare,
 24    AgentDone,
 25}
 26
 27impl Sound {
 28    fn file(&self) -> &'static str {
 29        match self {
 30            Self::Joined => "joined_call",
 31            Self::Leave => "leave_call",
 32            Self::Mute => "mute",
 33            Self::Unmute => "unmute",
 34            Self::StartScreenshare => "start_screenshare",
 35            Self::StopScreenshare => "stop_screenshare",
 36            Self::AgentDone => "agent_done",
 37        }
 38    }
 39}
 40
 41#[derive(Default)]
 42pub struct Audio {
 43    output_handle: Option<OutputStream>,
 44    source_cache: HashMap<Sound, Buffered<Decoder<Cursor<Vec<u8>>>>>,
 45}
 46
 47impl Global for Audio {}
 48
 49impl Audio {
 50    fn ensure_output_exists(&mut self) -> Option<&OutputStream> {
 51        if self.output_handle.is_none() {
 52            self.output_handle = OutputStreamBuilder::open_default_stream().log_err();
 53        }
 54
 55        self.output_handle.as_ref()
 56    }
 57
 58    pub fn play_source(
 59        source: impl rodio::Source + Send + 'static,
 60        cx: &mut App,
 61    ) -> anyhow::Result<()> {
 62        cx.update_default_global(|this: &mut Self, _cx| {
 63            let output_handle = this
 64                .ensure_output_exists()
 65                .ok_or_else(|| anyhow!("Could not open audio output"))?;
 66            output_handle.mixer().add(source);
 67            Ok(())
 68        })
 69    }
 70
 71    pub fn play_sound(sound: Sound, cx: &mut App) {
 72        cx.update_default_global(|this: &mut Self, cx| {
 73            let source = this.sound_source(sound, cx).log_err()?;
 74            let output_handle = this.ensure_output_exists()?;
 75            output_handle.mixer().add(source);
 76            Some(())
 77        });
 78    }
 79
 80    pub fn end_call(cx: &mut App) {
 81        cx.update_default_global(|this: &mut Self, _cx| {
 82            this.output_handle.take();
 83        });
 84    }
 85
 86    fn sound_source(&mut self, sound: Sound, cx: &App) -> Result<impl Source + use<>> {
 87        if let Some(wav) = self.source_cache.get(&sound) {
 88            return Ok(wav.clone());
 89        }
 90
 91        let path = format!("sounds/{}.wav", sound.file());
 92        let bytes = cx
 93            .asset_source()
 94            .load(&path)?
 95            .map(anyhow::Ok)
 96            .with_context(|| format!("No asset available for path {path}"))??
 97            .into_owned();
 98        let cursor = Cursor::new(bytes);
 99        let source = Decoder::new(cursor)?.buffered();
100
101        self.source_cache.insert(sound, source.clone());
102
103        Ok(source)
104    }
105}