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"
|
crossterm = "0.27.0"
|
||||||
interprocess = "1.2.1"
|
interprocess = "1.2.1"
|
||||||
ratatui = "0.23.0"
|
ratatui = "0.23.0"
|
||||||
|
rodio = {version = "0.17.3", features = [
|
||||||
|
"claxon",
|
||||||
|
"flac",
|
||||||
|
"hound",
|
||||||
|
"lewton",
|
||||||
|
"mp3",
|
||||||
|
"symphonia-all",
|
||||||
|
"vorbis",
|
||||||
|
"wav",
|
||||||
|
]}
|
||||||
serde = "1.0.196"
|
serde = "1.0.196"
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,20 @@ impl App {
|
||||||
self.push_message(Message::new(MessageType::StateFetch, None));
|
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) {
|
pub fn on_key(&mut self, key: char) {
|
||||||
match key {
|
match key {
|
||||||
'S' => self.toggle_shuffle(),
|
'S' => self.toggle_shuffle(),
|
||||||
'X' => self.toggle_next(),
|
'X' => self.toggle_next(),
|
||||||
'R' => self.toggle_repeat(),
|
'R' => self.toggle_repeat(),
|
||||||
|
' ' => self.toggle_pause(),
|
||||||
'q' => self.should_quit = true,
|
'q' => self.should_quit = true,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
@ -69,11 +78,13 @@ impl App {
|
||||||
|
|
||||||
pub fn on_down(&mut self) {}
|
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_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));
|
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![])
|
let playlist = List::new(vec![])
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ pub mod protocol;
|
||||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||||
pub struct ServerState {
|
pub struct ServerState {
|
||||||
pub playlist_params: PlaylistParams,
|
pub playlist_params: PlaylistParams,
|
||||||
pub directory_playlist: Option<DirectoryPlaylist>,
|
|
||||||
pub queue_playlist: QueuePlaylist,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type PlaylistElement = PathBuf;
|
pub type PlaylistElement = PathBuf;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ pub enum MessageType {
|
||||||
ToggleSuffle,
|
ToggleSuffle,
|
||||||
ToggleNext,
|
ToggleNext,
|
||||||
ToggleRepeat,
|
ToggleRepeat,
|
||||||
|
PlayTrack,
|
||||||
|
TogglePause,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,22 @@ use std::{
|
||||||
|
|
||||||
use crate::CliArgs;
|
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 struct Server {
|
||||||
pub state: ServerState,
|
pub state: ServerState,
|
||||||
|
pub playback: Playback,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn from_state(state: ServerState) -> Self {
|
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);
|
let exit_code = handle_error(err);
|
||||||
std::process::exit(exit_code);
|
std::process::exit(exit_code);
|
||||||
}
|
}
|
||||||
serve(ServerState {
|
|
||||||
|
let server = Arc::new(Mutex::new(Server::from_state(ServerState {
|
||||||
playlist_params: PlaylistParams {
|
playlist_params: PlaylistParams {
|
||||||
shuffle: args.shuffle,
|
shuffle: args.shuffle,
|
||||||
next: args.next,
|
next: args.next,
|
||||||
repeat: args.repeat,
|
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() {
|
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()?;
|
let socket_path = os::get_socket_path()?;
|
||||||
if socket_path.exists() {
|
if socket_path.exists() {
|
||||||
fs::remove_file(&socket_path).map_err(|err| ServerError::Io(err))?;
|
fs::remove_file(&socket_path).map_err(|err| ServerError::Io(err))?;
|
||||||
}
|
}
|
||||||
let socket = LocalSocketListener::bind(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...");
|
println!("Waiting for connections...");
|
||||||
let mut session_counter = 0;
|
let mut session_counter = 0;
|
||||||
for message in socket.incoming() {
|
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> {
|
fn route_request(request: &Message, server: &mut Server) -> Result<Message, String> {
|
||||||
match request.message_type {
|
match request.message_type {
|
||||||
MessageType::StateFetch => {
|
MessageType::StateFetch => {}
|
||||||
return Message::state_response(&server.state);
|
|
||||||
}
|
|
||||||
MessageType::ToggleNext => {
|
MessageType::ToggleNext => {
|
||||||
server.state.playlist_params.next = !server.state.playlist_params.next;
|
server.state.playlist_params.next = !server.state.playlist_params.next;
|
||||||
return Message::state_response(&server.state);
|
|
||||||
}
|
}
|
||||||
MessageType::ToggleSuffle => {
|
MessageType::ToggleSuffle => {
|
||||||
server.state.playlist_params.shuffle = !server.state.playlist_params.shuffle;
|
server.state.playlist_params.shuffle = !server.state.playlist_params.shuffle;
|
||||||
return Message::state_response(&server.state);
|
|
||||||
}
|
}
|
||||||
MessageType::ToggleRepeat => {
|
MessageType::ToggleRepeat => {
|
||||||
server.state.playlist_params.repeat = !server.state.playlist_params.repeat;
|
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