Implement track queueing, play current queue track

This commit is contained in:
chmanie 2023-05-27 02:13:16 +02:00
parent fc0b48941d
commit ea5597aab2
2 changed files with 100 additions and 31 deletions

View File

@ -7,7 +7,10 @@ use crabidy_core::proto::crabidy::{
}; };
use crossterm::{ use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind}, event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers,
ModifierKeyCode,
},
execute, execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
}; };
@ -71,9 +74,22 @@ trait ListView {
} }
} }
#[derive(Clone, Copy)]
enum UiFocus {
Library,
Queue,
}
#[derive(Clone, Copy)]
enum UiItemKind {
Node,
Track,
}
struct UiItem { struct UiItem {
uuid: String, uuid: String,
title: String, title: String,
kind: UiItemKind,
} }
struct QueueView { struct QueueView {
@ -103,12 +119,6 @@ impl ListView for QueueView {
} }
} }
#[derive(Clone, Copy)]
enum UiFocus {
Library,
Queue,
}
impl QueueView { impl QueueView {
fn check_focus(&mut self, focus: UiFocus) { fn check_focus(&mut self, focus: UiFocus) {
if !self.is_selected() && matches!(focus, UiFocus::Queue) { if !self.is_selected() && matches!(focus, UiFocus::Queue) {
@ -117,6 +127,18 @@ impl QueueView {
self.select(None); self.select(None);
} }
} }
fn skip(&self, tx: &Sender<MessageFromUi>) {
if let Some(pos) = self.selected() {
if pos < self.get_size() - 1 {
tx.send(MessageFromUi::SetCurrentTrack(pos + 1));
}
}
}
fn play_selected(&self, tx: &Sender<MessageFromUi>) {
if let Some(pos) = self.selected() {
tx.send(MessageFromUi::SetCurrentTrack(pos));
}
}
fn update(&mut self, queue: Queue) { fn update(&mut self, queue: Queue) {
self.list = queue self.list = queue
.tracks .tracks
@ -124,6 +146,7 @@ impl QueueView {
.map(|t| UiItem { .map(|t| UiItem {
uuid: t.uuid.clone(), uuid: t.uuid.clone(),
title: t.title.clone(), title: t.title.clone(),
kind: UiItemKind::Track,
}) })
.collect(); .collect();
} }
@ -183,12 +206,14 @@ impl LibraryView {
} }
fn dive(&mut self, tx: &Sender<MessageFromUi>) { fn dive(&mut self, tx: &Sender<MessageFromUi>) {
if let Some(item) = self.get_selected() { if let Some(item) = self.get_selected() {
tx.send(MessageFromUi::GetLibraryNode(item.uuid.clone())); if let UiItemKind::Node = item.kind {
tx.send(MessageFromUi::GetLibraryNode(item.uuid.clone()));
}
} }
} }
fn queue_replace_with_selected(&mut self, tx: &Sender<MessageFromUi>) { fn queue_replace_with_item(&mut self, tx: &Sender<MessageFromUi>) {
if let Some(item) = self.get_selected() { if let Some(item) = self.get_selected() {
tx.send(MessageFromUi::ReplaceWithNode(item.uuid.clone())); tx.send(MessageFromUi::ReplaceWithItem(item.uuid.clone(), item.kind));
} }
} }
fn update(&mut self, node: LibraryNode) { fn update(&mut self, node: LibraryNode) {
@ -209,6 +234,7 @@ impl LibraryView {
.map(|t| UiItem { .map(|t| UiItem {
uuid: t.uuid.clone(), uuid: t.uuid.clone(),
title: t.title.clone(), title: t.title.clone(),
kind: UiItemKind::Track,
}) })
.collect(); .collect();
} else { } else {
@ -219,6 +245,7 @@ impl LibraryView {
.map(|c| UiItem { .map(|c| UiItem {
uuid: c.uuid.clone(), uuid: c.uuid.clone(),
title: c.title.clone(), title: c.title.clone(),
kind: UiItemKind::Node,
}) })
.collect(); .collect();
} }
@ -295,14 +322,15 @@ enum MessageToUi {
enum MessageFromUi { enum MessageFromUi {
Quit, Quit,
GetLibraryNode(String), GetLibraryNode(String),
ReplaceWithNode(String), ReplaceWithItem(String, UiItemKind),
SetCurrentTrack(usize),
TogglePlay, TogglePlay,
} }
async fn orchestrate<'a>( async fn orchestrate<'a>(
(tx, rx): (Sender<MessageToUi>, Receiver<MessageFromUi>), (tx, rx): (Sender<MessageToUi>, Receiver<MessageFromUi>),
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
let mut rpc_client = rpc::RpcClient::connect("http://[::1]:50051").await?; let mut rpc_client = rpc::RpcClient::connect("http://192.168.178.28:50051").await?;
if let Some(root_node) = rpc_client.get_library_node("/").await? { if let Some(root_node) = rpc_client.get_library_node("/").await? {
tx.send(MessageToUi::ReplaceLibraryNode(root_node.clone())); tx.send(MessageToUi::ReplaceLibraryNode(root_node.clone()));
@ -324,12 +352,23 @@ async fn orchestrate<'a>(
tx.send(MessageToUi::ReplaceLibraryNode(node.clone())); tx.send(MessageToUi::ReplaceLibraryNode(node.clone()));
} }
}, },
MessageFromUi::ReplaceWithNode(uuid) => { MessageFromUi::ReplaceWithItem(uuid, kind) => {
rpc_client.replace_queue_with(&uuid).await? match kind {
UiItemKind::Node => {
rpc_client.replace_queue_with_node(&uuid).await?
}
UiItemKind::Track => {
rpc_client.replace_queue_with_track(&uuid).await?
}
}
} }
MessageFromUi::TogglePlay => { MessageFromUi::TogglePlay => {
rpc_client.toggle_play().await? rpc_client.toggle_play().await?
} }
MessageFromUi::SetCurrentTrack(pos) => {
rpc_client.set_current_track(pos).await?
}
} }
} }
Some(Ok(resp)) = queue_update_stream.next() => { Some(Ok(resp)) = queue_update_stream.next() => {
@ -404,36 +443,42 @@ fn run_ui(tx: Sender<MessageFromUi>, rx: Receiver<MessageToUi>) {
if crossterm::event::poll(timeout).unwrap() { if crossterm::event::poll(timeout).unwrap() {
if let Event::Key(key) = event::read().unwrap() { if let Event::Key(key) = event::read().unwrap() {
if key.kind == KeyEventKind::Press { if key.kind == KeyEventKind::Press {
match (app.focus, key.code) { match (app.focus, key.modifiers, key.code) {
(_, KeyCode::Char('q')) => { (_, KeyModifiers::NONE, KeyCode::Char('q')) => {
tx.send(MessageFromUi::Quit); tx.send(MessageFromUi::Quit);
break; break;
} }
(_, KeyCode::Tab) => app.cycle_active(), (_, KeyModifiers::NONE, KeyCode::Tab) => app.cycle_active(),
(_, KeyCode::Char(' ')) => { (_, KeyModifiers::NONE, KeyCode::Char(' ')) => {
tx.send(MessageFromUi::TogglePlay); tx.send(MessageFromUi::TogglePlay);
} }
(UiFocus::Library, KeyCode::Char('j')) => { (_, KeyModifiers::CONTROL, KeyCode::Char('n')) => {
app.queue.skip(&tx);
}
(UiFocus::Library, KeyModifiers::NONE, KeyCode::Char('j')) => {
app.library.next(); app.library.next();
} }
(UiFocus::Library, KeyCode::Char('k')) => { (UiFocus::Library, KeyModifiers::NONE, KeyCode::Char('k')) => {
app.library.prev(); app.library.prev();
} }
(UiFocus::Library, KeyCode::Char('h')) => { (UiFocus::Library, KeyModifiers::NONE, KeyCode::Char('h')) => {
app.library.ascend(&tx); app.library.ascend(&tx);
} }
(UiFocus::Library, KeyCode::Char('l')) => { (UiFocus::Library, KeyModifiers::NONE, KeyCode::Char('l')) => {
app.library.dive(&tx); app.library.dive(&tx);
} }
(UiFocus::Library, KeyCode::Enter) => { (UiFocus::Library, KeyModifiers::NONE, KeyCode::Enter) => {
app.library.queue_replace_with_selected(&tx); app.library.queue_replace_with_item(&tx);
} }
(UiFocus::Queue, KeyCode::Char('j')) => { (UiFocus::Queue, KeyModifiers::NONE, KeyCode::Char('j')) => {
app.queue.next(); app.queue.next();
} }
(UiFocus::Queue, KeyCode::Char('k')) => { (UiFocus::Queue, KeyModifiers::NONE, KeyCode::Char('k')) => {
app.queue.prev(); app.queue.prev();
} }
(UiFocus::Queue, KeyModifiers::NONE, KeyCode::Enter) => {
app.queue.play_selected(&tx);
}
_ => {} _ => {}
} }
} }

View File

@ -2,7 +2,8 @@ use crabidy_core::proto::crabidy::{
crabidy_service_client::CrabidyServiceClient, get_queue_updates_response::QueueUpdateResult, crabidy_service_client::CrabidyServiceClient, get_queue_updates_response::QueueUpdateResult,
GetLibraryNodeRequest, GetQueueUpdatesRequest, GetQueueUpdatesResponse, GetTrackUpdatesRequest, GetLibraryNodeRequest, GetQueueUpdatesRequest, GetQueueUpdatesResponse, GetTrackUpdatesRequest,
GetTrackUpdatesResponse, LibraryNode, LibraryNodeState, ReplaceWithNodeRequest, GetTrackUpdatesResponse, LibraryNode, LibraryNodeState, ReplaceWithNodeRequest,
ReplaceWithNodeResponse, TogglePlayRequest, ReplaceWithNodeResponse, ReplaceWithTrackRequest, ReplaceWithTrackResponse,
SetCurrentTrackRequest, SetCurrentTrackResponse, TogglePlayRequest,
}; };
use std::{ use std::{
@ -105,7 +106,7 @@ impl RpcClient {
Ok(stream) Ok(stream)
} }
pub async fn replace_queue_with(&mut self, uuid: &str) -> Result<(), Box<dyn Error>> { pub async fn replace_queue_with_node(&mut self, uuid: &str) -> Result<(), Box<dyn Error>> {
let replace_with_node_request = Request::new(ReplaceWithNodeRequest { let replace_with_node_request = Request::new(ReplaceWithNodeRequest {
uuid: uuid.to_string(), uuid: uuid.to_string(),
}); });
@ -118,12 +119,35 @@ impl RpcClient {
Ok(()) Ok(())
} }
pub async fn toggle_play(&mut self) -> Result<(), Box<dyn Error>> { pub async fn replace_queue_with_track(&mut self, uuid: &str) -> Result<(), Box<dyn Error>> {
let toggle_play_request = Request::new(TogglePlayRequest {}); let replace_with_track_request = Request::new(ReplaceWithTrackRequest {
uuid: uuid.to_string(),
});
let response = self let response = self
.client .client
.toggle_play(toggle_play_request) .replace_with_track(replace_with_track_request)
.await?;
Ok(())
}
pub async fn toggle_play(&mut self) -> Result<(), Box<dyn Error>> {
let toggle_play_request = Request::new(TogglePlayRequest {});
let response = self.client.toggle_play(toggle_play_request).await?;
Ok(())
}
pub async fn set_current_track(&mut self, pos: usize) -> Result<(), Box<dyn Error>> {
let set_current_track_request = Request::new(SetCurrentTrackRequest {
position: pos as u32,
});
let response = self
.client
.set_current_track(set_current_track_request)
.await?; .await?;
Ok(()) Ok(())