Audio playback mvp

master
hheik 2024-02-24 00:03:32 +02:00
parent 4ba9d6cf12
commit a3f03702df
7 changed files with 129 additions and 21 deletions

View File

@ -12,4 +12,14 @@ crc32fast = "1.3.2"
crossterm = "0.27.0"
interprocess = "1.2.1"
ratatui = "0.23.0"
rodio = {version = "0.17.3", features = [
"claxon",
"flac",
"hound",
"lewton",
"mp3",
"symphonia-all",
"vorbis",
"wav",
]}
serde = "1.0.196"

View File

@ -51,11 +51,20 @@ impl App {
self.push_message(Message::new(MessageType::StateFetch, None));
}
pub fn play(&mut self) {
self.push_message(Message::new(MessageType::PlayTrack, None));
}
pub fn toggle_pause(&mut self) {
self.push_message(Message::new(MessageType::TogglePause, None));
}
pub fn on_key(&mut self, key: char) {
match key {
'S' => self.toggle_shuffle(),
'X' => self.toggle_next(),
'R' => self.toggle_repeat(),
' ' => self.toggle_pause(),
'q' => self.should_quit = true,
_ => (),
}
@ -69,11 +78,13 @@ impl App {
pub fn on_down(&mut self) {}
pub fn on_enter(&mut self) {}
pub fn on_enter(&mut self) {
self.play();
}
pub fn on_tab(&mut self) {}
pub fn on_tick(&mut self, duration: Duration) {
pub fn on_tick(&mut self, _duration: Duration) {
self.push_message(Message::new(MessageType::StateFetch, None));
}
}

View File

@ -47,7 +47,7 @@ fn draw_no_connection<B: Backend>(f: &mut Frame<B>) {
);
}
fn draw_playlist<B: Backend>(f: &mut Frame<B>, state: &ServerState, area: Rect) {
fn draw_playlist<B: Backend>(f: &mut Frame<B>, _state: &ServerState, area: Rect) {
let playlist = List::new(vec![])
.block(
Block::default()

View File

@ -10,8 +10,6 @@ pub mod protocol;
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct ServerState {
pub playlist_params: PlaylistParams,
pub directory_playlist: Option<DirectoryPlaylist>,
pub queue_playlist: QueuePlaylist,
}
pub type PlaylistElement = PathBuf;

View File

@ -35,6 +35,8 @@ pub enum MessageType {
ToggleSuffle,
ToggleNext,
ToggleRepeat,
PlayTrack,
TogglePause,
}
#[derive(Debug, Serialize, Deserialize)]

View File

@ -13,16 +13,22 @@ use std::{
use crate::CliArgs;
pub mod audio_backend;
use self::playback::{playback_manager, Playback};
pub mod audio_backend;
pub mod playback;
#[derive(Debug)]
pub struct Server {
pub state: ServerState,
pub playback: Playback,
}
impl Server {
pub fn from_state(state: ServerState) -> Self {
Self { state }
Self {
state,
playback: Default::default(),
}
}
}
@ -31,14 +37,22 @@ pub fn run(args: CliArgs) -> Result<(), ServerError> {
let exit_code = handle_error(err);
std::process::exit(exit_code);
}
serve(ServerState {
let server = Arc::new(Mutex::new(Server::from_state(ServerState {
playlist_params: PlaylistParams {
shuffle: args.shuffle,
next: args.next,
repeat: args.repeat,
},
..Default::default()
})
})));
let server_clone = server.clone();
std::thread::Builder::new()
.name("playback_manager".into())
.spawn(move || playback_manager(server_clone))
.unwrap();
serve(server)
}
pub fn kill() {
@ -65,13 +79,12 @@ pub fn run_in_background() {
}
}
fn serve(state: ServerState) -> Result<(), ServerError> {
fn serve(server: Arc<Mutex<Server>>) -> Result<(), ServerError> {
let socket_path = os::get_socket_path()?;
if socket_path.exists() {
fs::remove_file(&socket_path).map_err(|err| ServerError::Io(err))?;
}
let socket = LocalSocketListener::bind(socket_path).map_err(|err| ServerError::Io(err))?;
let server = Arc::new(Mutex::new(Server::from_state(state)));
println!("Waiting for connections...");
let mut session_counter = 0;
for message in socket.incoming() {
@ -153,22 +166,23 @@ fn handle_error(err: ServerError) -> i32 {
fn route_request(request: &Message, server: &mut Server) -> Result<Message, String> {
match request.message_type {
MessageType::StateFetch => {
return Message::state_response(&server.state);
}
MessageType::StateFetch => {}
MessageType::ToggleNext => {
server.state.playlist_params.next = !server.state.playlist_params.next;
return Message::state_response(&server.state);
}
MessageType::ToggleSuffle => {
server.state.playlist_params.shuffle = !server.state.playlist_params.shuffle;
return Message::state_response(&server.state);
}
MessageType::ToggleRepeat => {
server.state.playlist_params.repeat = !server.state.playlist_params.repeat;
return Message::state_response(&server.state);
}
_ => {}
MessageType::PlayTrack => {
server.playback.play_test();
}
MessageType::TogglePause => {
server.playback.pause_test();
}
_ => return Ok(Message::new(MessageType::NotImplementedAck, None)),
}
Ok(Message::new(MessageType::NotImplementedAck, None))
return Message::state_response(&server.state);
}

73
src/server/playback.rs Normal file
View File

@ -0,0 +1,73 @@
use std::{
fs::File,
io::BufReader,
path::PathBuf,
sync::{Arc, Mutex},
};
use rodio::{Decoder, OutputStream, Sink};
use super::Server;
// HACK: hard-coded path to track
static TRACK: &str = "";
enum PlaybackCommand {
Play(PathBuf),
TogglePause,
}
pub struct Playback {
command_queue: Vec<PlaybackCommand>,
}
impl Default for Playback {
fn default() -> Self {
Self {
command_queue: vec![],
}
}
}
impl Playback {
pub fn play_test(&mut self) {
self.command_queue.push(PlaybackCommand::Play(TRACK.into()))
}
pub fn pause_test(&mut self) {
self.command_queue.push(PlaybackCommand::TogglePause)
}
}
pub fn playback_manager(server: Arc<Mutex<Server>>) {
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let sink: Sink = Sink::try_new(&stream_handle).unwrap();
loop {
for command in server.lock().unwrap().playback.command_queue.drain(..) {
match command {
PlaybackCommand::Play(track) => {
let file = BufReader::new(File::open(&track).unwrap());
let source = match Decoder::new(file) {
Ok(source) => source,
Err(err) => {
eprintln!("Decode error:\n\ttrack: {track:?}\n\tinfo: {err:?}");
continue;
}
};
sink.clear();
sink.append(source);
sink.play();
}
PlaybackCommand::TogglePause => {
if !sink.empty() {
if sink.is_paused() {
sink.play()
} else {
sink.pause()
}
}
}
}
}
}
}