Initial v2.2.0 push

This commit is contained in:
DaXcess
2024-08-12 16:14:24 +02:00
parent d1c01e9274
commit 97c18385dd
97 changed files with 7252 additions and 5211 deletions
+12
View File
@@ -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"
+2
View File
@@ -0,0 +1,2 @@
pub mod sink;
pub mod stream;
+68
View File
@@ -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(())
}
}
+88
View File
@@ -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
}
}