Skip to content

Commit

Permalink
feat: Add Song radio option in tracklist.
Browse files Browse the repository at this point in the history
At this moment this feature is only available on the Subsonic media provider. Using a
Jellyfin server the "Play song radio" option will be disabled.
  • Loading branch information
natilou committed Mar 24, 2024
1 parent ec085cf commit 6c81d1b
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 1 deletion.
4 changes: 4 additions & 0 deletions backend/mediaprovider/mediaprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ type SupportsSharing interface {
CanShareArtists() bool
}

type SupportsSongRadio interface {
GetSongRadio(trackID string, count int) ([]*Track, error)
}

type LyricsProvider interface {
GetLyrics(track *Track) (*Lyrics, error)
}
Expand Down
8 changes: 8 additions & 0 deletions backend/mediaprovider/subsonic/subsonicmediaprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,3 +543,11 @@ func fillPlaylist(pl *subsonic.Playlist, playlist *mediaprovider.Playlist) {
playlist.TrackCount = pl.SongCount
playlist.Duration = pl.Duration
}

func (s *subsonicMediaProvider) GetSongRadio(trackID string, count int) ([]*mediaprovider.Track, error) {
tr, err := s.client.GetSimilarSongs(trackID, map[string]string{"count": strconv.Itoa(count)})
if err != nil {
return nil, err
}
return sharedutil.MapSlice(tr, toTrack), nil
}
5 changes: 5 additions & 0 deletions res/bundled.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions res/bundled_gen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ fyne bundle -append -prefix Res icons/publicdomain/theatermasks.svg >> bundled.g
fyne bundle -append -prefix Res icons/publicdomain/grid.svg >> bundled.go
fyne bundle -append -prefix Res icons/publicdomain/list.svg >> bundled.go
fyne bundle -append -prefix Res icons/publicdomain/filter.svg >> bundled.go
fyne bundle -append -prefix Res icons/remix_design/broadcast.svg >> bundled.go
fyne bundle -append -prefix Res icons/remix_design/repeat.svg >> bundled.go
fyne bundle -append -prefix Res icons/remix_design/repeatone.svg >> bundled.go
fyne bundle -append -prefix Res icons/remix_design/shuffle.svg >> bundled.go
Expand Down
4 changes: 4 additions & 0 deletions res/icons/remix_design/broadcast.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions ui/browsing/albumpage.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,10 @@ func newAlbumPage(
a.tracklist.SetSorting(sort)
_, canRate := a.mp.(mediaprovider.SupportsRating)
_, canShare := a.mp.(mediaprovider.SupportsSharing)
_, canSongRadio := a.mp.(mediaprovider.SupportsSongRadio)
a.tracklist.Options.DisableRating = !canRate
a.tracklist.Options.DisableSharing = !canShare
a.tracklist.Options.DisableSongRadio = !canSongRadio
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
a.cfg.TracklistColumns = cols
}
Expand Down
2 changes: 2 additions & 0 deletions ui/browsing/artistpage.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,10 @@ func (a *ArtistPage) showTopTracks() {
tl.Options = widgets.TracklistOptions{AutoNumber: true}
_, canRate := a.mp.(mediaprovider.SupportsRating)
_, canShare := a.mp.(mediaprovider.SupportsSharing)
_, canSongRadio := a.mp.(mediaprovider.SupportsSongRadio)
tl.Options.DisableRating = !canRate
tl.Options.DisableSharing = !canShare
tl.Options.DisableSongRadio = !canSongRadio
tl.SetVisibleColumns(a.cfg.TracklistColumns)
tl.SetSorting(a.trackSort)
tl.OnVisibleColumnsChanged = func(cols []string) {
Expand Down
2 changes: 2 additions & 0 deletions ui/browsing/favoritespage.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,10 @@ func (a *FavoritesPage) onShowFavoriteSongs() {
tracklist.Options = widgets.TracklistOptions{AutoNumber: true}
_, canRate := a.mp.(mediaprovider.SupportsRating)
_, canShare := a.mp.(mediaprovider.SupportsSharing)
_, canSongRadio := a.mp.(mediaprovider.SupportsSongRadio)
tracklist.Options.DisableRating = !canRate
tracklist.Options.DisableSharing = !canShare
tracklist.Options.DisableSongRadio = !canSongRadio
tracklist.SetVisibleColumns(a.cfg.TracklistColumns)
tracklist.SetSorting(a.trackSort)
tracklist.OnVisibleColumnsChanged = func(cols []string) {
Expand Down
2 changes: 2 additions & 0 deletions ui/browsing/playlistpage.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,13 @@ func newPlaylistPage(
}
_, canRate := a.sm.Server.(mediaprovider.SupportsRating)
_, canShare := a.sm.Server.(mediaprovider.SupportsSharing)
_, canSongRadio := a.sm.Server.(mediaprovider.SupportsSongRadio)
remove := fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)
remove.Icon = theme.ContentClearIcon()
a.tracklist.Options = widgets.TracklistOptions{
DisableRating: !canRate,
DisableSharing: !canShare,
DisableSongRadio: !canSongRadio,
AuxiliaryMenuItems: []*fyne.MenuItem{
util.NewReorderTracksSubmenu(a.doSetNewTrackOrder),
remove,
Expand Down
4 changes: 4 additions & 0 deletions ui/browsing/trackspage.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type tracksPageState struct {
mp mediaprovider.MediaProvider
canRate bool
canShare bool
canSongRadio bool
}

func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, pool *util.WidgetPool, mp mediaprovider.MediaProvider) *TracksPage {
Expand All @@ -50,10 +51,12 @@ func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig,
t.tracklist = t.obtainTracklist()
_, t.canRate = mp.(mediaprovider.SupportsRating)
_, t.canShare = mp.(mediaprovider.SupportsSharing)
_, t.canSongRadio = mp.(mediaprovider.SupportsSongRadio)
t.tracklist.Options = widgets.TracklistOptions{
DisableSorting: true,
DisableRating: !t.canRate,
DisableSharing: !t.canShare,
DisableSongRadio: !t.canSongRadio,
AutoNumber: true,
}
t.tracklist.SetVisibleColumns(conf.TracklistColumns)
Expand Down Expand Up @@ -141,6 +144,7 @@ func (t *TracksPage) doSearch(query string) {
DisableSorting: true,
DisableRating: !t.canRate,
DisableSharing: !t.canShare,
DisableSongRadio: !t.canSongRadio,
}
t.searchTracklist.SetVisibleColumns(t.conf.TracklistColumns)
t.searchTracklist.SetNowPlaying(t.nowPlayingID)
Expand Down
34 changes: 34 additions & 0 deletions ui/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,20 @@ func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widget
tracklist.OnShare = func(trackID string) {
go m.ShowShareDialog(trackID)
}
tracklist.OnPlaySongRadio = func (track *mediaprovider.Track) {
go func() {
tracks, err := m.GetSongRadioTracks(track)
if err != nil {
log.Printf("Error getting song radio: ", err)
return
}
m.App.PlaybackManager.LoadTracks(tracks, false, false)
if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto {
m.App.PlaybackManager.SetReplayGainMode(mode)
}
m.App.PlaybackManager.PlayFromBeginning()
}()
}
}

func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) {
Expand Down Expand Up @@ -868,3 +882,23 @@ func (c *Controller) ShowAlbumInfoDialog(albumID, albumName string, albumCover i
pop.Show()
}()
}

func (c *Controller) GetSongRadioTracks(sourceTrack *mediaprovider.Track) ([]*mediaprovider.Track, error) {
r, ok := c.App.ServerManager.Server.(mediaprovider.SupportsSongRadio)
if !ok {
return nil, fmt.Errorf("Server does not support song radio")
}

radioTracks, err := r.GetSongRadio(sourceTrack.ID, 100)
if err != nil {
return nil, fmt.Errorf("Error getting song radio: ", err)
}

// The goal of this implementation is to place the source track first in the queue.
filteredTracks := sharedutil.FilterSlice(radioTracks, func(track *mediaprovider.Track) bool{
return track.ID != sourceTrack.ID
})
tracks := []*mediaprovider.Track{sourceTrack}
tracks = append(tracks, filteredTracks...)
return tracks, nil
}
1 change: 1 addition & 0 deletions ui/theme/theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
var (
AlbumIcon fyne.Resource = theme.NewThemedResource(res.ResDiscSvg)
ArtistIcon fyne.Resource = theme.NewThemedResource(res.ResPeopleSvg)
BroadcastIcon fyne.Resource = theme.NewThemedResource(res.ResBroadcastSvg)
FavoriteIcon fyne.Resource = theme.NewThemedResource(res.ResHeartFilledSvg)
NotFavoriteIcon fyne.Resource = theme.NewThemedResource(res.ResHeartOutlineSvg)
NowPlayingIcon fyne.Resource = theme.NewThemedResource(res.ResHeadphonesSvg)
Expand Down
20 changes: 19 additions & 1 deletion ui/widgets/tracklist.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ type TracklistOptions struct {

// Disables the sharing option.
DisableSharing bool

// Disables the song radio option.
DisableSongRadio bool
}

type Tracklist struct {
Expand All @@ -92,6 +95,7 @@ type Tracklist struct {
OnSetRating func(trackIDs []string, rating int)
OnDownload func(tracks []*mediaprovider.Track, downloadName string)
OnShare func(trackID string)
OnPlaySongRadio func(track *mediaprovider.Track)

OnShowArtistPage func(artistID string)
OnShowAlbumPage func(albumID string)
Expand All @@ -114,6 +118,7 @@ type Tracklist struct {
ctxMenu *fyne.Menu
ratingSubmenu *fyne.MenuItem
shareMenuItem *fyne.MenuItem
songRadioMenuItem *fyne.MenuItem
container *fyne.Container
}

Expand Down Expand Up @@ -539,8 +544,12 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
}
})
add.Icon = theme.ContentAddIcon()
t.songRadioMenuItem = fyne.NewMenuItem("Play song radio", func() {
t.onPlaySongRadio(t.selectedTracks())
})
t.songRadioMenuItem.Icon = myTheme.BroadcastIcon
t.ctxMenu.Items = append(t.ctxMenu.Items,
play, shuffle, add)
play, shuffle, add, t.songRadioMenuItem)
}
playlist := fyne.NewMenuItem("Add to playlist...", func() {
if t.OnAddToPlaylist != nil {
Expand Down Expand Up @@ -579,6 +588,7 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
}
t.ratingSubmenu.Disabled = t.Options.DisableRating
t.shareMenuItem.Disabled = t.Options.DisableSharing || len(t.selectedTracks()) != 1
t.songRadioMenuItem.Disabled = t.Options.DisableSongRadio || len(t.selectedTracks()) != 1
widget.ShowPopUpMenuAtPosition(t.ctxMenu, fyne.CurrentApp().Driver().CanvasForObject(t), e.AbsolutePosition)
}

Expand Down Expand Up @@ -649,6 +659,14 @@ func (t *Tracklist) onShare(tracks []*mediaprovider.Track) {
}
}

func (t *Tracklist) onPlaySongRadio(tracks []*mediaprovider.Track) {
if t.OnPlaySongRadio != nil {
if len(tracks) > 0 {
t.OnPlaySongRadio(tracks[0])
}
}
}

func (t *Tracklist) selectedTracks() []*mediaprovider.Track {
t.tracksMutex.RLock()
defer t.tracksMutex.RUnlock()
Expand Down

0 comments on commit 6c81d1b

Please sign in to comment.