Files
supersonic/ui/bottompanel.go
T
Michael Manganiello 2ab19f65e1 Add Loop functionality to player controls
Adding the Loop functionality, along with the Player changes to support
it, and a button to the player controls.

`Player` now has a `loopMode` attribute, which determines whether it
should loop. At the moment, only the following modes are supported:

* `LoopNone`: Disables loop.
* `LoopAll`: Enables loop for the entire playlist queue.

This has been designed as an enum, to support other modes in the future
(e.g. loop for the current track only).

It depends on the `loop` functionality provided by MPV (using the
[`loop-playlist` option](https://mpv.io/manual/master/#options-loop-playlist)),
so no custom logic is needed to reset the currently playing index.
2023-06-18 22:43:03 -03:00

139 lines
3.9 KiB
Go

package ui
import (
"fmt"
"image"
"log"
"time"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/player"
"github.com/dweymouth/supersonic/ui/controller"
"github.com/dweymouth/supersonic/ui/layouts"
"github.com/dweymouth/supersonic/ui/widgets"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
type BottomPanel struct {
widget.BaseWidget
ImageManager *backend.ImageManager
playbackManager *backend.PlaybackManager
NowPlaying *widgets.NowPlayingCard
Controls *widgets.PlayerControls
AuxControls *widgets.AuxControls
coverArtID string
container *fyne.Container
}
var _ fyne.Widget = (*BottomPanel)(nil)
func NewBottomPanel(p *player.Player, contr *controller.Controller) *BottomPanel {
bp := &BottomPanel{}
bp.ExtendBaseWidget(bp)
p.OnPaused(func() {
bp.Controls.SetPlaying(false)
})
p.OnPlaying(func() {
bp.Controls.SetPlaying(true)
})
p.OnStopped(func() {
bp.Controls.SetPlaying(false)
})
p.OnLoopModeChanged(func(mode string) {
bp.Controls.SetLoopMode(mode)
})
bp.NowPlaying = widgets.NewNowPlayingCard()
bp.NowPlaying.OnShowCoverImage = func() {
im, err := bp.ImageManager.GetFullSizeCoverArt(bp.coverArtID)
if err != nil {
log.Printf("error getting full size cover image: %s", err.Error())
} else {
contr.ShowPopUpImage(im)
}
}
bp.NowPlaying.OnSetFavorite = func(fav bool) {
contr.SetTrackFavorites([]string{bp.playbackManager.NowPlaying().ID}, fav)
}
bp.NowPlaying.OnSetRating = func(rating int) {
contr.SetTrackRatings([]string{bp.playbackManager.NowPlaying().ID}, rating)
}
bp.NowPlaying.OnAddToPlaylist = func() {
contr.DoAddTracksToPlaylistWorkflow([]string{bp.playbackManager.NowPlaying().ID})
}
bp.NowPlaying.OnAlbumNameTapped(func() {
contr.NavigateTo(controller.AlbumRoute(bp.playbackManager.NowPlaying().AlbumID))
})
bp.NowPlaying.OnArtistNameTapped(func() {
contr.NavigateTo(controller.ArtistRoute(bp.playbackManager.NowPlaying().ArtistIDs[0]))
})
bp.NowPlaying.OnTrackNameTapped(func() {
contr.NavigateTo(controller.NowPlayingRoute(bp.playbackManager.NowPlaying().ID))
})
bp.Controls = widgets.NewPlayerControls()
bp.Controls.OnPlayPause(func() {
p.PlayPause()
})
bp.Controls.OnSeekNext(func() {
p.SeekNext()
})
bp.Controls.OnSeekPrevious(func() {
p.SeekBackOrPrevious()
})
bp.Controls.OnSeek(func(f float64) {
p.Seek(fmt.Sprintf("%d", int(f*100)), player.SeekAbsolutePercent)
})
bp.Controls.OnChangeLoopMode(func() {
p.SetNextLoopMode()
})
bp.AuxControls = widgets.NewAuxControls(p.GetVolume())
bp.AuxControls.VolumeControl.OnVolumeChanged = func(v int) {
_ = p.SetVolume(v)
}
bp.container = container.New(layouts.NewLeftMiddleRightLayout(500),
bp.NowPlaying, bp.Controls, bp.AuxControls)
return bp
}
func (bp *BottomPanel) SetPlaybackManager(pm *backend.PlaybackManager) {
bp.playbackManager = pm
pm.OnSongChange(bp.onSongChange)
pm.OnPlayTimeUpdate(func(cur, total float64) {
if !pm.IsSeeking() {
bp.Controls.UpdatePlayTime(cur, total)
}
})
}
func (bp *BottomPanel) onSongChange(song, _ *mediaprovider.Track) {
if song == nil {
bp.NowPlaying.Update("", "", false, "", nil)
} else {
bp.coverArtID = song.CoverArtID
var im image.Image
if bp.ImageManager != nil {
// set image to expire not long after the length of the song
// if song is played through without much pausing, image will still
// be in cache for the next song if it's from the same album, or
// if the user navigates to the album page for the track
imgTTLSec := song.Duration + 30
im, _ = bp.ImageManager.GetCoverThumbnailWithTTL(song.CoverArtID, time.Duration(imgTTLSec)*time.Second)
}
bp.NowPlaying.Update(song.Name, song.ArtistNames[0], song.ArtistIDs[0] != "", song.Album, im)
}
}
func (bp *BottomPanel) CreateRenderer() fyne.WidgetRenderer {
bp.ExtendBaseWidget(bp)
return widget.NewSimpleRenderer(bp.container)
}