Big audio backend cleanup, small bugfix on auto-disconnect
parent
a37daa23a0
commit
7405656854
|
@ -1,125 +1,30 @@
|
|||
use librespot::playback::audio_backend::{Sink, SinkAsBytes, SinkResult};
|
||||
use librespot::playback::convert::Converter;
|
||||
use librespot::playback::decoder::AudioPacket;
|
||||
use log::{error, trace};
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::ipc;
|
||||
use crate::ipc::packet::IpcPacket;
|
||||
|
||||
pub struct StdoutSink {
|
||||
client: ipc::Client,
|
||||
buffer: Arc<Mutex<Vec<u8>>>,
|
||||
is_stopped: Arc<Mutex<bool>>,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
const BUFFER_SIZE: usize = 7680;
|
||||
|
||||
impl StdoutSink {
|
||||
pub fn start_writer(&mut self) {
|
||||
// With 48khz, 32-bit float, 2 channels, 1 second of audio is 384000 bytes
|
||||
// 384000 / 50 = 7680 bytes per 20ms
|
||||
|
||||
let buffer = self.buffer.clone();
|
||||
let is_stopped = self.is_stopped.clone();
|
||||
let client = self.client.clone();
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
let mut output = std::io::stdout();
|
||||
let mut act_buffer = [0u8; BUFFER_SIZE];
|
||||
|
||||
// Use closure to make sure lock is released as fast as possible
|
||||
let is_stopped = || {
|
||||
let is_stopped = is_stopped.lock().unwrap();
|
||||
*is_stopped
|
||||
};
|
||||
|
||||
// Start songbird's playback
|
||||
client.send(IpcPacket::StartPlayback).unwrap();
|
||||
|
||||
loop {
|
||||
if is_stopped() {
|
||||
break;
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_millis(15));
|
||||
|
||||
let mut buffer = buffer.lock().unwrap();
|
||||
let to_drain: usize;
|
||||
|
||||
if buffer.len() < BUFFER_SIZE {
|
||||
// Copy the buffer into the action buffer
|
||||
// Fill remaining length with zeroes
|
||||
act_buffer[..buffer.len()].copy_from_slice(&buffer[..]);
|
||||
act_buffer[buffer.len()..].fill(0);
|
||||
|
||||
to_drain = buffer.len();
|
||||
} else {
|
||||
act_buffer.copy_from_slice(&buffer[..BUFFER_SIZE]);
|
||||
to_drain = BUFFER_SIZE;
|
||||
}
|
||||
|
||||
output.write_all(&act_buffer).unwrap_or(());
|
||||
buffer.drain(..to_drain);
|
||||
}
|
||||
});
|
||||
|
||||
self.handle = Some(handle);
|
||||
}
|
||||
|
||||
pub fn stop_writer(&mut self) -> std::thread::Result<()> {
|
||||
// Use closure to avoid deadlocking the mutex
|
||||
let set_stopped = |value| {
|
||||
let mut is_stopped = self.is_stopped.lock().unwrap();
|
||||
*is_stopped = value;
|
||||
};
|
||||
|
||||
// Notify thread to stop
|
||||
set_stopped(true);
|
||||
|
||||
// Wait for thread to stop
|
||||
let result = match self.handle.take() {
|
||||
Some(handle) => handle.join(),
|
||||
None => Ok(()),
|
||||
};
|
||||
|
||||
// Reset stopped value
|
||||
set_stopped(false);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn new(client: ipc::Client) -> Self {
|
||||
StdoutSink {
|
||||
client,
|
||||
is_stopped: Arc::new(Mutex::new(false)),
|
||||
buffer: Arc::new(Mutex::new(Vec::new())),
|
||||
handle: None,
|
||||
}
|
||||
StdoutSink { client }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink for StdoutSink {
|
||||
fn start(&mut self) -> SinkResult<()> {
|
||||
self.start_writer();
|
||||
// TODO: Handle error
|
||||
self.client.send(IpcPacket::StartPlayback).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> SinkResult<()> {
|
||||
// Stop the writer thread
|
||||
// This is done before pausing songbird, because else the writer thread
|
||||
// might hang on writing to stdout
|
||||
if let Err(why) = self.stop_writer() {
|
||||
error!("Failed to stop stdout writer: {:?}", why);
|
||||
} else {
|
||||
trace!("Stopped stdout writer");
|
||||
}
|
||||
|
||||
// Stop songbird's playback
|
||||
self.client.send(IpcPacket::StopPlayback).unwrap();
|
||||
|
||||
|
@ -140,7 +45,11 @@ impl Sink for StdoutSink {
|
|||
&samples_f32,
|
||||
)
|
||||
.unwrap();
|
||||
self.write_bytes(resampled.as_bytes())?;
|
||||
|
||||
let samples_i16 =
|
||||
&converter.f64_to_s16(&resampled.iter().map(|v| *v as f64).collect::<Vec<f64>>());
|
||||
|
||||
self.write_bytes(samples_i16.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -149,18 +58,7 @@ impl Sink for StdoutSink {
|
|||
|
||||
impl SinkAsBytes for StdoutSink {
|
||||
fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()> {
|
||||
let get_buffer_len = || {
|
||||
let buffer = self.buffer.lock().unwrap();
|
||||
buffer.len()
|
||||
};
|
||||
|
||||
while get_buffer_len() > BUFFER_SIZE * 2 {
|
||||
std::thread::sleep(Duration::from_millis(15));
|
||||
}
|
||||
|
||||
let mut buffer = self.buffer.lock().unwrap();
|
||||
|
||||
buffer.extend_from_slice(data);
|
||||
std::io::stdout().write_all(data).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use serenity::{
|
|||
};
|
||||
use songbird::{
|
||||
create_player,
|
||||
input::{children_to_reader, Input},
|
||||
input::{children_to_reader, Codec, Container, Input},
|
||||
tracks::TrackHandle,
|
||||
Call, Event, EventContext, EventHandler,
|
||||
};
|
||||
|
@ -137,7 +137,8 @@ impl SpoticordSession {
|
|||
let reader = children_to_reader::<f32>(vec![child]);
|
||||
|
||||
// Create track (paused, fixes audio glitches)
|
||||
let (mut track, track_handle) = create_player(Input::float_pcm(true, reader));
|
||||
let (mut track, track_handle) =
|
||||
create_player(Input::new(true, reader, Codec::Pcm, Container::Raw, None));
|
||||
track.pause();
|
||||
|
||||
// Set call audio to track
|
||||
|
@ -549,6 +550,9 @@ impl SpoticordSession {
|
|||
loop {
|
||||
timer.tick().await;
|
||||
|
||||
// Make sure this task has not been aborted, if it has this will automatically stop execution.
|
||||
tokio::task::yield_now().await;
|
||||
|
||||
let is_playing = {
|
||||
let pbi = pbi.read().await;
|
||||
|
||||
|
|
Loading…
Reference in New Issue