feat: Add Song radio option in tracklist.
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.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -80,6 +80,11 @@ var ResFilterSvg = &fyne.StaticResource{
|
||||
StaticContent: []byte(
|
||||
"<svg fill=\"#000000\" version=\"1.1\" id=\"Capa_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" \r\n\t width=\"800px\" height=\"800px\" viewBox=\"0 0 971.986 971.986\"\r\n\t xml:space=\"preserve\">\r\n<g>\r\n\t<path d=\"M370.216,459.3c10.2,11.1,15.8,25.6,15.8,40.6v442c0,26.601,32.1,40.101,51.1,21.4l123.3-141.3\r\n\t\tc16.5-19.8,25.6-29.601,25.6-49.2V500c0-15,5.7-29.5,15.8-40.601L955.615,75.5c26.5-28.8,6.101-75.5-33.1-75.5h-873\r\n\t\tc-39.2,0-59.7,46.6-33.1,75.5L370.216,459.3z\"/>\r\n</g>\r\n</svg>\r\n"),
|
||||
}
|
||||
var ResBroadcastSvg = &fyne.StaticResource{
|
||||
StaticName: "broadcast.svg",
|
||||
StaticContent: []byte(
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<svg width=\"800px\" height=\"800px\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4.92893 2.92896L6.34315 4.34317C4.89543 5.79088 4 7.79088 4 10C4 12.2092 4.89543 14.2092 6.34315 15.6569L4.92893 17.0711C3.11929 15.2614 2 12.7614 2 10C2 7.2386 3.11929 4.7386 4.92893 2.92896ZM19.0711 2.92896C20.8807 4.7386 22 7.2386 22 10C22 12.7614 20.8807 15.2614 19.0711 17.0711L17.6569 15.6569C19.1046 14.2092 20 12.2092 20 10C20 7.79088 19.1046 5.79088 17.6569 4.34317L19.0711 2.92896ZM7.75736 5.75738L9.17157 7.1716C8.44771 7.89545 8 8.89545 8 10C8 11.1046 8.44771 12.1046 9.17157 12.8285L7.75736 14.2427C6.67157 13.1569 6 11.6569 6 10C6 8.34317 6.67157 6.84317 7.75736 5.75738ZM16.2426 5.75738C17.3284 6.84317 18 8.34317 18 10C18 11.6569 17.3284 13.1569 16.2426 14.2427L14.8284 12.8285C15.5523 12.1046 16 11.1046 16 10C16 8.89545 15.5523 7.89545 14.8284 7.1716L16.2426 5.75738ZM12 12C10.8954 12 10 11.1046 10 10C10 8.89545 10.8954 8.00002 12 8.00002C13.1046 8.00002 14 8.89545 14 10C14 11.1046 13.1046 12 12 12ZM12 14C12.5798 14 13.0774 14.413 13.1843 14.9829L14.5 22H9.5L10.8157 14.9829C10.9226 14.413 11.4202 14 12 14Z\"/>\n</svg>"),
|
||||
}
|
||||
var ResRepeatSvg = &fyne.StaticResource{
|
||||
StaticName: "repeat.svg",
|
||||
StaticContent: []byte(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.92893 2.92896L6.34315 4.34317C4.89543 5.79088 4 7.79088 4 10C4 12.2092 4.89543 14.2092 6.34315 15.6569L4.92893 17.0711C3.11929 15.2614 2 12.7614 2 10C2 7.2386 3.11929 4.7386 4.92893 2.92896ZM19.0711 2.92896C20.8807 4.7386 22 7.2386 22 10C22 12.7614 20.8807 15.2614 19.0711 17.0711L17.6569 15.6569C19.1046 14.2092 20 12.2092 20 10C20 7.79088 19.1046 5.79088 17.6569 4.34317L19.0711 2.92896ZM7.75736 5.75738L9.17157 7.1716C8.44771 7.89545 8 8.89545 8 10C8 11.1046 8.44771 12.1046 9.17157 12.8285L7.75736 14.2427C6.67157 13.1569 6 11.6569 6 10C6 8.34317 6.67157 6.84317 7.75736 5.75738ZM16.2426 5.75738C17.3284 6.84317 18 8.34317 18 10C18 11.6569 17.3284 13.1569 16.2426 14.2427L14.8284 12.8285C15.5523 12.1046 16 11.1046 16 10C16 8.89545 15.5523 7.89545 14.8284 7.1716L16.2426 5.75738ZM12 12C10.8954 12 10 11.1046 10 10C10 8.89545 10.8954 8.00002 12 8.00002C13.1046 8.00002 14 8.89545 14 10C14 11.1046 13.1046 12 12 12ZM12 14C12.5798 14 13.0774 14.413 13.1843 14.9829L14.5 22H9.5L10.8157 14.9829C10.9226 14.413 11.4202 14 12 14Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
+19
-1
@@ -76,6 +76,9 @@ type TracklistOptions struct {
|
||||
|
||||
// Disables the sharing option.
|
||||
DisableSharing bool
|
||||
|
||||
// Disables the song radio option.
|
||||
DisableSongRadio bool
|
||||
}
|
||||
|
||||
type Tracklist struct {
|
||||
@@ -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)
|
||||
@@ -114,6 +118,7 @@ type Tracklist struct {
|
||||
ctxMenu *fyne.Menu
|
||||
ratingSubmenu *fyne.MenuItem
|
||||
shareMenuItem *fyne.MenuItem
|
||||
songRadioMenuItem *fyne.MenuItem
|
||||
container *fyne.Container
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user