First progress bar version
This commit is contained in:
19
src/app.rs
19
src/app.rs
@@ -3,7 +3,7 @@ use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent};
|
||||
use ratatui::{
|
||||
Terminal,
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Flex, Layout, Rect},
|
||||
layout::{Constraint, Direction, Flex, Layout, Rect},
|
||||
prelude::CrosstermBackend,
|
||||
widgets::{Block, Clear, Paragraph, StatefulWidget, TableState, Widget, Wrap},
|
||||
};
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
event::{Event, Events},
|
||||
player::Player,
|
||||
subsonic_helper,
|
||||
widgets::Queue,
|
||||
widgets::{Progress, Queue},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
@@ -161,7 +161,7 @@ impl App {
|
||||
Action::TryPlaySong(id) => self.player.try_play(id, self.client.clone())?,
|
||||
Action::AddToSink(id) => {
|
||||
let res = self.player.add_to_sink(id);
|
||||
if res.is_err(){
|
||||
if res.is_err() {
|
||||
self.tx.send(Action::TrackEnded)?;
|
||||
}
|
||||
res?
|
||||
@@ -229,18 +229,27 @@ impl App {
|
||||
|
||||
impl Widget for &mut App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(vec![Constraint::Min(0), Constraint::Percentage(15)])
|
||||
.split(area);
|
||||
|
||||
match self.mode {
|
||||
Mode::Help => {}
|
||||
Mode::Popup => {}
|
||||
Mode::Quit => {}
|
||||
Mode::Queue => StatefulWidget::render(
|
||||
Mode::Queue =>
|
||||
StatefulWidget::render(
|
||||
&Queue::new(self.player.songs_list.clone(), self.player.current_playing),
|
||||
area,
|
||||
layout[0],
|
||||
buf,
|
||||
&mut self.queue_state,
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
Widget::render(&Progress::new(&self.player), layout[1],buf);
|
||||
|
||||
if let Some((title, message)) = &mut self.popup {
|
||||
let block = Block::bordered().title(title.as_str());
|
||||
let area = popup_area(area, 60, 20);
|
||||
|
||||
@@ -3,7 +3,6 @@ use cpal::traits::HostTrait;
|
||||
use futures::StreamExt;
|
||||
use reqwest::Client;
|
||||
use rodio::{OutputStream, Sink, Source};
|
||||
use tracing::debug;
|
||||
use std::fs::{self, File};
|
||||
use std::io::BufReader;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -11,6 +10,7 @@ use std::time::Duration;
|
||||
use subsonic_types::request;
|
||||
use subsonic_types::response::Child;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::action::Action;
|
||||
use crate::config;
|
||||
@@ -95,7 +95,7 @@ impl Player {
|
||||
let file = std::fs::File::open(&path)?;
|
||||
|
||||
let decoder = rodio::Decoder::new(BufReader::new(file))?;
|
||||
// This help having a constant volume
|
||||
// This help having a constant volume
|
||||
let agc_source = decoder.automatic_gain_control(1.0, 4.0, 0.005, 5.0);
|
||||
|
||||
self.sink.append(agc_source);
|
||||
@@ -140,6 +140,21 @@ impl Player {
|
||||
pub fn skip_next(&self) {
|
||||
self.sink.skip_one();
|
||||
}
|
||||
|
||||
pub fn current_pos_duration(&self) -> Duration {
|
||||
self.sink.get_pos()
|
||||
}
|
||||
pub fn ratio_played(&self) -> f64 {
|
||||
if let Some(song) = self.songs_list.lock().unwrap().get(self.current_playing) {
|
||||
self.sink.get_pos().as_secs() as f64
|
||||
/ song
|
||||
.duration
|
||||
.map(|d| d.to_duration().as_secs())
|
||||
.unwrap_or(0) as f64
|
||||
} else {
|
||||
0.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn async_audio_dl(id: String, client: Client) -> Result<()> {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod queue;
|
||||
pub mod progress;
|
||||
|
||||
pub use queue::*;
|
||||
pub use progress::*;
|
||||
|
||||
83
src/widgets/progress.rs
Normal file
83
src/widgets/progress.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use ratatui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Style, Stylize},
|
||||
text::Text,
|
||||
widgets::{Block, BorderType, Borders, Gauge, Widget},
|
||||
};
|
||||
|
||||
use crate::player::Player;
|
||||
|
||||
pub struct Progress<'a> {
|
||||
player: &'a Player,
|
||||
}
|
||||
|
||||
impl<'a> Progress<'a> {
|
||||
pub fn new(player: &'a Player) -> Self {
|
||||
Self { player }
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Progress<'_> {
|
||||
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) {
|
||||
let ratio = self.player.ratio_played();
|
||||
let pos = self.player.current_pos_duration().as_secs();
|
||||
if let Some(song) = self
|
||||
.player
|
||||
.songs_list
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(self.player.current_playing)
|
||||
{
|
||||
Block::default()
|
||||
.title(format!(
|
||||
"{} - {}",
|
||||
song.title.clone(),
|
||||
song.artist.clone().unwrap_or_default()
|
||||
))
|
||||
.borders(Borders::all())
|
||||
.border_type(BorderType::Rounded)
|
||||
.render(area, buf);
|
||||
let layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(vec![
|
||||
Constraint::Min(0),
|
||||
Constraint::Length(1),
|
||||
Constraint::Min(0),
|
||||
])
|
||||
.split(area);
|
||||
let inner_layout = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(vec![
|
||||
Constraint::Max(25),
|
||||
Constraint::Min(0),
|
||||
Constraint::Max(25),
|
||||
])
|
||||
.split(layout[1]);
|
||||
|
||||
Text::from(format!(
|
||||
"{:0>2}:{:0>2}:{:0>2} ",
|
||||
pos / (60 * 60),
|
||||
pos / 60,
|
||||
pos % 60
|
||||
))
|
||||
.right_aligned()
|
||||
.render(inner_layout[0], buf);
|
||||
|
||||
let pos = song.duration.map(|d| d.to_duration().as_secs() ).unwrap_or_default();
|
||||
Text::from(format!(
|
||||
" {:0>2}:{:0>2}:{:0>2}",
|
||||
pos / (60 * 60),
|
||||
pos / 60,
|
||||
pos % 60
|
||||
))
|
||||
.left_aligned()
|
||||
.render(inner_layout[2], buf);
|
||||
|
||||
Gauge::default()
|
||||
.label("")
|
||||
.gauge_style(Style::new().cyan())
|
||||
.ratio(ratio)
|
||||
.render(inner_layout[1], buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,8 @@ impl Queue {
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulWidget for &Queue {
|
||||
type State = TableState;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
impl Queue {
|
||||
fn render_table(&self, area: Rect, buf: &mut Buffer, state: Option<&mut TableState>) {
|
||||
if !self.songs_list.lock().unwrap().is_empty() {
|
||||
let header_cells = ["#", "Title", "Artist", "Duration"].map(|h| h.bold());
|
||||
let header = Row::new(header_cells).height(2);
|
||||
@@ -80,13 +79,31 @@ impl StatefulWidget for &Queue {
|
||||
.highlight_symbol(">>")
|
||||
.block(block);
|
||||
|
||||
StatefulWidget::render(table, area, buf, state);
|
||||
if let Some(state) = state {
|
||||
StatefulWidget::render(table, area, buf, state);
|
||||
} else {
|
||||
Widget::render(table, area, buf);
|
||||
}
|
||||
|
||||
} else {
|
||||
Text::render(Text::from("Empty queue").centered(), area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &Queue {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_table(area, buf, None);
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulWidget for &Queue {
|
||||
type State = TableState;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
self.render_table(area, buf, Some(state));
|
||||
}
|
||||
}
|
||||
|
||||
fn row_from_song(index: usize, song: &Child) -> Row {
|
||||
let duration = song.duration.unwrap().to_duration().as_secs();
|
||||
Row::new([
|
||||
|
||||
Reference in New Issue
Block a user