replays.rs

 1use anyhow::{Context, anyhow};
 2use async_tar::{Builder, Header};
 3use gpui::{BackgroundExecutor, Task};
 4
 5use collections::HashMap;
 6use parking_lot::Mutex;
 7use rodio::Source;
 8use smol::fs::File;
 9use std::{io, path::PathBuf, sync::Arc, time::Duration};
10
11use crate::REPLAY_DURATION;
12use crate::audio_pipeline::rodio_ext::Replay;
13
14#[derive(Default, Clone)]
15pub(crate) struct Replays(Arc<Mutex<HashMap<String, Replay>>>);
16
17impl Replays {
18    pub(crate) fn add_voip_stream(&self, stream_name: String, source: Replay) {
19        let mut map = self.0.lock();
20        map.retain(|_, replay| replay.source_is_active());
21        map.insert(stream_name, source);
22    }
23
24    pub(crate) fn replays_to_tar(
25        &self,
26        executor: BackgroundExecutor,
27    ) -> Task<anyhow::Result<(PathBuf, Duration)>> {
28        let map = Arc::clone(&self.0);
29        executor.spawn(async move {
30            let recordings: Vec<_> = map
31                .lock()
32                .iter_mut()
33                .map(|(name, replay)| {
34                    let queued = REPLAY_DURATION.min(replay.duration_ready());
35                    (name.clone(), replay.take_duration(queued).record())
36                })
37                .collect();
38            let longest = recordings
39                .iter()
40                .map(|(_, r)| {
41                    r.total_duration()
42                        .expect("SamplesBuffer always returns a total duration")
43                })
44                .max()
45                .ok_or(anyhow!("There is no audio to capture"))?;
46
47            let path = std::env::current_dir()
48                .context("Could not get current dir")?
49                .join("replays.tar");
50            let tar = File::create(&path)
51                .await
52                .context("Could not create file for tar")?;
53
54            let mut tar = Builder::new(tar);
55
56            for (name, recording) in recordings {
57                let mut writer = io::Cursor::new(Vec::new());
58                rodio::wav_to_writer(recording, &mut writer).context("failed to encode wav")?;
59                let wav_data = writer.into_inner();
60                let path = name.replace(' ', "_") + ".wav";
61                let mut header = Header::new_gnu();
62                // rw permissions for everyone
63                header.set_mode(0o666);
64                header.set_size(wav_data.len() as u64);
65                tar.append_data(&mut header, path, wav_data.as_slice())
66                    .await
67                    .context("failed to apped wav to tar")?;
68            }
69            tar.into_inner()
70                .await
71                .context("Could not finish writing tar")?
72                .sync_all()
73                .await
74                .context("Could not flush tar file to disk")?;
75            Ok((path, longest))
76        })
77    }
78}