diff --git a/src/episode.rs b/src/episode.rs index 44987d7..218fb44 100644 --- a/src/episode.rs +++ b/src/episode.rs @@ -2,7 +2,7 @@ use crate::library::Library; use crate::playable::Playable; use crate::queue::Queue; use crate::traits::{ListItem, ViewExt}; -use rspotify::model::show::SimplifiedEpisode; +use rspotify::model::show::{SimplifiedEpisode, FullEpisode}; use std::fmt; use std::sync::Arc; @@ -39,6 +39,20 @@ impl From<&SimplifiedEpisode> for Episode { } } +impl From<&FullEpisode> for Episode { + fn from(episode: &FullEpisode) -> Self { + Self { + id: episode.id.clone(), + uri: episode.uri.clone(), + duration: episode.duration_ms, + name: episode.name.clone(), + description: episode.description.clone(), + release_date: episode.release_date.clone(), + cover_url: episode.images.get(0).map(|img| img.url.clone()), + } + } +} + impl fmt::Display for Episode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.name) diff --git a/src/mpris.rs b/src/mpris.rs index 18f59b2..332a291 100644 --- a/src/mpris.rs +++ b/src/mpris.rs @@ -11,12 +11,15 @@ use dbus::{Path, SignalArgs}; use crate::album::Album; use crate::playable::Playable; use crate::playlist::Playlist; +use crate::show::Show; use crate::queue::{Queue, RepeatSetting}; use crate::spotify::{PlayerEvent, Spotify, URIType}; use crate::track::Track; use crate::traits::ListItem; +use crate::episode::Episode; type Metadata = HashMap>>; + struct MprisState(String, Option); fn get_playbackstatus(spotify: Arc) -> String { @@ -25,7 +28,7 @@ fn get_playbackstatus(spotify: Arc) -> String { PlayerEvent::Paused => "Paused", _ => "Stopped", } - .to_string() + .to_string() } fn get_metadata(playable: Option) -> Metadata { @@ -127,7 +130,7 @@ fn run_dbus_server(spotify: Arc, queue: Arc, rx: mpsc::Receiver< "org.mpris.MediaPlayer2.ncspot", dbus::NameFlag::ReplaceExisting as u32, ) - .expect("Failed to register dbus player name"); + .expect("Failed to register dbus player name"); let f = Factory::new_fn::<()>(); @@ -220,7 +223,7 @@ fn run_dbus_server(spotify: Arc, queue: Arc, rx: mpsc::Receiver< RepeatSetting::RepeatTrack => "Track", RepeatSetting::RepeatPlaylist => "Playlist", } - .to_string(), + .to_string(), ); Ok(()) }) @@ -470,6 +473,31 @@ fn run_dbus_server(spotify: Arc, queue: Arc, rx: mpsc::Receiver< } } } + Some(URIType::Show) => { + if let Some(s) = spotify.get_show(&id) { + let mut show = Show::from(&s); + let spotify = spotify.clone(); + show.load_episodes(spotify); + if let Some(e) = &show.episodes { + queue.clear(); + let mut ep = e.clone(); + ep.reverse(); + let index = queue.append_next( + ep.iter() + .map(|episode| Playable::Episode(episode.clone())) + .collect(), + ); + queue.play(index, false, false) + } + } + } + Some(URIType::Episode) => { + if let Some(e) = spotify.episode(&id) { + queue.clear(); + queue.append(Playable::Episode(Episode::from(&e))); + queue.play(0, false, false) + } + } Some(URIType::Artist) => {} None => {} } @@ -545,7 +573,7 @@ fn run_dbus_server(spotify: Arc, queue: Arc, rx: mpsc::Receiver< conn.send( changed.to_emit_message(&Path::new("/org/mpris/MediaPlayer2".to_string()).unwrap()), ) - .unwrap(); + .unwrap(); } } } diff --git a/src/show.rs b/src/show.rs index da13feb..3fa273b 100644 --- a/src/show.rs +++ b/src/show.rs @@ -5,7 +5,7 @@ use crate::queue::Queue; use crate::spotify::Spotify; use crate::traits::{IntoBoxedViewExt, ListItem, ViewExt}; use crate::ui::show::ShowView; -use rspotify::model::show::SimplifiedShow; +use rspotify::model::show::{SimplifiedShow, FullShow}; use std::fmt; use std::sync::Arc; @@ -63,6 +63,20 @@ impl From<&SimplifiedShow> for Show { } } +impl From<&FullShow> for Show { + fn from(show: &FullShow) -> Self { + Self { + id: show.id.clone(), + uri: show.uri.clone(), + name: show.name.clone(), + publisher: show.publisher.clone(), + description: show.description.clone(), + cover_url: show.images.get(0).map(|i| i.url.clone()), + episodes: None, + } + } +} + impl fmt::Display for Show { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} - {}", self.publisher, self.name) diff --git a/src/spotify.rs b/src/spotify.rs index bcba4bc..24357b9 100644 --- a/src/spotify.rs +++ b/src/spotify.rs @@ -58,7 +58,7 @@ use crate::events::{Event, EventManager}; use crate::playable::Playable; use crate::queue; use crate::track::Track; -use rspotify::model::show::{Show, SimplifiedEpisode}; +use rspotify::model::show::{Show, SimplifiedEpisode, FullShow, FullEpisode}; pub const VOLUME_PERCENT: u16 = ((u16::max_value() as f64) * 1.0 / 100.0) as u16; @@ -663,6 +663,14 @@ impl Spotify { self.api_with_retry(|api| api.track(track_id)) } + pub fn get_show(&self, show_id: &str) -> Option { + self.api_with_retry(|api| api.get_a_show(show_id.to_string(), None)) + } + + pub fn episode(&self, episode_id: &str) -> Option { + self.api_with_retry(|api| api.get_an_episode(episode_id.to_string(), None)) + } + pub fn search( &self, searchtype: SearchType, @@ -904,6 +912,8 @@ pub enum URIType { Artist, Track, Playlist, + Show, + Episode } impl URIType { @@ -916,6 +926,10 @@ impl URIType { Some(URIType::Track) } else if s.starts_with("spotify:") && s.contains(":playlist:") { Some(URIType::Playlist) + } else if s.starts_with("spotify:show:"){ + Some(URIType::Show) + } else if s.starts_with("spotify:episode:"){ + Some(URIType::Episode) } else { None } diff --git a/src/ui/search.rs b/src/ui/search.rs index d775396..ec4eb9d 100644 --- a/src/ui/search.rs +++ b/src/ui/search.rs @@ -25,6 +25,7 @@ use crate::ui::listview::{ListView, Pagination}; use crate::ui::tabview::TabView; use rspotify::model::search::SearchResult; use rspotify::senum::SearchType; +use crate::episode::Episode; pub struct SearchView { results_tracks: Arc>>, @@ -37,6 +38,8 @@ pub struct SearchView { pagination_playlists: Pagination, results_shows: Arc>>, pagination_shows: Pagination, + results_episodes: Arc>>, + pagination_episodes: Pagination, edit: NamedView, tabs: NamedView, edit_focused: bool, @@ -61,6 +64,7 @@ impl SearchView { let results_artists = Arc::new(RwLock::new(Vec::new())); let results_playlists = Arc::new(RwLock::new(Vec::new())); let results_shows = Arc::new(RwLock::new(Vec::new())); + let results_episodes = Arc::new(RwLock::new(Vec::new())); let searchfield = EditView::new() .on_submit(move |s, input| { @@ -82,15 +86,18 @@ impl SearchView { let list_playlists = ListView::new(results_playlists.clone(), queue.clone(), library.clone()); let pagination_playlists = list_playlists.get_pagination().clone(); - let list_shows = ListView::new(results_shows.clone(), queue, library); + let list_shows = ListView::new(results_shows.clone(), queue.clone(), library.clone()); let pagination_shows = list_shows.get_pagination().clone(); + let list_episodes = ListView::new(results_episodes.clone(), queue, library); + let pagination_episodes = list_episodes.get_pagination().clone(); let tabs = TabView::new() .tab("tracks", "Tracks", list_tracks) .tab("albums", "Albums", list_albums) .tab("artists", "Artists", list_artists) .tab("playlists", "Playlists", list_playlists) - .tab("shows", "Podcasts", list_shows); + .tab("shows", "Podcasts", list_shows) + .tab("episodes", "Podcast Episodes", list_episodes); SearchView { results_tracks, @@ -103,6 +110,8 @@ impl SearchView { pagination_playlists, results_shows, pagination_shows, + results_episodes, + pagination_episodes, edit: searchfield, tabs: tabs.with_name(LIST_ID), edit_focused: true, @@ -274,6 +283,22 @@ impl SearchView { 0 } + fn get_show( + spotify: &Arc, + shows: &Arc>>, + query: &str, + _offset: usize, + _append: bool, + ) -> u32 { + if let Some(result) = spotify.get_show(&query).as_ref() { + let pls = vec![result.into()]; + let mut r = shows.write().unwrap(); + *r = pls; + return 1; + } + 0 + } + fn search_show( spotify: &Arc, shows: &Arc>>, @@ -297,6 +322,45 @@ impl SearchView { 0 } + fn get_episode( + spotify: &Arc, + episodes: &Arc>>, + query: &str, + _offset: usize, + _append: bool, + ) -> u32 { + if let Some(result) = spotify.episode(&query).as_ref() { + let e = vec![result.into()]; + let mut r = episodes.write().unwrap(); + *r = e; + return 1 + } + 0 + } + + fn search_episode( + spotify: &Arc, + episodes: &Arc>>, + query: &str, + offset: usize, + append: bool, + ) -> u32 { + if let Some(SearchResult::Episodes(results)) = + spotify.search(SearchType::Episode, &query, 50, offset as u32) + { + let mut e = results.items.iter().map(|se| se.into()).collect(); + let mut r = episodes.write().unwrap(); + + if append { + r.append(&mut e); + } else { + *r = e; + } + return results.total; + } + 0 + } + fn perform_search( &self, handler: SearchHandler, @@ -366,6 +430,8 @@ impl SearchView { *results_playlists.write().unwrap() = Vec::new(); let results_shows = self.results_shows.clone(); *results_shows.write().unwrap() = Vec::new(); + let results_episodes = self.results_episodes.clone(); + *results_episodes.write().unwrap() = Vec::new(); let mut tab_view = self.tabs.get_mut(); match uritype { @@ -405,6 +471,24 @@ impl SearchView { ); tab_view.move_focus_to(3); } + URIType::Show => { + self.perform_search( + Box::new(Self::get_show), + &self.results_shows, + &query, + None, + ); + tab_view.move_focus_to(4); + } + URIType::Episode => { + self.perform_search( + Box::new(Self::get_episode), + &self.results_episodes, + &query, + None, + ); + tab_view.move_focus_to(5); + } } } else { self.perform_search( @@ -437,6 +521,12 @@ impl SearchView { &query, Some(&self.pagination_shows), ); + self.perform_search( + Box::new(Self::search_episode), + &self.results_episodes, + &query, + Some(&self.pagination_episodes), + ); } } }