diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index bbe7dcd..fbef25c 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -424,6 +424,7 @@ func (s *subsonicMediaProvider) GetRadioStations() ([]*mediaprovider.RadioStatio } return 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, " ", ""), Name: rs.Name, HomePageURL: rs.HomePageUrl, diff --git a/backend/playbackengine.go b/backend/playbackengine.go index f4589eb..91154e4 100644 --- a/backend/playbackengine.go +++ b/backend/playbackengine.go @@ -235,6 +235,31 @@ func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, insertQueueMo return nil } +func (p *playbackEngine) LoadRadioStation(radio *mediaprovider.RadioStation, insertMode InsertQueueMode) { + if insertMode == Replace { + p.player.Stop() + p.nowPlayingIdx = -1 + p.playQueue = nil + } + needToSetNext := insertMode == InsertNext || (insertMode == Append && p.nowPlayingIdx == len(p.playQueue)-1) + insertIdx := len(p.playQueue) + if insertMode == InsertNext { + insertIdx = p.nowPlayingIdx + 1 + } + new := make([]mediaprovider.MediaItem, len(p.playQueue)+1) + firstHalf := p.playQueue[:insertIdx] + copy(new, firstHalf) + new[len(firstHalf)] = radio + copy(new[len(firstHalf)+1:], p.playQueue[insertIdx:]) + p.playQueue = new + + if needToSetNext { + p.setNextTrack(p.nowPlayingIdx + 1) + } + + p.invokeNoArgCallbacks(p.onQueueChange) +} + // Stop playback and clear the play queue. func (p *playbackEngine) StopAndClearPlayQueue() { changed := len(p.playQueue) > 0 diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 2581082..9d06e67 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -182,6 +182,10 @@ func (p *PlaybackManager) PlaySimilarSongs(id string) { }) } +func (p *PlaybackManager) LoadRadioStation(station *mediaprovider.RadioStation, queueMode InsertQueueMode) { + p.engine.LoadRadioStation(station, queueMode) +} + func (p *PlaybackManager) fetchAndPlayTracks(fetchFn func() ([]*mediaprovider.Track, error)) { if songs, err := fetchFn(); err != nil { log.Printf("error fetching tracks: %s", err.Error()) diff --git a/ui/browsing/browsingpane.go b/ui/browsing/browsingpane.go index 4fce17d..055f3ec 100644 --- a/ui/browsing/browsingpane.go +++ b/ui/browsing/browsingpane.go @@ -140,11 +140,13 @@ func (b *BrowsingPane) AddSettingsMenuSeparator() { fyne.NewMenuItemSeparator()) } -func (b *BrowsingPane) AddNavigationButton(icon fyne.Resource, pageName controller.PageName, action func()) { +func (b *BrowsingPane) AddNavigationButton(icon fyne.Resource, pageName controller.PageName, action func()) *widget.Button { // make a copy of the icon, because it can change the color browsingPaneIcon := theme.NewThemedResource(icon) - b.navBtnsContainer.Add(widget.NewButtonWithIcon("", browsingPaneIcon, action)) + btn := widget.NewButtonWithIcon("", browsingPaneIcon, action) + b.navBtnsContainer.Add(btn) b.navBtnsPageMap[pageName] = browsingPaneIcon + return btn } func (b *BrowsingPane) DisableNavigationButtons() { diff --git a/ui/browsing/radiospage.go b/ui/browsing/radiospage.go index af313f1..ba2caa6 100644 --- a/ui/browsing/radiospage.go +++ b/ui/browsing/radiospage.go @@ -5,6 +5,7 @@ import ( "net/url" "strings" + "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/controller" @@ -25,6 +26,7 @@ type RadiosPage struct { contr *controller.Controller rp mediaprovider.RadioProvider + pm *backend.PlaybackManager radios []*mediaprovider.RadioStation list *RadioList @@ -33,19 +35,24 @@ type RadiosPage struct { searcher *widgets.SearchEntry } -func NewRadiosPage(contr *controller.Controller, rp mediaprovider.RadioProvider) *RadiosPage { - return newRadiosPage(contr, rp, "", 0) +func NewRadiosPage(contr *controller.Controller, rp mediaprovider.RadioProvider, pm *backend.PlaybackManager) *RadiosPage { + return newRadiosPage(contr, rp, pm, "", 0) } -func newRadiosPage(contr *controller.Controller, rp mediaprovider.RadioProvider, searchText string, scrollPos float32) *RadiosPage { +func newRadiosPage(contr *controller.Controller, rp mediaprovider.RadioProvider, pm *backend.PlaybackManager, searchText string, scrollPos float32) *RadiosPage { a := &RadiosPage{ contr: contr, rp: rp, + pm: pm, titleDisp: widget.NewRichTextWithText("Internet Radio Stations"), } a.ExtendBaseWidget(a) a.titleDisp.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameHeadingText a.list = NewRadioList() + a.list.OnPlay = func(station *mediaprovider.RadioStation) { + pm.LoadRadioStation(station, backend.Replace) + pm.PlayFromBeginning() + } a.searcher = widgets.NewSearchEntry() a.searcher.PlaceHolder = "Search page" a.searcher.OnSearched = a.onSearched @@ -105,9 +112,7 @@ func (a *RadiosPage) Scroll(amount float32) { } func (a *RadiosPage) Route() controller.Route { - // TODO - //return controller.RadiosRoute() - return controller.Route{} + return controller.RadiosRoute() } func (a *RadiosPage) Reload() { @@ -118,6 +123,7 @@ func (a *RadiosPage) Save() SavedPage { return &savedrRadiosPage{ contr: a.contr, rp: a.rp, + pm: a.pm, searchText: a.searcher.Entry.Text, scrollPos: a.list.list.GetScrollOffset(), } @@ -126,12 +132,13 @@ func (a *RadiosPage) Save() SavedPage { type savedrRadiosPage struct { contr *controller.Controller rp mediaprovider.RadioProvider + pm *backend.PlaybackManager searchText string scrollPos float32 } func (s *savedrRadiosPage) Restore() Page { - return newRadiosPage(s.contr, s.rp, s.searchText, s.scrollPos) + return newRadiosPage(s.contr, s.rp, s.pm, s.searchText, s.scrollPos) } func (a *RadiosPage) buildContainer() { @@ -150,7 +157,7 @@ func (a *RadiosPage) CreateRenderer() fyne.WidgetRenderer { type RadioList struct { widget.BaseWidget - OnPlay func(string) + OnPlay func(*mediaprovider.RadioStation) radios []*mediaprovider.RadioStation @@ -228,7 +235,7 @@ func (g *RadioList) SetRadios(radios []*mediaprovider.RadioStation) { func (a *RadioList) onPlayRadio(item *mediaprovider.RadioStation) { if a.OnPlay != nil { - a.OnPlay(item.ID) + a.OnPlay(item) } } diff --git a/ui/browsing/router.go b/ui/browsing/router.go index fd7089a..9252309 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -55,6 +55,10 @@ func (r Router) CreatePage(rte controller.Route) Page { return NewPlaylistsPage(r.Controller, r.widgetPool, &r.App.Config.PlaylistsPage, r.App.ServerManager.Server) case controller.Tracks: return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.widgetPool, r.App.ServerManager.Server, r.App.ImageManager) + case controller.Radios: + var rp mediaprovider.RadioProvider + rp, _ = r.App.ServerManager.Server.(mediaprovider.RadioProvider) + return NewRadiosPage(r.Controller, rp, r.App.PlaybackManager) } return nil } diff --git a/ui/controller/routes.go b/ui/controller/routes.go index dc2255a..83b9e22 100644 --- a/ui/controller/routes.go +++ b/ui/controller/routes.go @@ -15,6 +15,7 @@ const ( Playlist Playlists Tracks + Radios ) type Route struct { @@ -61,6 +62,10 @@ func ArtistsRoute() Route { return Route{Page: Artists} } +func RadiosRoute() Route { + return Route{Page: Radios} +} + func NowPlayingRoute(highlightedTrackID string) Route { return Route{Page: NowPlaying, Arg: highlightedTrackID} } diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 68d5e10..99d56f7 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -53,6 +53,9 @@ type MainWindow struct { theme *theme.MyTheme haveSystemTray bool container *fyne.Container + + // needs to bes shown/hidden when switching between servers based on whether they support radio + radioBtn *widget.Button } func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string, app *backend.App, size fyne.Size) MainWindow { @@ -183,6 +186,11 @@ func (m *MainWindow) RunOnServerConnectedTasks(app *backend.App, displayAppName } m.App.Config.Application.LastCheckedVersion = t } + + _, supportsRadio := m.App.ServerManager.Server.(mediaprovider.RadioProvider) + m.radioBtn.Hidden = !supportsRadio + m.radioBtn.Refresh() + m.App.SaveConfigFile() } @@ -265,6 +273,9 @@ func (m *MainWindow) addNavigationButtons() { m.BrowsingPane.AddNavigationButton(theme.TracksIcon, controller.Tracks, func() { m.Router.NavigateTo(controller.TracksRoute()) }) + m.radioBtn = m.BrowsingPane.AddNavigationButton(theme.TracksIcon /*todo*/, controller.Radios, func() { + m.Router.NavigateTo(controller.RadiosRoute()) + }) } func (m *MainWindow) addShortcuts() {