Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add play recommendations for song/artist on pressing 'r'
  • Loading branch information
Eric Faber authored and Eric Faber committed Jan 4, 2020
commit 0f9dbac7a482070287f119ffba9a6bec175291b4
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ rspotify = "0.7.0"
tui = { version = "0.8.0", features = ["crossterm"], default-features = false }
failure = "0.1.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.8"
dirs = "2.0.2"
clap = "2.33.0"
Expand Down
122 changes: 122 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ use rspotify::spotify::{
page::{CursorBasedPage, Page},
playing::PlayHistory,
playlist::{PlaylistTrack, SimplifiedPlaylist},
recommend::{Recommendations},
search::{SearchAlbums, SearchArtists, SearchPlaylists, SearchTracks},
track::{FullTrack, SavedTrack, SimplifiedTrack},
user::PrivateUser,
},
senum::{Country, RepeatState},
};
use serde_json::{
Value,
map::Map,
};
use std::{
cmp::{max, min},
collections::HashSet,
Expand Down Expand Up @@ -143,6 +148,7 @@ pub enum RouteId {
MadeForYou,
Artists,
Podcasts,
Recommendations,
}

pub struct Route {
Expand All @@ -158,6 +164,7 @@ pub enum TrackTableContext {
AlbumSearch,
PlaylistSearch,
SavedTracks,
RecommendedTracks,
}

#[derive(Clone, PartialEq, Debug)]
Expand All @@ -166,6 +173,12 @@ pub enum AlbumTableContext {
Full,
}

#[derive(Clone, PartialEq, Debug)]
pub enum RecommendationsContext {
Artist,
Song,
}

pub struct SearchResult {
pub albums: Option<SearchAlbums>,
pub artists: Option<SearchArtists>,
Expand Down Expand Up @@ -242,6 +255,9 @@ pub struct App {
pub playlist_tracks: Vec<PlaylistTrack>,
pub playlists: Option<Page<SimplifiedPlaylist>>,
pub recently_played: SpotifyResultAndSelectedIndex<Option<CursorBasedPage<PlayHistory>>>,
pub recommended_tracks: Vec<FullTrack>,
pub recommendations_seed: String,
pub recommendations_context: Option<RecommendationsContext>,
pub search_results: SearchResult,
pub selected_album: Option<SelectedAlbum>,
pub selected_album_full: Option<SelectedFullAlbum>,
Expand Down Expand Up @@ -293,6 +309,9 @@ impl App {
playlist_offset: 0,
playlist_tracks: vec![],
playlists: None,
recommended_tracks: vec![],
recommendations_context: None,
recommendations_seed: "".to_string(),
search_results: SearchResult {
hovered_block: SearchResultBlock::SongSearch,
selected_block: SearchResultBlock::Empty,
Expand Down Expand Up @@ -479,6 +498,69 @@ impl App {
}
}

pub fn get_recommendations_for_seed(&mut self,
seed_artists: Option<Vec<String>>,
seed_tracks: Option<Vec<String>>,
first_track: Option<&FullTrack>) {
if let (Some(spotify), Some(user)) = (
&self.spotify,
&self.user.to_owned()
) {
let user_country = Country::from_str(&user.country.to_owned().unwrap_or_else(|| "".to_string()));
let empty_payload: Map<String, Value> = Map::new();

match spotify.recommendations(
seed_artists, // artists
None, // genres
seed_tracks, // tracks
30, // limit : 20-100 according to reference, but much higher than 30
// seems to give errors w/ playback
// I recommend leaving this at 30 for now
user_country, // country
&empty_payload // payload
) {
Ok(result) => {

if let Some(mut recommended_tracks) = self.extract_recommended_tracks(&result){

//custom first track
match first_track {
Some(track) => {
recommended_tracks.insert(0, track.clone());
}
None => {}
}
self.recommended_tracks = recommended_tracks.clone();
self.set_tracks_to_table(recommended_tracks);
self.track_table.context = Some(TrackTableContext::RecommendedTracks);

if self.get_current_route().id != RouteId::Recommendations {
self.push_navigation_stack(RouteId::Recommendations, ActiveBlock::TrackTable);
};
}
self.start_recommendations_playback(Some(0));
}
Err(e) => {
println!("error: {:?}", e)
}
}
}
}

pub fn get_recommendations_for_trackid(&mut self, id : &str){
if let Some(track) = self.get_fulltrack_from_id(id).clone() {
let track_id_list: Option<Vec<String>> = match &track.id {
Some(id) => {
Some(vec![id.to_string()])
}
None => {
None
}
};
self.get_recommendations_for_seed(None, track_id_list, Some(&track));
}
}

fn change_volume(&mut self, volume_percent: u8) {
if let (Some(spotify), Some(device_id), Some(context)) = (
&self.spotify,
Expand Down Expand Up @@ -560,6 +642,17 @@ impl App {
}
}

pub fn start_recommendations_playback(&mut self, offset: Option<usize>){
&self.start_playback(
None,
Some(self.recommended_tracks
.iter()
.map(|x| x.uri.clone())
.collect::<Vec<String>>()),
//Some(self.song_progress_ms as usize) [this does not work]
offset);
}

pub fn start_playback(
&mut self,
context_uri: Option<String>,
Expand Down Expand Up @@ -716,6 +809,35 @@ impl App {
);
}

fn extract_recommended_tracks(&self, recommendations: &Recommendations) -> Option<Vec<FullTrack>>{
if let Some(spotify) = &self.spotify {
let tracks = recommendations.clone().tracks.into_iter().map(|item| item.uri).collect::<Vec<String>>();
if let Ok(result) = spotify.tracks(
tracks.iter().map(|x| &x[..]).collect::<Vec<&str>>(),
None
){
return Some(result.tracks);
}
}

None
}

fn get_fulltrack_from_id(&self, id: &str) -> Option<FullTrack>{
if let Some(spotify) = &self.spotify {
match spotify.track(id){
Ok(track) => {
return Some(track);
},
Err(_e) => {
return None;
}
};
}

None
}

pub fn set_tracks_to_table(&mut self, tracks: Vec<FullTrack>) {
self.track_table.tracks = tracks.clone();

Expand Down
33 changes: 32 additions & 1 deletion src/handlers/album_tracks.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::common_key_events;
use crate::{
app::{AlbumTableContext, App},
app::{AlbumTableContext, App, RecommendationsContext},
event::Key,
};

Expand Down Expand Up @@ -106,6 +106,37 @@ pub fn handler(key: Key, app: &mut App) {
};
}
},
//recommended playlist based on selected track
Key::Char('r') => match app.album_table_context {
AlbumTableContext::Full => {
if let Some(albums) = &app.library.clone().saved_albums.get_results(None) {
if let Some(selected_album) = albums.items.get(app.album_list_index) {
if let Some(track) = &selected_album
.album
.tracks
.items.get(app.saved_album_tracks_index) {
if let Some(id) = &track.id {
app.recommendations_context = Some(RecommendationsContext::Song);
app.recommendations_seed = track.name.clone();
app.get_recommendations_for_trackid(&id);
}
}
}
}
}
AlbumTableContext::Simplified => {
if let Some(selected_album) = &app.selected_album.clone() {
if let Some(track) = &selected_album.tracks
.items.get(selected_album.selected_index){
if let Some(id) = &track.id {
app.recommendations_context = Some(RecommendationsContext::Song);
app.recommendations_seed = track.name.clone();
app.get_recommendations_for_trackid(&id);
}
}
};
}
},
_ => {}
};
}
Expand Down
42 changes: 41 additions & 1 deletion src/handlers/artist.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::common_key_events;
use crate::app::{App, ArtistBlock, TrackTableContext};
use crate::app::{App, ArtistBlock, TrackTableContext, RecommendationsContext};
use crate::event::Key;

fn handle_down_press_on_selected_block(app: &mut App) {
Expand Down Expand Up @@ -94,6 +94,41 @@ fn handle_up_press_on_hovered_block(app: &mut App) {
}
}

fn handle_recommend_event_on_selected_block(app: &mut App){
//recommendations.
if let Some(artist) = &mut app.artist.clone() {
match artist.artist_selected_block {
ArtistBlock::TopTracks => {
let selected_index = artist.selected_top_track_index;
if let Some(track) = artist.top_tracks.get(selected_index){
let track_id_list: Option<Vec<String>> = match &track.id {
Some(id) => {
Some(vec![id.to_string()])
}
None => {
None
}
};
app.recommendations_context = Some(RecommendationsContext::Song);
app.recommendations_seed = track.name.clone();
app.get_recommendations_for_seed(None, track_id_list, Some(track));
}
}
ArtistBlock::RelatedArtists => {
let selected_index = artist.selected_related_artist_index;
let artist_id = &artist.related_artists[selected_index].id;
let artist_name = &artist.related_artists[selected_index].name;
let artist_id_list: Option<Vec<String>> = Some(vec![artist_id.clone()]);

app.recommendations_context = Some(RecommendationsContext::Artist);
app.recommendations_seed = artist_name.clone();
app.get_recommendations_for_seed(artist_id_list, None, None);
}
_ => {}
}
}
}

fn handle_enter_event_on_selected_block(app: &mut App) {
if let Some(artist) = &mut app.artist.clone() {
match artist.artist_selected_block {
Expand Down Expand Up @@ -185,6 +220,11 @@ pub fn handler(key: Key, app: &mut App) {
handle_enter_event_on_hovered_block(app);
}
}
Key::Char('r') => {
if artist.artist_selected_block != ArtistBlock::Empty {
handle_recommend_event_on_selected_block(app);
}
}
_ => {}
};
}
Expand Down
14 changes: 13 additions & 1 deletion src/handlers/artists.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::common_key_events;
use crate::{
app::{ActiveBlock, App, RouteId},
app::{ActiveBlock, App, RouteId, RecommendationsContext},
event::Key,
};

Expand Down Expand Up @@ -32,6 +32,18 @@ pub fn handler(key: Key, app: &mut App) {
app.push_navigation_stack(RouteId::Artist, ActiveBlock::ArtistBlock);
}
Key::Char('D') => app.user_unfollow_artists(),
Key::Char('r') => {
let artists = app.artists.to_owned();
let artist = artists.get(app.artists_list_index);
if let Some(artist) = artist {
let artist_name = artist.name.clone();
let artist_id_list: Option<Vec<String>> = Some(vec![artist.id.clone()]);

app.recommendations_context = Some(RecommendationsContext::Artist);
app.recommendations_seed = artist_name.clone();
app.get_recommendations_for_seed(artist_id_list, None, None);
}
}
_ => {}
}
}
6 changes: 6 additions & 0 deletions src/handlers/common_key_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ pub fn handle_right_event(app: &mut App) {
Some(ActiveBlock::Podcasts),
);
}
RouteId::Recommendations => {
app.set_current_route_state(
Some(ActiveBlock::TrackTable),
Some(ActiveBlock::TrackTable),
);
}
RouteId::AlbumList => {
app.set_current_route_state(
Some(ActiveBlock::AlbumList),
Expand Down
20 changes: 19 additions & 1 deletion src/handlers/recently_played.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::{super::app::App, common_key_events};
use crate::event::Key;
use crate::{
app::{RecommendationsContext},
event::Key,
};

pub fn handler(key: Key, app: &mut App) {
match key {
Expand Down Expand Up @@ -44,6 +47,21 @@ pub fn handler(key: Key, app: &mut App) {
app.start_playback(None, Some(track_uris), Some(app.recently_played.index));
};
}
Key::Char('r') => {
if let Some(recently_played_result) = &app.recently_played.result.clone() {
let selected_track_history_item = recently_played_result
.items
.get(app.recently_played.index);

if let Some(item) = selected_track_history_item {
if let Some(id) = &item.track.id {
app.recommendations_context = Some(RecommendationsContext::Song);
app.recommendations_seed = item.track.name.clone();
app.get_recommendations_for_trackid(&id);
}
}
}
}
_ => {}
};
}
Expand Down
Loading