Initial v2.2.0 push
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "spoticord_audio"
|
||||
version = "2.2.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
librespot = { git = "https://github.com/SpoticordMusic/librespot.git", version = "0.5.0-dev", default-features = false }
|
||||
songbird = { version = "0.4.3", features = ["simd-json"] }
|
||||
tokio = { version = "1.39.2", features = ["sync"], default-features = false }
|
||||
zerocopy = "0.7.35"
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod sink;
|
||||
pub mod stream;
|
||||
@@ -0,0 +1,68 @@
|
||||
use crate::stream::Stream;
|
||||
use librespot::playback::audio_backend::{Sink, SinkAsBytes, SinkError, SinkResult};
|
||||
use librespot::playback::convert::Converter;
|
||||
use librespot::playback::decoder::AudioPacket;
|
||||
use std::io::Write;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
pub enum SinkEvent {
|
||||
Start,
|
||||
Stop,
|
||||
}
|
||||
|
||||
pub struct StreamSink {
|
||||
stream: Stream,
|
||||
sender: UnboundedSender<SinkEvent>,
|
||||
}
|
||||
|
||||
impl StreamSink {
|
||||
pub fn new(stream: Stream, sender: UnboundedSender<SinkEvent>) -> Self {
|
||||
Self { stream, sender }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink for StreamSink {
|
||||
fn start(&mut self) -> SinkResult<()> {
|
||||
if let Err(_why) = self.sender.send(SinkEvent::Start) {
|
||||
// WARNING: Returning an error causes librespot-playback to exit the process with status 1
|
||||
|
||||
// return Err(SinkError::ConnectionRefused(_why.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> SinkResult<()> {
|
||||
if let Err(_why) = self.sender.send(SinkEvent::Stop) {
|
||||
// WARNING: Returning an error causes librespot-playback to exit the process with status 1
|
||||
|
||||
// return Err(SinkError::ConnectionRefused(_why.to_string()));
|
||||
}
|
||||
|
||||
self.stream.flush().ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()> {
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
let AudioPacket::Samples(samples) = packet else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.write_bytes(converter.f64_to_f32(&samples).as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SinkAsBytes for StreamSink {
|
||||
fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()> {
|
||||
self.stream
|
||||
.write_all(data)
|
||||
.map_err(|why| SinkError::OnWrite(why.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use std::{
|
||||
io::{Read, Seek, Write},
|
||||
sync::{Arc, Condvar, Mutex},
|
||||
};
|
||||
|
||||
use songbird::input::core::io::MediaSource;
|
||||
|
||||
/// The lower the value, the less latency
|
||||
///
|
||||
/// Too low of a value results in jittery audio
|
||||
const BUFFER_SIZE: usize = 64 * 1024;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Stream {
|
||||
inner: Arc<(Mutex<Vec<u8>>, Condvar)>,
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Stream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let (mutex, condvar) = &*self.inner;
|
||||
let mut buffer = mutex.lock().expect("Mutex was poisoned");
|
||||
|
||||
// Prevent Discord jitter by filling buffer with zeroes if we don't have any audio
|
||||
// (i.e. when you skip too far ahead in a song which hasn't been downloaded yet)
|
||||
if buffer.is_empty() {
|
||||
buf.fill(0);
|
||||
condvar.notify_all();
|
||||
|
||||
return Ok(buf.len());
|
||||
}
|
||||
|
||||
let max_read = usize::min(buf.len(), buffer.len());
|
||||
|
||||
buf[0..max_read].copy_from_slice(&buffer[0..max_read]);
|
||||
buffer.drain(0..max_read);
|
||||
condvar.notify_all();
|
||||
|
||||
Ok(max_read)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Stream {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let (mutex, condvar) = &*self.inner;
|
||||
let mut buffer = mutex.lock().expect("Mutex was poisoned");
|
||||
|
||||
while buffer.len() + buf.len() > BUFFER_SIZE {
|
||||
buffer = condvar.wait(buffer).expect("Mutex was poisoned");
|
||||
}
|
||||
|
||||
buffer.extend_from_slice(buf);
|
||||
condvar.notify_all();
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
let (mutex, condvar) = &*self.inner;
|
||||
let mut buffer = mutex.lock().expect("Mutex was poisoned");
|
||||
|
||||
buffer.clear();
|
||||
condvar.notify_all();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for Stream {
|
||||
fn seek(&mut self, _: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaSource for Stream {
|
||||
fn byte_len(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_seekable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user