Include radio stations in Quick Search

This commit is contained in:
Drew Weymouth
2024-07-01 08:04:10 -07:00
parent 5d13e7bf96
commit 59d979ba80
8 changed files with 74 additions and 8 deletions
+1
View File
@@ -270,6 +270,7 @@ type LyricsProvider interface {
}
type RadioProvider interface {
GetRadioStation(id string) (*RadioStation, error)
GetRadioStations() ([]*RadioStation, error)
}
+5 -1
View File
@@ -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
}
+22 -1
View File
@@ -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 {
+5
View File
@@ -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())
+1 -2
View File
@@ -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) {
+6
View File
@@ -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)
+4
View File
@@ -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))