fullscreen play queue renders correctly
This commit is contained in:
@@ -25,10 +25,12 @@ type FullscreenPage struct {
|
|||||||
fullscreenPageState
|
fullscreenPageState
|
||||||
|
|
||||||
queue []*mediaprovider.Track
|
queue []*mediaprovider.Track
|
||||||
|
queueList *widgets.PlayQueueList
|
||||||
imageLoadCancel context.CancelFunc
|
imageLoadCancel context.CancelFunc
|
||||||
card *widgets.LargeNowPlayingCard
|
card *widgets.LargeNowPlayingCard
|
||||||
nowPlayingID string
|
nowPlayingID string
|
||||||
albumID string
|
albumID string
|
||||||
|
container *fyne.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
type fullscreenPageState struct {
|
type fullscreenPageState struct {
|
||||||
@@ -66,25 +68,28 @@ func NewFullscreenPage(
|
|||||||
a.contr.SetTrackRatings([]string{a.nowPlayingID}, rating)
|
a.contr.SetTrackRatings([]string{a.nowPlayingID}, rating)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.queueList = widgets.NewPlayQueueList(a.im)
|
||||||
|
paddedLayout := &layouts.PercentPadLayout{
|
||||||
|
LeftRightObjectPercent: .8,
|
||||||
|
TopBottomObjectPercent: .8,
|
||||||
|
}
|
||||||
|
a.container = container.NewGridWithColumns(2,
|
||||||
|
container.New(paddedLayout, a.card),
|
||||||
|
container.New(paddedLayout,
|
||||||
|
util.AddHeaderBackground(
|
||||||
|
container.NewAppTabs(
|
||||||
|
container.NewTabItem("Play Queue",
|
||||||
|
container.NewBorder(layout.NewSpacer(), nil, nil, nil, a.queueList)),
|
||||||
|
container.NewTabItem("Lyrics", layout.NewSpacer()),
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
|
||||||
a.Reload()
|
a.Reload()
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *FullscreenPage) CreateRenderer() fyne.WidgetRenderer {
|
func (a *FullscreenPage) CreateRenderer() fyne.WidgetRenderer {
|
||||||
paddedLayout := &layouts.PercentPadLayout{
|
return widget.NewSimpleRenderer(a.container)
|
||||||
LeftRightObjectPercent: .8,
|
|
||||||
TopBottomObjectPercent: .8,
|
|
||||||
}
|
|
||||||
container := container.NewGridWithColumns(2,
|
|
||||||
container.New(paddedLayout, a.card),
|
|
||||||
container.New(paddedLayout,
|
|
||||||
util.AddHeaderBackground(
|
|
||||||
container.NewAppTabs(
|
|
||||||
container.NewTabItem("Up Next", layout.NewSpacer()),
|
|
||||||
container.NewTabItem("Lyrics", layout.NewSpacer()),
|
|
||||||
))),
|
|
||||||
)
|
|
||||||
return widget.NewSimpleRenderer(container)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *FullscreenPage) Save() SavedPage {
|
func (a *FullscreenPage) Save() SavedPage {
|
||||||
@@ -115,10 +120,12 @@ func (a *FullscreenPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Tr
|
|||||||
a.card.SetCoverImage(img)
|
a.card.SetCoverImage(img)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
a.queueList.SetNowPlaying(song.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *FullscreenPage) Reload() {
|
func (a *FullscreenPage) Reload() {
|
||||||
a.queue = a.pm.GetPlayQueue()
|
a.queue = a.pm.GetPlayQueue()
|
||||||
|
a.queueList.SetTracks(a.queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fullscreenPageState) Restore() Page {
|
func (s *fullscreenPageState) Restore() Page {
|
||||||
|
|||||||
@@ -58,6 +58,16 @@ func SelectTrackRange(tracks []*TrackListModel, idx int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FindTrackByID(tracks []*TrackListModel, id string) (*mediaprovider.Track, int) {
|
||||||
|
idx := sharedutil.Find(tracks, func(tr *TrackListModel) bool {
|
||||||
|
return tr.Track.ID == id
|
||||||
|
})
|
||||||
|
if idx >= 0 {
|
||||||
|
return tracks[idx].Track, idx
|
||||||
|
}
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
|
|
||||||
func minInt(a, b int) int {
|
func minInt(a, b int) int {
|
||||||
if a < b {
|
if a < b {
|
||||||
return a
|
return a
|
||||||
|
|||||||
+134
-14
@@ -1,20 +1,30 @@
|
|||||||
package widgets
|
package widgets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/canvas"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/driver/desktop"
|
"fyne.io/fyne/v2/driver/desktop"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"github.com/dweymouth/supersonic/backend"
|
||||||
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
"github.com/dweymouth/supersonic/ui/layouts"
|
"github.com/dweymouth/supersonic/ui/layouts"
|
||||||
"github.com/dweymouth/supersonic/ui/os"
|
"github.com/dweymouth/supersonic/ui/os"
|
||||||
"github.com/dweymouth/supersonic/ui/theme"
|
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||||
"github.com/dweymouth/supersonic/ui/util"
|
"github.com/dweymouth/supersonic/ui/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const thumbnailSize = 60
|
||||||
|
|
||||||
type PlayQueueList struct {
|
type PlayQueueList struct {
|
||||||
FocusList
|
widget.BaseWidget
|
||||||
|
|
||||||
|
list *FocusList
|
||||||
|
|
||||||
OnShowArtistPage func(artistID string)
|
OnShowArtistPage func(artistID string)
|
||||||
OnPlayTrackAt func(idx int)
|
OnPlayTrackAt func(idx int)
|
||||||
@@ -22,10 +32,84 @@ type PlayQueueList struct {
|
|||||||
nowPlayingID string
|
nowPlayingID string
|
||||||
colLayout *layouts.ColumnsLayout
|
colLayout *layouts.ColumnsLayout
|
||||||
|
|
||||||
tracksMutex sync.Mutex
|
tracksMutex sync.RWMutex
|
||||||
tracks []*util.TrackListModel
|
tracks []*util.TrackListModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPlayQueueList(im *backend.ImageManager) *PlayQueueList {
|
||||||
|
p := &PlayQueueList{}
|
||||||
|
p.ExtendBaseWidget(p)
|
||||||
|
|
||||||
|
// #, Cover, Title/Artist, Time
|
||||||
|
p.colLayout = layouts.NewColumnsLayout([]float32{40, thumbnailSize, -1, 60})
|
||||||
|
|
||||||
|
playIconResource := theme.NewThemedResource(theme.MediaPlayIcon())
|
||||||
|
playIconResource.ColorName = theme.ColorNamePrimary
|
||||||
|
playIconImg := canvas.NewImageFromResource(playIconResource)
|
||||||
|
playIconImg.FillMode = canvas.ImageFillContain
|
||||||
|
playIconImg.SetMinSize(fyne.NewSquareSize(theme.IconInlineSize() * 1.5))
|
||||||
|
|
||||||
|
playingIcon := container.NewCenter(playIconImg)
|
||||||
|
|
||||||
|
p.list = NewFocusList(
|
||||||
|
p.lenTracks,
|
||||||
|
func() fyne.CanvasObject {
|
||||||
|
return NewPlayQueueListRow(p, im, playingIcon)
|
||||||
|
},
|
||||||
|
func(itemID widget.ListItemID, item fyne.CanvasObject) {
|
||||||
|
p.tracksMutex.RLock()
|
||||||
|
// we could have removed tracks from the list in between
|
||||||
|
// Fyne calling the length callback and this update callback
|
||||||
|
// so the itemID may be out of bounds. if so, do nothing.
|
||||||
|
if itemID >= len(p.tracks) {
|
||||||
|
p.tracksMutex.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
model := p.tracks[itemID]
|
||||||
|
p.tracksMutex.RUnlock()
|
||||||
|
|
||||||
|
tr := item.(*PlayQueueListRow)
|
||||||
|
p.list.SetItemForID(itemID, tr)
|
||||||
|
if tr.trackID != model.Track.ID || tr.ListItemID != itemID {
|
||||||
|
tr.ListItemID = itemID
|
||||||
|
}
|
||||||
|
tr.Update(model, itemID+1)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PlayQueueList) SetTracks(trs []*mediaprovider.Track) {
|
||||||
|
p.tracksMutex.Lock()
|
||||||
|
p.list.ClearItemForIDMap()
|
||||||
|
p.tracks = util.ToTrackListModels(trs)
|
||||||
|
p.tracksMutex.Unlock()
|
||||||
|
p.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the currently playing track ID and updates the list rendering
|
||||||
|
func (p *PlayQueueList) SetNowPlaying(trackID string) {
|
||||||
|
prevNowPlaying := p.nowPlayingID
|
||||||
|
p.tracksMutex.RLock()
|
||||||
|
trPrev, idxPrev := util.FindTrackByID(p.tracks, prevNowPlaying)
|
||||||
|
tr, idx := util.FindTrackByID(p.tracks, trackID)
|
||||||
|
p.tracksMutex.RUnlock()
|
||||||
|
p.nowPlayingID = trackID
|
||||||
|
if trPrev != nil {
|
||||||
|
p.list.RefreshItem(idxPrev)
|
||||||
|
}
|
||||||
|
if tr != nil {
|
||||||
|
p.list.RefreshItem(idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PlayQueueList) lenTracks() int {
|
||||||
|
p.tracksMutex.RLock()
|
||||||
|
defer p.tracksMutex.RUnlock()
|
||||||
|
return len(p.tracks)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *PlayQueueList) onArtistTapped(artistID string) {
|
func (t *PlayQueueList) onArtistTapped(artistID string) {
|
||||||
if t.OnShowArtistPage != nil {
|
if t.OnShowArtistPage != nil {
|
||||||
t.OnShowArtistPage(artistID)
|
t.OnShowArtistPage(artistID)
|
||||||
@@ -55,26 +139,37 @@ func (p *PlayQueueList) onSelectTrack(idx int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlayQueueList) selectTrack(idx int) {
|
func (p *PlayQueueList) selectTrack(idx int) {
|
||||||
p.tracksMutex.Lock()
|
p.tracksMutex.RLock()
|
||||||
defer p.tracksMutex.Unlock()
|
defer p.tracksMutex.RUnlock()
|
||||||
util.SelectTrack(p.tracks, idx)
|
util.SelectTrack(p.tracks, idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlayQueueList) selectAddOrRemove(idx int) {
|
func (p *PlayQueueList) selectAddOrRemove(idx int) {
|
||||||
p.tracksMutex.Lock()
|
p.tracksMutex.RLock()
|
||||||
defer p.tracksMutex.Unlock()
|
defer p.tracksMutex.RUnlock()
|
||||||
p.tracks[idx].Selected = !p.tracks[idx].Selected
|
p.tracks[idx].Selected = !p.tracks[idx].Selected
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlayQueueList) selectRange(idx int) {
|
func (p *PlayQueueList) selectRange(idx int) {
|
||||||
p.tracksMutex.Lock()
|
p.tracksMutex.RLock()
|
||||||
defer p.tracksMutex.Unlock()
|
defer p.tracksMutex.RUnlock()
|
||||||
util.SelectTrackRange(p.tracks, idx)
|
util.SelectTrackRange(p.tracks, idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PlayQueueList) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PlayQueueList) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
return widget.NewSimpleRenderer(p.list)
|
||||||
|
}
|
||||||
|
|
||||||
type PlayQueueListRow struct {
|
type PlayQueueListRow struct {
|
||||||
FocusListRowBase
|
FocusListRowBase
|
||||||
|
|
||||||
|
OnTappedSecondary func(e *fyne.PointEvent, trackIdx int)
|
||||||
|
|
||||||
|
imageLoader util.ThumbnailLoader
|
||||||
playQueueList *PlayQueueList
|
playQueueList *PlayQueueList
|
||||||
trackID string
|
trackID string
|
||||||
isPlaying bool
|
isPlaying bool
|
||||||
@@ -87,16 +182,18 @@ type PlayQueueListRow struct {
|
|||||||
time *widget.Label
|
time *widget.Label
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlayQueueListRow(playQueueList *PlayQueueList, playingIcon fyne.CanvasObject) *PlayQueueListRow {
|
func NewPlayQueueListRow(playQueueList *PlayQueueList, im *backend.ImageManager, playingIcon fyne.CanvasObject) *PlayQueueListRow {
|
||||||
p := &PlayQueueListRow{
|
p := &PlayQueueListRow{
|
||||||
playingIcon: playingIcon,
|
playingIcon: playingIcon,
|
||||||
playQueueList: playQueueList,
|
playQueueList: playQueueList,
|
||||||
num: widget.NewLabel(""),
|
num: widget.NewLabel(""),
|
||||||
cover: NewImagePlaceholder(theme.TracksIcon, 64),
|
cover: NewImagePlaceholder(myTheme.TracksIcon, thumbnailSize),
|
||||||
title: util.NewTruncatingLabel(),
|
title: util.NewTruncatingLabel(),
|
||||||
artist: NewMultiHyperlink(),
|
artist: NewMultiHyperlink(),
|
||||||
time: util.NewTrailingAlignLabel(),
|
time: util.NewTrailingAlignLabel(),
|
||||||
}
|
}
|
||||||
|
p.ExtendBaseWidget(p)
|
||||||
|
|
||||||
p.artist.OnTapped = playQueueList.onArtistTapped
|
p.artist.OnTapped = playQueueList.onArtistTapped
|
||||||
p.OnDoubleTapped = func() {
|
p.OnDoubleTapped = func() {
|
||||||
playQueueList.onPlayTrackAt(p.ItemID())
|
playQueueList.onPlayTrackAt(p.ItemID())
|
||||||
@@ -104,8 +201,18 @@ func NewPlayQueueListRow(playQueueList *PlayQueueList, playingIcon fyne.CanvasOb
|
|||||||
p.OnTapped = func() {
|
p.OnTapped = func() {
|
||||||
playQueueList.onSelectTrack(p.ItemID())
|
playQueueList.onSelectTrack(p.ItemID())
|
||||||
}
|
}
|
||||||
//p.title.TextStyle.Bold = true
|
p.OnTappedSecondary = playQueueList.onShowContextMenu
|
||||||
p.ExtendBaseWidget(p)
|
p.OnFocusNeighbor = func(up bool) {
|
||||||
|
playQueueList.list.FocusNeighbor(p.ItemID(), up)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.imageLoader = util.NewThumbnailLoader(im, func(i image.Image) {
|
||||||
|
p.cover.SetImage(i, false)
|
||||||
|
})
|
||||||
|
p.imageLoader.OnBeforeLoad = func() {
|
||||||
|
p.cover.SetImage(nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
p.Content = container.New(playQueueList.colLayout,
|
p.Content = container.New(playQueueList.colLayout,
|
||||||
container.NewCenter(p.num),
|
container.NewCenter(p.num),
|
||||||
p.cover,
|
p.cover,
|
||||||
@@ -116,6 +223,12 @@ func NewPlayQueueListRow(playQueueList *PlayQueueList, playingIcon fyne.CanvasOb
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PlayQueueListRow) TappedSecondary(e *fyne.PointEvent) {
|
||||||
|
if p.OnTappedSecondary != nil {
|
||||||
|
p.OnTappedSecondary(e, p.ListItemID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) {
|
func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) {
|
||||||
changed := false
|
changed := false
|
||||||
if tm.Selected != p.Selected {
|
if tm.Selected != p.Selected {
|
||||||
@@ -123,12 +236,19 @@ func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) {
|
|||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if num := strconv.Itoa(rowNum); p.num.Text != num {
|
||||||
|
p.num.Text = num
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
// Update info that can change if this row is bound to
|
// Update info that can change if this row is bound to
|
||||||
// a new track (*mediaprovider.Track)
|
// a new track (*mediaprovider.Track)
|
||||||
tr := tm.Track
|
tr := tm.Track
|
||||||
if tr.ID != p.trackID {
|
if tr.ID != p.trackID {
|
||||||
|
p.imageLoader.Load(tm.Track.CoverArtID)
|
||||||
p.EnsureUnfocused()
|
p.EnsureUnfocused()
|
||||||
p.trackID = tr.ID
|
p.trackID = tr.ID
|
||||||
|
p.title.Text = tr.Name
|
||||||
p.artist.BuildSegments(tr.ArtistNames, tr.ArtistIDs)
|
p.artist.BuildSegments(tr.ArtistNames, tr.ArtistIDs)
|
||||||
p.time.Text = util.SecondsToTimeString(float64(tr.Duration))
|
p.time.Text = util.SecondsToTimeString(float64(tr.Duration))
|
||||||
changed = true
|
changed = true
|
||||||
@@ -142,7 +262,7 @@ func (p *PlayQueueListRow) Update(tm *util.TrackListModel, rowNum int) {
|
|||||||
if isPlaying {
|
if isPlaying {
|
||||||
p.Content.(*fyne.Container).Objects[0] = p.playingIcon
|
p.Content.(*fyne.Container).Objects[0] = p.playingIcon
|
||||||
} else {
|
} else {
|
||||||
p.Content.(*fyne.Container).Objects[0] = p.num
|
p.Content.(*fyne.Container).Objects[0] = container.NewCenter(p.num)
|
||||||
}
|
}
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-15
@@ -279,8 +279,8 @@ func (t *Tracklist) SetSorting(sorting TracklistSort) {
|
|||||||
func (t *Tracklist) SetNowPlaying(trackID string) {
|
func (t *Tracklist) SetNowPlaying(trackID string) {
|
||||||
prevNowPlaying := t.nowPlayingID
|
prevNowPlaying := t.nowPlayingID
|
||||||
t.tracksMutex.RLock()
|
t.tracksMutex.RLock()
|
||||||
trPrev, idxPrev := t.findTrackByID(prevNowPlaying)
|
trPrev, idxPrev := util.FindTrackByID(t.tracks, prevNowPlaying)
|
||||||
tr, idx := t.findTrackByID(trackID)
|
tr, idx := util.FindTrackByID(t.tracks, trackID)
|
||||||
t.tracksMutex.RUnlock()
|
t.tracksMutex.RUnlock()
|
||||||
t.nowPlayingID = trackID
|
t.nowPlayingID = trackID
|
||||||
if trPrev != nil {
|
if trPrev != nil {
|
||||||
@@ -294,7 +294,7 @@ func (t *Tracklist) SetNowPlaying(trackID string) {
|
|||||||
// Increments the play count of the given track and updates the list rendering
|
// Increments the play count of the given track and updates the list rendering
|
||||||
func (t *Tracklist) IncrementPlayCount(trackID string) {
|
func (t *Tracklist) IncrementPlayCount(trackID string) {
|
||||||
t.tracksMutex.RLock()
|
t.tracksMutex.RLock()
|
||||||
tr, idx := t.findTrackByID(trackID)
|
tr, idx := util.FindTrackByID(t.tracks, trackID)
|
||||||
t.tracksMutex.RUnlock()
|
t.tracksMutex.RUnlock()
|
||||||
if tr != nil {
|
if tr != nil {
|
||||||
tr.PlayCount += 1
|
tr.PlayCount += 1
|
||||||
@@ -572,7 +572,7 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
|
|||||||
|
|
||||||
func (t *Tracklist) onSetFavorite(trackID string, fav bool) {
|
func (t *Tracklist) onSetFavorite(trackID string, fav bool) {
|
||||||
t.tracksMutex.RLock()
|
t.tracksMutex.RLock()
|
||||||
tr, _ := t.findTrackByID(trackID)
|
tr, _ := util.FindTrackByID(t.tracks, trackID)
|
||||||
t.tracksMutex.RUnlock()
|
t.tracksMutex.RUnlock()
|
||||||
t.onSetFavorites([]*mediaprovider.Track{tr}, fav, false)
|
t.onSetFavorites([]*mediaprovider.Track{tr}, fav, false)
|
||||||
}
|
}
|
||||||
@@ -593,7 +593,7 @@ func (t *Tracklist) onSetFavorites(tracks []*mediaprovider.Track, fav bool, need
|
|||||||
func (t *Tracklist) onSetRating(trackID string, rating int) {
|
func (t *Tracklist) onSetRating(trackID string, rating int) {
|
||||||
// update our own track model
|
// update our own track model
|
||||||
t.tracksMutex.RLock()
|
t.tracksMutex.RLock()
|
||||||
tr, _ := t.findTrackByID(trackID)
|
tr, _ := util.FindTrackByID(t.tracks, trackID)
|
||||||
t.tracksMutex.RUnlock()
|
t.tracksMutex.RUnlock()
|
||||||
t.onSetRatings([]*mediaprovider.Track{tr}, rating, false)
|
t.onSetRatings([]*mediaprovider.Track{tr}, rating, false)
|
||||||
}
|
}
|
||||||
@@ -629,16 +629,6 @@ func (t *Tracklist) onDownload(tracks []*mediaprovider.Track, downloadName strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tracklist) findTrackByID(id string) (*mediaprovider.Track, int) {
|
|
||||||
idx := sharedutil.Find(t.tracks, func(tr *util.TrackListModel) bool {
|
|
||||||
return tr.Track.ID == id
|
|
||||||
})
|
|
||||||
if idx >= 0 {
|
|
||||||
return t.tracks[idx].Track, idx
|
|
||||||
}
|
|
||||||
return nil, -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tracklist) selectedTrackModels() []*util.TrackListModel {
|
func (t *Tracklist) selectedTrackModels() []*util.TrackListModel {
|
||||||
t.tracksMutex.RLock()
|
t.tracksMutex.RLock()
|
||||||
defer t.tracksMutex.RUnlock()
|
defer t.tracksMutex.RUnlock()
|
||||||
|
|||||||
Reference in New Issue
Block a user