Big audio backend cleanup, small bugfix on auto-disconnect
This commit is contained in:
		
							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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DaXcess
						DaXcess