podcast support (#203)
* implement search for shows/podcasts * create Playable supertype for queue to contain tracks and episodes * wip: implement playback of episodes * load spotify id from uri instead of raw id to fix podcast playback * show duration for podcast episodes * implement generic status bar for playables (tracks and episodes) omit saved indicator for now as the library does not yet support podcasts * instead of only the last 50 fetch all episodes of a show * refactor: extract Playable code to separate file * implement playback/queuing of shows + sharing url * implement podcast library * migrate mpris code to Playable supertype
This commit is contained in:
committed by
GitHub
parent
8bf06147e2
commit
1b1d392ab8
@@ -37,7 +37,12 @@ impl LibraryView {
|
||||
.tab(
|
||||
"playlists",
|
||||
"Playlists",
|
||||
PlaylistsView::new(queue, library.clone()),
|
||||
PlaylistsView::new(queue.clone(), library.clone()),
|
||||
)
|
||||
.tab(
|
||||
"podcasts",
|
||||
"Podcasts",
|
||||
ListView::new(library.shows.clone(), queue, library.clone()),
|
||||
);
|
||||
|
||||
Self { tabs }
|
||||
|
||||
@@ -12,6 +12,7 @@ use unicode_width::UnicodeWidthStr;
|
||||
use crate::command::{Command, GotoMode, MoveAmount, MoveMode, TargetMode};
|
||||
use crate::commands::CommandResult;
|
||||
use crate::library::Library;
|
||||
use crate::playable::Playable;
|
||||
use crate::queue::Queue;
|
||||
use crate::track::Track;
|
||||
use crate::traits::{IntoBoxedViewExt, ListItem, ViewExt};
|
||||
@@ -146,7 +147,10 @@ impl<I: ListItem> ListView<I> {
|
||||
let content = self.content.read().unwrap();
|
||||
let any = &(*content) as &dyn std::any::Any;
|
||||
if let Some(tracks) = any.downcast_ref::<Vec<Track>>() {
|
||||
let tracks: Vec<&Track> = tracks.iter().collect();
|
||||
let tracks: Vec<Playable> = tracks
|
||||
.iter()
|
||||
.map(|track| Playable::Track(track.clone()))
|
||||
.collect();
|
||||
let index = self.queue.append_next(tracks);
|
||||
self.queue.play(index + self.selected, true, false);
|
||||
true
|
||||
@@ -351,7 +355,10 @@ impl<I: ListItem + Clone> ViewExt for ListView<I> {
|
||||
TargetMode::Selected => self.content.read().ok().and_then(|content| {
|
||||
content.get(self.selected).and_then(ListItem::share_url)
|
||||
}),
|
||||
TargetMode::Current => self.queue.get_current().and_then(|t| t.share_url()),
|
||||
TargetMode::Current => self
|
||||
.queue
|
||||
.get_current()
|
||||
.and_then(|t| t.as_listitem().share_url()),
|
||||
};
|
||||
|
||||
if let Some(url) = url {
|
||||
|
||||
@@ -10,5 +10,6 @@ pub mod playlist;
|
||||
pub mod playlists;
|
||||
pub mod queue;
|
||||
pub mod search;
|
||||
pub mod show;
|
||||
pub mod statusbar;
|
||||
pub mod tabview;
|
||||
|
||||
@@ -9,14 +9,14 @@ use std::sync::Arc;
|
||||
use crate::command::{Command, MoveMode, ShiftMode};
|
||||
use crate::commands::CommandResult;
|
||||
use crate::library::Library;
|
||||
use crate::playable::Playable;
|
||||
use crate::queue::Queue;
|
||||
use crate::track::Track;
|
||||
use crate::traits::ViewExt;
|
||||
use crate::ui::listview::ListView;
|
||||
use crate::ui::modal::Modal;
|
||||
|
||||
pub struct QueueView {
|
||||
list: ListView<Track>,
|
||||
list: ListView<Playable>,
|
||||
library: Arc<Library>,
|
||||
queue: Arc<Queue>,
|
||||
}
|
||||
@@ -85,7 +85,7 @@ impl QueueView {
|
||||
}
|
||||
|
||||
impl ViewWrapper for QueueView {
|
||||
wrap_impl!(self.list: ListView<Track>);
|
||||
wrap_impl!(self.list: ListView<Playable>);
|
||||
}
|
||||
|
||||
impl ViewExt for QueueView {
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::events::EventManager;
|
||||
use crate::library::Library;
|
||||
use crate::playlist::Playlist;
|
||||
use crate::queue::Queue;
|
||||
use crate::show::Show;
|
||||
use crate::spotify::{Spotify, URIType};
|
||||
use crate::track::Track;
|
||||
use crate::traits::{ListItem, ViewExt};
|
||||
@@ -34,6 +35,8 @@ pub struct SearchView {
|
||||
pagination_artists: Pagination<Artist>,
|
||||
results_playlists: Arc<RwLock<Vec<Playlist>>>,
|
||||
pagination_playlists: Pagination<Playlist>,
|
||||
results_shows: Arc<RwLock<Vec<Show>>>,
|
||||
pagination_shows: Pagination<Show>,
|
||||
edit: NamedView<EditView>,
|
||||
tabs: NamedView<TabView>,
|
||||
edit_focused: bool,
|
||||
@@ -57,6 +60,7 @@ impl SearchView {
|
||||
let results_albums = Arc::new(RwLock::new(Vec::new()));
|
||||
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 searchfield = EditView::new()
|
||||
.on_submit(move |s, input| {
|
||||
@@ -75,14 +79,18 @@ impl SearchView {
|
||||
let pagination_albums = list_albums.get_pagination().clone();
|
||||
let list_artists = ListView::new(results_artists.clone(), queue.clone(), library.clone());
|
||||
let pagination_artists = list_artists.get_pagination().clone();
|
||||
let list_playlists = ListView::new(results_playlists.clone(), queue, library);
|
||||
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 pagination_shows = list_shows.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("playlists", "Playlists", list_playlists)
|
||||
.tab("shows", "Podcasts", list_shows);
|
||||
|
||||
SearchView {
|
||||
results_tracks,
|
||||
@@ -93,6 +101,8 @@ impl SearchView {
|
||||
pagination_artists,
|
||||
results_playlists,
|
||||
pagination_playlists,
|
||||
results_shows,
|
||||
pagination_shows,
|
||||
edit: searchfield,
|
||||
tabs: tabs.with_name(LIST_ID),
|
||||
edit_focused: true,
|
||||
@@ -264,6 +274,29 @@ impl SearchView {
|
||||
0
|
||||
}
|
||||
|
||||
fn search_show(
|
||||
spotify: &Arc<Spotify>,
|
||||
shows: &Arc<RwLock<Vec<Show>>>,
|
||||
query: &str,
|
||||
offset: usize,
|
||||
append: bool,
|
||||
) -> u32 {
|
||||
if let Some(SearchResult::Shows(results)) =
|
||||
spotify.search(SearchType::Show, &query, 50, offset as u32)
|
||||
{
|
||||
let mut pls = results.items.iter().map(|sp| sp.into()).collect();
|
||||
let mut r = shows.write().unwrap();
|
||||
|
||||
if append {
|
||||
r.append(&mut pls);
|
||||
} else {
|
||||
*r = pls;
|
||||
}
|
||||
return results.total;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
fn perform_search<I: ListItem>(
|
||||
&self,
|
||||
handler: SearchHandler<I>,
|
||||
@@ -331,6 +364,8 @@ impl SearchView {
|
||||
*results_artists.write().unwrap() = Vec::new();
|
||||
let results_playlists = self.results_playlists.clone();
|
||||
*results_playlists.write().unwrap() = Vec::new();
|
||||
let results_shows = self.results_shows.clone();
|
||||
*results_shows.write().unwrap() = Vec::new();
|
||||
|
||||
let mut tab_view = self.tabs.get_mut();
|
||||
match uritype {
|
||||
@@ -396,6 +431,12 @@ impl SearchView {
|
||||
&query,
|
||||
Some(&self.pagination_playlists),
|
||||
);
|
||||
self.perform_search(
|
||||
Box::new(Self::search_show),
|
||||
&self.results_shows,
|
||||
&query,
|
||||
Some(&self.pagination_shows),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
46
src/ui/show.rs
Normal file
46
src/ui/show.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use cursive::view::ViewWrapper;
|
||||
use cursive::Cursive;
|
||||
|
||||
use crate::command::Command;
|
||||
use crate::commands::CommandResult;
|
||||
use crate::episode::Episode;
|
||||
use crate::library::Library;
|
||||
use crate::queue::Queue;
|
||||
use crate::show::Show;
|
||||
use crate::traits::ViewExt;
|
||||
use crate::ui::listview::ListView;
|
||||
|
||||
pub struct ShowView {
|
||||
list: ListView<Episode>,
|
||||
show: Show,
|
||||
}
|
||||
|
||||
impl ShowView {
|
||||
pub fn new(queue: Arc<Queue>, library: Arc<Library>, show: &Show) -> Self {
|
||||
let mut show = show.clone();
|
||||
show.load_episodes(queue.get_spotify());
|
||||
|
||||
let episodes = show.episodes.clone().unwrap_or_default();
|
||||
|
||||
Self {
|
||||
list: ListView::new(Arc::new(RwLock::new(episodes)), queue, library),
|
||||
show,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewWrapper for ShowView {
|
||||
wrap_impl!(self.list: ListView<Episode>);
|
||||
}
|
||||
|
||||
impl ViewExt for ShowView {
|
||||
fn title(&self) -> String {
|
||||
self.show.name.clone()
|
||||
}
|
||||
|
||||
fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result<CommandResult, String> {
|
||||
self.list.on_command(s, cmd)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use cursive::Printer;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::library::Library;
|
||||
use crate::playable::Playable;
|
||||
use crate::queue::{Queue, RepeatSetting};
|
||||
use crate::spotify::{PlayerEvent, Spotify};
|
||||
|
||||
@@ -132,51 +133,54 @@ impl View for StatusBar {
|
||||
printer.print((0, 0), &"┉".repeat(printer.size.x));
|
||||
});
|
||||
|
||||
if let Some(ref t) = self.queue.get_current() {
|
||||
let elapsed = self.spotify.get_current_progress();
|
||||
let elapsed_ms = elapsed.as_millis() as u32;
|
||||
let elapsed = self.spotify.get_current_progress();
|
||||
let elapsed_ms = elapsed.as_millis() as u32;
|
||||
|
||||
let formatted_elapsed = format!(
|
||||
"{:02}:{:02}",
|
||||
elapsed.as_secs() / 60,
|
||||
elapsed.as_secs() % 60
|
||||
);
|
||||
let formatted_elapsed = format!(
|
||||
"{:02}:{:02}",
|
||||
elapsed.as_secs() / 60,
|
||||
elapsed.as_secs() % 60
|
||||
);
|
||||
|
||||
let saved = if self.library.is_saved_track(t) {
|
||||
if self.use_nerdfont {
|
||||
"\u{f62b} "
|
||||
} else {
|
||||
"✓ "
|
||||
}
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let playback_duration_status = match self.queue.get_current() {
|
||||
Some(ref t) => format!("{} / {}", formatted_elapsed, t.duration_str()),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let right = updating.to_string()
|
||||
+ repeat
|
||||
+ shuffle
|
||||
+ saved
|
||||
+ &format!("{} / {}", formatted_elapsed, t.duration_str())
|
||||
+ &volume;
|
||||
let offset = HAlign::Right.get_offset(right.width(), printer.size.x);
|
||||
let right = updating.to_string()
|
||||
+ repeat
|
||||
+ shuffle
|
||||
// + saved
|
||||
+ &playback_duration_status
|
||||
+ &volume;
|
||||
let offset = HAlign::Right.get_offset(right.width(), printer.size.x);
|
||||
|
||||
printer.with_color(style, |printer| {
|
||||
printer.with_color(style, |printer| {
|
||||
if let Some(ref t) = self.queue.get_current() {
|
||||
printer.print((4, 1), &t.to_string());
|
||||
printer.print((offset, 1), &right);
|
||||
});
|
||||
}
|
||||
printer.print((offset, 1), &right);
|
||||
});
|
||||
|
||||
if let Some(t) = self.queue.get_current() {
|
||||
printer.with_color(style_bar, |printer| {
|
||||
let duration_width = (((printer.size.x as u32) * elapsed_ms) / t.duration) as usize;
|
||||
let duration_width =
|
||||
(((printer.size.x as u32) * elapsed_ms) / t.duration()) as usize;
|
||||
printer.print((0, 0), &"━".repeat(duration_width + 1));
|
||||
});
|
||||
} else {
|
||||
let right = updating.to_string() + repeat + shuffle + &volume;
|
||||
let offset = HAlign::Right.get_offset(right.width(), printer.size.x);
|
||||
|
||||
printer.with_color(style, |printer| {
|
||||
printer.print((offset, 1), &right);
|
||||
});
|
||||
}
|
||||
|
||||
// if let Some(Playable::Track(ref t)) = self.queue.get_current() {
|
||||
// let saved = if self.library.is_saved_track(&Playable::Track(t.clone())) {
|
||||
// if self.use_nerdfont {
|
||||
// "\u{f62b} "
|
||||
// } else {
|
||||
// "✓ "
|
||||
// }
|
||||
// } else {
|
||||
// ""
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
||||
fn layout(&mut self, size: Vec2) {
|
||||
@@ -208,7 +212,7 @@ impl View for StatusBar {
|
||||
if event == MouseEvent::Press(MouseButton::Left)
|
||||
|| event == MouseEvent::Hold(MouseButton::Left)
|
||||
{
|
||||
if let Some(ref t) = self.queue.get_current() {
|
||||
if let Some(Playable::Track(ref t)) = self.queue.get_current() {
|
||||
let f: f32 = position.x as f32 / self.last_size.x as f32;
|
||||
let new = t.duration as f32 * f;
|
||||
self.spotify.seek(new as u32);
|
||||
|
||||
Reference in New Issue
Block a user