introduce track data class

This commit is contained in:
Henrik Friedrichsen
2019-03-06 00:15:28 +01:00
parent 0f3cc41115
commit 91e89c2970
10 changed files with 98 additions and 75 deletions

View File

@@ -1,16 +1,15 @@
use crossbeam_channel::{unbounded, Receiver, Sender, TryIter}; use crossbeam_channel::{unbounded, Receiver, Sender, TryIter};
use cursive::{CbFunc, Cursive}; use cursive::{CbFunc, Cursive};
use rspotify::spotify::model::track::FullTrack;
use spotify::PlayerStatus;
use queue::QueueChange; use queue::QueueChange;
use spotify::PlayerStatus;
use track::Track;
use ui::playlist::PlaylistEvent; use ui::playlist::PlaylistEvent;
pub enum Event { pub enum Event {
Queue(QueueChange), Queue(QueueChange),
PlayerStatus(PlayerStatus), PlayerStatus(PlayerStatus),
PlayerTrack(Option<FullTrack>), PlayerTrack(Option<Track>),
Playlist(PlaylistEvent), Playlist(PlaylistEvent),
} }

View File

@@ -35,6 +35,7 @@ mod events;
mod queue; mod queue;
mod spotify; mod spotify;
mod theme; mod theme;
mod track;
mod ui; mod ui;
use events::{Event, EventManager}; use events::{Event, EventManager};

View File

@@ -1,12 +1,12 @@
use std::collections::vec_deque::Iter; use std::collections::vec_deque::Iter;
use std::collections::VecDeque; use std::collections::VecDeque;
use rspotify::spotify::model::track::FullTrack; use track::Track;
use events::{Event, EventManager}; use events::{Event, EventManager};
pub struct Queue { pub struct Queue {
queue: VecDeque<FullTrack>, queue: VecDeque<Track>,
ev: EventManager, ev: EventManager,
} }
@@ -24,35 +24,35 @@ impl Queue {
ev: ev, ev: ev,
} }
} }
pub fn remove(&mut self, index: usize) -> Option<FullTrack> { pub fn remove(&mut self, index: usize) -> Option<Track> {
match self.queue.remove(index) { match self.queue.remove(index) {
Some(track) => { Some(track) => {
debug!("Removed from queue: {}", &track.name); debug!("Removed from queue: {}", &track);
self.ev.send(Event::Queue(QueueChange::Remove(index))); self.ev.send(Event::Queue(QueueChange::Remove(index)));
Some(track) Some(track)
} }
None => None, None => None,
} }
} }
pub fn enqueue(&mut self, track: FullTrack) { pub fn enqueue(&mut self, track: Track) {
debug!("Queued: {}", &track.name); debug!("Queued: {}", &track);
self.queue.push_back(track); self.queue.push_back(track);
self.ev.send(Event::Queue(QueueChange::Enqueue)); self.ev.send(Event::Queue(QueueChange::Enqueue));
} }
pub fn dequeue(&mut self) -> Option<FullTrack> { pub fn dequeue(&mut self) -> Option<Track> {
match self.queue.pop_front() { match self.queue.pop_front() {
Some(track) => { Some(track) => {
debug!("Dequeued : {}", track.name); debug!("Dequeued : {}", track);
self.ev.send(Event::Queue(QueueChange::Dequeue)); self.ev.send(Event::Queue(QueueChange::Dequeue));
Some(track) Some(track)
} }
None => None, None => None,
} }
} }
pub fn peek(&self) -> Option<&FullTrack> { pub fn peek(&self) -> Option<&Track> {
self.queue.get(0) self.queue.get(0)
} }
pub fn iter(&self) -> Iter<FullTrack> { pub fn iter(&self) -> Iter<Track> {
self.queue.iter() self.queue.iter()
} }
} }

View File

@@ -14,7 +14,6 @@ use rspotify::spotify::client::Spotify as SpotifyAPI;
use rspotify::spotify::model::page::Page; use rspotify::spotify::model::page::Page;
use rspotify::spotify::model::playlist::{PlaylistTrack, SimplifiedPlaylist}; use rspotify::spotify::model::playlist::{PlaylistTrack, SimplifiedPlaylist};
use rspotify::spotify::model::search::SearchTracks; use rspotify::spotify::model::search::SearchTracks;
use rspotify::spotify::model::track::FullTrack;
use failure::Error; use failure::Error;
@@ -34,9 +33,10 @@ use std::time::{Duration, SystemTime};
use events::{Event, EventManager}; use events::{Event, EventManager};
use queue::Queue; use queue::Queue;
use track::Track;
enum WorkerCommand { enum WorkerCommand {
Load(FullTrack), Load(Track),
Play, Play,
Pause, Pause,
Stop, Stop,
@@ -51,7 +51,7 @@ pub enum PlayerStatus {
pub struct Spotify { pub struct Spotify {
status: RwLock<PlayerStatus>, status: RwLock<PlayerStatus>,
track: RwLock<Option<FullTrack>>, track: RwLock<Option<Track>>,
pub api: SpotifyAPI, pub api: SpotifyAPI,
elapsed: RwLock<Option<Duration>>, elapsed: RwLock<Option<Duration>>,
since: RwLock<Option<SystemTime>>, since: RwLock<Option<SystemTime>>,
@@ -99,9 +99,7 @@ impl futures::Future for Worker {
debug!("message received!"); debug!("message received!");
match cmd { match cmd {
WorkerCommand::Load(track) => { WorkerCommand::Load(track) => {
let trackid = self.play_task = Box::new(self.player.load(track.id, false, 0));
SpotifyId::from_base62(&track.id).expect("could not load track");
self.play_task = Box::new(self.player.load(trackid, false, 0));
info!("player loading track.."); info!("player loading track..");
self.events.send(Event::PlayerTrack(Some(track))); self.events.send(Event::PlayerTrack(Some(track)));
} }
@@ -127,10 +125,8 @@ impl futures::Future for Worker {
let mut queue = self.queue.lock().unwrap(); let mut queue = self.queue.lock().unwrap();
if let Some(track) = queue.dequeue() { if let Some(track) = queue.dequeue() {
debug!("next track in queue: {}", track.name); debug!("next track in queue: {}", track);
let trackid = self.play_task = Box::new(self.player.load(track.id, false, 0));
SpotifyId::from_base62(&track.id).expect("could not load track");
self.play_task = Box::new(self.player.load(trackid, false, 0));
self.player.play(); self.player.play();
self.events.send(Event::PlayerTrack(Some(track))); self.events.send(Event::PlayerTrack(Some(track)));
@@ -246,7 +242,7 @@ impl Spotify {
(*status).clone() (*status).clone()
} }
pub fn get_current_track(&self) -> Option<FullTrack> { pub fn get_current_track(&self) -> Option<Track> {
let track = self let track = self
.track .track
.read() .read()
@@ -311,7 +307,7 @@ impl Spotify {
.user_playlist_tracks(&self.user, playlist_id, None, 50, 0, None) .user_playlist_tracks(&self.user, playlist_id, None, 50, 0, None)
} }
pub fn load(&self, track: FullTrack) { pub fn load(&self, track: Track) {
info!("loading track: {:?}", track); info!("loading track: {:?}", track);
self.channel self.channel
.unbounded_send(WorkerCommand::Load(track)) .unbounded_send(WorkerCommand::Load(track))
@@ -340,7 +336,7 @@ impl Spotify {
*status = new_status; *status = new_status;
} }
pub fn update_track(&self, new_track: Option<FullTrack>) { pub fn update_track(&self, new_track: Option<Track>) {
self.set_elapsed(None); self.set_elapsed(None);
self.set_since(None); self.set_since(None);

54
src/track.rs Normal file
View File

@@ -0,0 +1,54 @@
use std::fmt;
use librespot::core::spotify_id::SpotifyId;
use rspotify::spotify::model::track::FullTrack;
#[derive(Clone)]
pub struct Track {
pub id: SpotifyId,
pub duration: u32,
pub artists: String,
pub title: String,
}
impl Track {
pub fn new(track: &FullTrack) -> Track {
let artists_joined = track
.artists
.iter()
.map(|ref artist| artist.name.clone())
.collect::<Vec<String>>()
.join(", ");
Track {
id: SpotifyId::from_base62(&track.id).expect("could not load track"),
duration: track.duration_ms / 1000,
artists: artists_joined,
title: track.name.clone(),
}
}
pub fn duration_str(&self) -> String {
let minutes = self.duration / 60;
let seconds = self.duration % 60;
format!("{:02}:{:02}", minutes, seconds)
}
}
impl fmt::Display for Track {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} - {}", self.artists, self.title)
}
}
impl fmt::Debug for Track {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"({} - {} ({})",
self.artists,
self.title,
self.id.to_base62()
)
}
}

View File

@@ -9,6 +9,7 @@ use rspotify::spotify::model::playlist::SimplifiedPlaylist;
use queue::Queue; use queue::Queue;
use spotify::Spotify; use spotify::Spotify;
use track::Track;
pub enum PlaylistEvent { pub enum PlaylistEvent {
Refresh, Refresh,
@@ -49,7 +50,7 @@ impl PlaylistView {
let tracks = spotify_ref.user_playlist_tracks(&id).unwrap().items; let tracks = spotify_ref.user_playlist_tracks(&id).unwrap().items;
let mut locked_queue = queue_ref.lock().expect("Could not aquire lock"); let mut locked_queue = queue_ref.lock().expect("Could not aquire lock");
for playlist_track in tracks { for playlist_track in tracks {
locked_queue.enqueue(playlist_track.track.clone()); locked_queue.enqueue(Track::new(&playlist_track.track));
} }
}); });

View File

@@ -8,10 +8,9 @@ use cursive::Cursive;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use rspotify::spotify::model::track::FullTrack;
use queue::{Queue, QueueChange}; use queue::{Queue, QueueChange};
use spotify::Spotify; use spotify::Spotify;
use track::Track;
use ui::trackbutton::TrackButton; use ui::trackbutton::TrackButton;
pub struct QueueView { pub struct QueueView {
@@ -72,7 +71,7 @@ impl QueueView {
} }
} }
fn create_button(&self, track: &FullTrack) -> TrackButton { fn create_button(&self, track: &Track) -> TrackButton {
let mut button = TrackButton::new(&track); let mut button = TrackButton::new(&track);
// 'd' deletes the selected track // 'd' deletes the selected track
{ {

View File

@@ -9,6 +9,7 @@ use std::sync::Mutex;
use queue::Queue; use queue::Queue;
use spotify::Spotify; use spotify::Spotify;
use track::Track;
use ui::trackbutton::TrackButton; use ui::trackbutton::TrackButton;
pub struct SearchView { pub struct SearchView {
@@ -29,7 +30,9 @@ impl SearchView {
results.clear(); results.clear();
if let Ok(tracks) = tracks { if let Ok(tracks) = tracks {
for track in tracks.tracks.items { for search_track in tracks.tracks.items {
let track = Track::new(&search_track);
let s = spotify.clone(); let s = spotify.clone();
let mut button = TrackButton::new(&track); let mut button = TrackButton::new(&track);

View File

@@ -51,21 +51,6 @@ impl View for StatusBar {
}); });
if let Some(ref t) = self.spotify.get_current_track() { if let Some(ref t) = self.spotify.get_current_track() {
let name = format!(
"{} - {}",
t.artists
.iter()
.map(|ref artist| artist.name.clone())
.collect::<Vec<String>>()
.join(", "),
t.name
)
.to_string();
let minutes = t.duration_ms / 60000;
let seconds = (t.duration_ms % 60000) / 1000;
let formatted_duration = format!("{:02}:{:02}", minutes, seconds);
let elapsed = self.spotify.get_current_progress(); let elapsed = self.spotify.get_current_progress();
let formatted_elapsed = format!( let formatted_elapsed = format!(
"{:02}:{:02}", "{:02}:{:02}",
@@ -73,18 +58,18 @@ impl View for StatusBar {
elapsed.as_secs() % 60 elapsed.as_secs() % 60
); );
let duration = format!("{} / {} ", formatted_elapsed, formatted_duration); let duration = format!("{} / {} ", formatted_elapsed, t.duration_str());
let offset = HAlign::Right.get_offset(duration.width(), printer.size.x); let offset = HAlign::Right.get_offset(duration.width(), printer.size.x);
printer.with_color(style, |printer| { printer.with_color(style, |printer| {
printer.print((4, 1), &name); printer.print((4, 1), &t.to_string());
printer.print((offset, 1), &duration); printer.print((offset, 1), &duration);
}); });
printer.with_color(style_bar, |printer| { printer.with_color(style_bar, |printer| {
printer.print((0, 0), &"".repeat(printer.size.x)); printer.print((0, 0), &"".repeat(printer.size.x));
let duration_width = (((printer.size.x as u32) * (elapsed.as_millis() as u32)) let duration_width =
/ t.duration_ms) as usize; (((printer.size.x as u32) * (elapsed.as_secs() as u32)) / t.duration) as usize;
printer.print((0, 0), &format!("{}{}", "=".repeat(duration_width), ">")); printer.print((0, 0), &format!("{}{}", "=".repeat(duration_width), ">"));
}); });
} else { } else {

View File

@@ -6,15 +6,14 @@ use cursive::traits::View;
use cursive::vec::Vec2; use cursive::vec::Vec2;
use cursive::Cursive; use cursive::Cursive;
use cursive::Printer; use cursive::Printer;
use rspotify::spotify::model::track::FullTrack;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use track::Track;
pub struct TrackButton { pub struct TrackButton {
callbacks: Vec<(EventTrigger, Callback)>, callbacks: Vec<(EventTrigger, Callback)>,
track: FullTrack, track: Track,
title: String,
duration: String,
enabled: bool, enabled: bool,
last_size: Vec2, last_size: Vec2,
@@ -22,24 +21,10 @@ pub struct TrackButton {
} }
impl TrackButton { impl TrackButton {
pub fn new(track: &FullTrack) -> TrackButton { pub fn new(track: &Track) -> TrackButton {
let artists = track
.artists
.iter()
.map(|ref artist| artist.name.clone())
.collect::<Vec<String>>()
.join(", ");
let formatted_title = format!("{} - {}", artists, track.name);
let minutes = track.duration_ms / 60000;
let seconds = (track.duration_ms % 60000) / 1000;
let formatted_duration = format!("{:02}:{:02}", minutes, seconds);
TrackButton { TrackButton {
callbacks: Vec::new(), callbacks: Vec::new(),
track: track.clone(), track: track.clone(),
title: formatted_title,
duration: formatted_duration,
enabled: true, enabled: true,
last_size: Vec2::zero(), last_size: Vec2::zero(),
invalidated: true, invalidated: true,
@@ -72,9 +57,9 @@ impl View for TrackButton {
}; };
// shorten titles that are too long and append ".." to indicate this // shorten titles that are too long and append ".." to indicate this
let mut title_shortened = self.title.clone(); let mut title_shortened = self.track.to_string();
title_shortened.truncate(printer.size.x - self.duration.width() - 1); title_shortened.truncate(printer.size.x - self.track.duration_str().width() - 1);
if title_shortened.width() < self.title.width() { if title_shortened.width() < self.track.to_string().width() {
let offset = title_shortened.width() - 2; let offset = title_shortened.width() - 2;
title_shortened.replace_range(offset.., ".."); title_shortened.replace_range(offset.., "..");
} }
@@ -84,10 +69,10 @@ impl View for TrackButton {
}); });
// track duration goes to the end of the line // track duration goes to the end of the line
let offset = HAlign::Right.get_offset(self.duration.width(), printer.size.x); let offset = HAlign::Right.get_offset(self.track.duration_str().width(), printer.size.x);
printer.with_color(style, |printer| { printer.with_color(style, |printer| {
printer.print((offset, 0), &self.duration); printer.print((offset, 0), &self.track.duration_str());
}); });
} }