Include radio stations in Quick Search
This commit is contained in:
@@ -270,6 +270,7 @@ type LyricsProvider interface {
|
||||
}
|
||||
|
||||
type RadioProvider interface {
|
||||
GetRadioStation(id string) (*RadioStation, error)
|
||||
GetRadioStations() ([]*RadioStation, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -215,6 +215,7 @@ const (
|
||||
ContentTypePlaylist
|
||||
ContentTypeTrack
|
||||
ContentTypeGenre
|
||||
ContentTypeRadioStation
|
||||
)
|
||||
|
||||
func (c ContentType) String() string {
|
||||
@@ -229,6 +230,8 @@ func (c ContentType) String() string {
|
||||
return "Playlist"
|
||||
case ContentTypeGenre:
|
||||
return "Genre"
|
||||
case ContentTypeRadioStation:
|
||||
return "Radio station"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
@@ -243,8 +246,9 @@ type SearchResult struct {
|
||||
// for Album / Playlist: track count
|
||||
// Artist / Genre: album count
|
||||
// Track: length (seconds)
|
||||
// for Radio: none
|
||||
Size int
|
||||
|
||||
// Unset for ContentTypes Artist, Playlist, and Genre
|
||||
// Unset for ContentTypes Artist, Playlist, Genre, and RadioStation
|
||||
ArtistName string
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ func (s *subsonicMediaProvider) SearchAll(searchQuery string, maxResults int) ([
|
||||
var result *subsonic.SearchResult3
|
||||
var playlists []*subsonic.Playlist
|
||||
var genres []*subsonic.Genre
|
||||
var radios []*mediaprovider.RadioStation
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@@ -60,12 +61,23 @@ func (s *subsonicMediaProvider) SearchAll(searchQuery string, maxResults int) ([
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
r, e := s.GetRadioStations()
|
||||
if e == nil {
|
||||
radios = sharedutil.FilterSlice(r, func(r *mediaprovider.RadioStation) bool {
|
||||
return helpers.AllTermsMatch(strings.ToLower(sanitize.Accents(r.Name)), queryLowerWords)
|
||||
})
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := mergeResults(result, playlists, genres)
|
||||
results := mergeResults(result, playlists, genres, radios)
|
||||
helpers.RankSearchResults(results, querySanitized, queryLowerWords)
|
||||
if len(results) > maxResults {
|
||||
results = results[:maxResults]
|
||||
@@ -77,6 +89,7 @@ func mergeResults(
|
||||
searchResult *subsonic.SearchResult3,
|
||||
matchingPlaylists []*subsonic.Playlist,
|
||||
matchingGenres []*subsonic.Genre,
|
||||
matchingRadios []*mediaprovider.RadioStation,
|
||||
) []*mediaprovider.SearchResult {
|
||||
var results []*mediaprovider.SearchResult
|
||||
|
||||
@@ -131,6 +144,14 @@ func mergeResults(
|
||||
})
|
||||
}
|
||||
|
||||
for _, r := range matchingRadios {
|
||||
results = append(results, &mediaprovider.SearchResult{
|
||||
Type: mediaprovider.ContentTypeRadioStation,
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ import (
|
||||
"github.com/dweymouth/supersonic/sharedutil"
|
||||
)
|
||||
|
||||
const cacheValidDurationSeconds = 60
|
||||
const (
|
||||
playlistCacheValidDurationSeconds = 60
|
||||
cacheValidDurationSeconds = 120 // genres and radios aren't expected to change as much
|
||||
)
|
||||
|
||||
type subsonicMediaProvider struct {
|
||||
client *subsonic.Client
|
||||
@@ -29,6 +32,9 @@ type subsonicMediaProvider struct {
|
||||
|
||||
playlistsCached []*mediaprovider.Playlist
|
||||
playlistsCachedAt int64 // unix
|
||||
|
||||
radiosCached []*mediaprovider.RadioStation
|
||||
radiosCachedAt int64 // unix
|
||||
}
|
||||
|
||||
func SubsonicMediaProvider(subsonicClient *subsonic.Client) mediaprovider.MediaProvider {
|
||||
@@ -194,7 +200,7 @@ func (s *subsonicMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.P
|
||||
}
|
||||
|
||||
func (s *subsonicMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error) {
|
||||
if s.playlistsCached != nil && time.Now().Unix()-s.playlistsCachedAt < cacheValidDurationSeconds {
|
||||
if s.playlistsCached != nil && time.Now().Unix()-s.playlistsCachedAt < playlistCacheValidDurationSeconds {
|
||||
return s.playlistsCached, nil
|
||||
}
|
||||
|
||||
@@ -436,11 +442,15 @@ func (s *subsonicMediaProvider) GetPlayQueue() (*mediaprovider.SavedPlayQueue, e
|
||||
var _ mediaprovider.RadioProvider = (*subsonicMediaProvider)(nil)
|
||||
|
||||
func (s *subsonicMediaProvider) GetRadioStations() ([]*mediaprovider.RadioStation, error) {
|
||||
if s.radiosCached != nil && time.Now().Unix()-s.radiosCachedAt < cacheValidDurationSeconds {
|
||||
return s.radiosCached, nil
|
||||
}
|
||||
|
||||
rs, err := s.client.GetInternetRadioStations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sharedutil.MapSlice(rs, func(rs *subsonic.InternetRadioStation) *mediaprovider.RadioStation {
|
||||
s.radiosCached = sharedutil.MapSlice(rs, func(rs *subsonic.InternetRadioStation) *mediaprovider.RadioStation {
|
||||
return &mediaprovider.RadioStation{
|
||||
// TODO - subsonic library is missing ID in its radiostation object. add it
|
||||
ID: "radio-" + strings.ReplaceAll(rs.Name, " ", ""),
|
||||
@@ -448,7 +458,23 @@ func (s *subsonicMediaProvider) GetRadioStations() ([]*mediaprovider.RadioStatio
|
||||
HomePageURL: rs.HomePageUrl,
|
||||
StreamURL: rs.StreamUrl,
|
||||
}
|
||||
}), nil
|
||||
})
|
||||
s.radiosCachedAt = time.Now().Unix()
|
||||
return s.radiosCached, nil
|
||||
}
|
||||
|
||||
func (s *subsonicMediaProvider) GetRadioStation(id string) (*mediaprovider.RadioStation, error) {
|
||||
rs, err := s.GetRadioStations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index := slices.IndexFunc(rs, func(r *mediaprovider.RadioStation) bool {
|
||||
return r.ID == id
|
||||
})
|
||||
if index < 0 {
|
||||
return nil, errors.New("radio station not found")
|
||||
}
|
||||
return rs[index], nil
|
||||
}
|
||||
|
||||
func toTrack(ch *subsonic.Child) *mediaprovider.Track {
|
||||
|
||||
@@ -234,6 +234,11 @@ func (p *PlaybackManager) LoadRadioStation(station *mediaprovider.RadioStation,
|
||||
p.engine.LoadRadioStation(station, queueMode)
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) PlayRadioStation(station *mediaprovider.RadioStation) error {
|
||||
p.LoadRadioStation(station, Replace)
|
||||
return p.PlayFromBeginning()
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) fetchAndPlayTracks(fetchFn func() ([]*mediaprovider.Track, error)) {
|
||||
if songs, err := fetchFn(); err != nil {
|
||||
log.Printf("error fetching tracks: %s", err.Error())
|
||||
|
||||
@@ -99,8 +99,7 @@ func (a *RadiosPage) load(searchOnLoad bool, scrollPos float32) {
|
||||
}
|
||||
|
||||
func (a *RadiosPage) onPlay(station *mediaprovider.RadioStation) {
|
||||
a.pm.LoadRadioStation(station, backend.Replace)
|
||||
a.pm.PlayFromBeginning()
|
||||
a.pm.PlayRadioStation(station)
|
||||
}
|
||||
|
||||
func (a *RadiosPage) onQueue(station *mediaprovider.RadioStation, next bool) {
|
||||
|
||||
@@ -591,6 +591,12 @@ func (c *Controller) ShowQuickSearch() {
|
||||
c.NavigateTo(PlaylistRoute(id))
|
||||
case mediaprovider.ContentTypeGenre:
|
||||
c.NavigateTo(GenreRoute(id))
|
||||
case mediaprovider.ContentTypeRadioStation:
|
||||
if rp, ok := c.App.ServerManager.Server.(mediaprovider.RadioProvider); ok {
|
||||
if radio, err := rp.GetRadioStation(id); err == nil {
|
||||
go c.App.PlaybackManager.PlayRadioStation(radio)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
c.ClosePopUpOnEscape(pop)
|
||||
|
||||
@@ -212,6 +212,8 @@ func placeholderIconForContentType(c mediaprovider.ContentType) fyne.Resource {
|
||||
return myTheme.GenreIcon
|
||||
case mediaprovider.ContentTypePlaylist:
|
||||
return myTheme.PlaylistIcon
|
||||
case mediaprovider.ContentTypeRadioStation:
|
||||
return myTheme.RadioIcon
|
||||
default:
|
||||
return theme.WarningIcon() // unreached
|
||||
}
|
||||
@@ -284,6 +286,8 @@ func (s *searchResult) Update(result *mediaprovider.SearchResult) {
|
||||
secondaryText = result.ArtistName
|
||||
case mediaprovider.ContentTypePlaylist:
|
||||
secondaryText = fmt.Sprintf("%d %s", result.Size, maybePluralize("track", result.Size))
|
||||
case mediaprovider.ContentTypeRadioStation:
|
||||
fallthrough // result.Size is always 0 for radios
|
||||
case mediaprovider.ContentTypeGenre:
|
||||
if result.Size > 0 {
|
||||
secondaryText = fmt.Sprintf("%d %s", result.Size, maybePluralize("album", result.Size))
|
||||
|
||||
Reference in New Issue
Block a user