Audio playback mvp
parent
4ba9d6cf12
commit
a3f03702df
10
Cargo.toml
10
Cargo.toml
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ pub enum MessageType {
|
|||
ToggleSuffle,
|
||||
ToggleNext,
|
||||
ToggleRepeat,
|
||||
PlayTrack,
|
||||
TogglePause,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue