Podcast support improvements. (#229)

* Added OpenUri D-BUS MPRIS support.
Removed "user:" from URIType check because Spotify doesn't always provide it.

* Added tags to .gitignore

* Changed mpris metadata to actually return the track's url instead of the Spotify URI so that it matches the functionality of the official Spotify client.

* Changed mpris:trackid and xesam:url to not use static naming so it can support podcasts.

* Changed xesam:url to default to an empty string instead of "0"

* Added possibility to start playing Shows and Episodes via MPRIS.
Added possibility to search for Podcast Episodes.
This commit is contained in:
Bettehem
2020-07-26 12:29:43 +03:00
committed by GitHub
parent 9adab923d5
commit 413703a310
5 changed files with 169 additions and 9 deletions

View File

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

View File

@@ -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<String, Variant<Box<dyn RefArg>>>;
struct MprisState(String, Option<Playable>);
fn get_playbackstatus(spotify: Arc<Spotify>) -> String {
@@ -25,7 +28,7 @@ fn get_playbackstatus(spotify: Arc<Spotify>) -> String {
PlayerEvent::Paused => "Paused",
_ => "Stopped",
}
.to_string()
.to_string()
}
fn get_metadata(playable: Option<Playable>) -> Metadata {
@@ -127,7 +130,7 @@ fn run_dbus_server(spotify: Arc<Spotify>, queue: Arc<Queue>, 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<Spotify>, queue: Arc<Queue>, rx: mpsc::Receiver<
RepeatSetting::RepeatTrack => "Track",
RepeatSetting::RepeatPlaylist => "Playlist",
}
.to_string(),
.to_string(),
);
Ok(())
})
@@ -470,6 +473,31 @@ fn run_dbus_server(spotify: Arc<Spotify>, queue: Arc<Queue>, 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<Spotify>, queue: Arc<Queue>, rx: mpsc::Receiver<
conn.send(
changed.to_emit_message(&Path::new("/org/mpris/MediaPlayer2".to_string()).unwrap()),
)
.unwrap();
.unwrap();
}
}
}

View File

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

View File

@@ -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<FullShow> {
self.api_with_retry(|api| api.get_a_show(show_id.to_string(), None))
}
pub fn episode(&self, episode_id: &str) -> Option<FullEpisode> {
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
}

View File

@@ -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<RwLock<Vec<Track>>>,
@@ -37,6 +38,8 @@ pub struct SearchView {
pagination_playlists: Pagination<Playlist>,
results_shows: Arc<RwLock<Vec<Show>>>,
pagination_shows: Pagination<Show>,
results_episodes: Arc<RwLock<Vec<Episode>>>,
pagination_episodes: Pagination<Episode>,
edit: NamedView<EditView>,
tabs: NamedView<TabView>,
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<Spotify>,
shows: &Arc<RwLock<Vec<Show>>>,
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<Spotify>,
shows: &Arc<RwLock<Vec<Show>>>,
@@ -297,6 +322,45 @@ impl SearchView {
0
}
fn get_episode(
spotify: &Arc<Spotify>,
episodes: &Arc<RwLock<Vec<Episode>>>,
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<Spotify>,
episodes: &Arc<RwLock<Vec<Episode>>>,
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<I: ListItem>(
&self,
handler: SearchHandler<I>,
@@ -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),
);
}
}
}