generated from milosgajdos/go-repo-template
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Just like with gobot we use playht API and stream the audio on the default audio device using rodio crate.. We introduce a tts module that handles the TTS tasks in rustbot. We use the playht_rs crate for TTS synthesis and stream the audio to the default audio device. Signed-off-by: Milos Gajdos <[email protected]>
- Loading branch information
1 parent
c8a9c4d
commit 77ce5c0
Showing
9 changed files
with
315 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
use crate::prelude::*; | ||
use bytes::BytesMut; | ||
use rodio::{Decoder, Sink}; | ||
use std::io::Cursor; | ||
use tokio::{ | ||
self, | ||
io::{self, AsyncReadExt}, | ||
sync::watch, | ||
time::{self, Duration, Instant}, | ||
}; | ||
|
||
pub async fn play( | ||
mut audio_rd: io::DuplexStream, | ||
sink: Sink, | ||
audio_done: watch::Sender<bool>, | ||
mut done: watch::Receiver<bool>, | ||
) -> Result<()> { | ||
println!("launching audio player"); | ||
let mut audio_data = BytesMut::new(); | ||
// TODO: make this a cli switch as this value has been picked rather arbitrarily | ||
let interval_duration = Duration::from_millis(AUDIO_INTERVAL); | ||
let mut interval = time::interval(interval_duration); | ||
let mut last_play_time = Instant::now(); | ||
let mut has_played_audio = false; | ||
|
||
loop { | ||
tokio::select! { | ||
_ = done.changed() => { | ||
if *done.borrow() { | ||
break; | ||
} | ||
} | ||
result = audio_rd.read_buf(&mut audio_data) => { | ||
if let Ok(chunk) = result { | ||
if chunk == 0 { | ||
break; | ||
} | ||
if audio_data.len() > AUDIO_BUFFER_SIZE { | ||
// NOTE: this avoids unnecessary data duplication and manages the buffer efficiently | ||
let cursor = Cursor::new(audio_data.split_to(AUDIO_BUFFER_SIZE).freeze().to_vec()); | ||
match Decoder::new(cursor) { | ||
Ok(source) => { | ||
sink.append(source); | ||
last_play_time = Instant::now(); | ||
has_played_audio = true; | ||
} | ||
Err(e) => { | ||
eprintln!("Failed to decode received audio: {}", e); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
_ = interval.tick() => { | ||
// No audio data received in the past interval_duration ms and we've | ||
// already played some audio -- that means we can proceed with dialogue | ||
// by writing a followup question into JetStream through jet::writer. | ||
if has_played_audio && last_play_time.elapsed() >= interval_duration && sink.empty() { | ||
if !audio_data.is_empty() { | ||
let cursor = Cursor::new(audio_data.clone().freeze().to_vec()); | ||
if let Ok(source) = Decoder::new(cursor) { | ||
sink.append(source); | ||
audio_data.clear(); | ||
} | ||
} | ||
sink.sleep_until_end(); | ||
// NOTE: notify jet::writer | ||
audio_done.send(true)?; | ||
has_played_audio = false; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Flush any remaining data | ||
if !audio_data.is_empty() { | ||
let cursor = Cursor::new(audio_data.clone().to_vec()); | ||
if let Ok(source) = Decoder::new(cursor) { | ||
sink.append(source); | ||
} | ||
} | ||
if !sink.empty() { | ||
sink.sleep_until_end(); | ||
} | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
use bytes::{BufMut, Bytes, BytesMut}; | ||
use std::error::Error; | ||
use std::fmt; | ||
|
||
#[derive(Debug)] | ||
pub struct BufferFullError { | ||
pub bytes_written: usize, | ||
} | ||
|
||
impl fmt::Display for BufferFullError { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
write!(f, "buffer is full, {} bytes written", self.bytes_written) | ||
} | ||
} | ||
|
||
impl Error for BufferFullError {} | ||
|
||
pub struct Buffer { | ||
buffer: BytesMut, | ||
max_size: usize, | ||
} | ||
|
||
impl Buffer { | ||
pub fn new(max_size: usize) -> Self { | ||
Buffer { | ||
buffer: BytesMut::with_capacity(max_size), | ||
max_size, | ||
} | ||
} | ||
|
||
pub fn write(&mut self, data: &[u8]) -> Result<usize, BufferFullError> { | ||
let available = self.max_size - self.buffer.len(); | ||
let write_len = std::cmp::min(data.len(), available); | ||
|
||
self.buffer.put_slice(&data[..write_len]); | ||
|
||
if self.buffer.len() == self.max_size { | ||
return Err(BufferFullError { | ||
bytes_written: write_len, | ||
}); | ||
} | ||
Ok(write_len) | ||
} | ||
|
||
pub fn reset(&mut self) { | ||
self.buffer.clear(); | ||
} | ||
|
||
pub fn as_bytes(&self) -> Bytes { | ||
self.buffer.clone().freeze() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.