First progress bar version

This commit is contained in:
2025-03-31 23:58:06 +02:00
parent cd16a3b340
commit 39468ce5ba
5 changed files with 137 additions and 11 deletions

View File

@@ -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);

View File

@@ -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<()> {

View File

@@ -1,3 +1,5 @@
pub mod queue;
pub mod progress;
pub use queue::*;
pub use progress::*;

83
src/widgets/progress.rs Normal file
View 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);
}
}
}

View File

@@ -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([