Merge branch 'main' into feature/shuffle
This commit is contained in:
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/dweymouth/supersonic/sharedutil"
|
"github.com/dweymouth/supersonic/sharedutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) ArtistSortOrders() []string {
|
func (j *JellyfinMediaProvider) ArtistSortOrders() []string {
|
||||||
return []string{
|
return []string{
|
||||||
mediaprovider.ArtistSortAlbumCount,
|
mediaprovider.ArtistSortAlbumCount,
|
||||||
mediaprovider.ArtistSortNameAZ,
|
mediaprovider.ArtistSortNameAZ,
|
||||||
@@ -17,7 +17,7 @@ func (j *jellyfinMediaProvider) ArtistSortOrders() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) IterateArtists(sortOrder string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
|
func (j *JellyfinMediaProvider) IterateArtists(sortOrder string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
|
||||||
var jfSort jellyfin.Sort
|
var jfSort jellyfin.Sort
|
||||||
var disablePagination bool
|
var disablePagination bool
|
||||||
var sortFn func([]*jellyfin.Artist) []*jellyfin.Artist
|
var sortFn func([]*jellyfin.Artist) []*jellyfin.Artist
|
||||||
@@ -63,7 +63,7 @@ func (j *jellyfinMediaProvider) IterateArtists(sortOrder string, filter mediapro
|
|||||||
return helpers.NewArtistIterator(fetcher, filter, j.prefetchCoverCB)
|
return helpers.NewArtistIterator(fetcher, filter, j.prefetchCoverCB)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) SearchArtists(searchQuery string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
|
func (j *JellyfinMediaProvider) SearchArtists(searchQuery string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
|
||||||
// TODO: Jellyfin API is not returning search results for artists.
|
// TODO: Jellyfin API is not returning search results for artists.
|
||||||
// Uncomment the following code once the issue is resolved.
|
// Uncomment the following code once the issue is resolved.
|
||||||
// Related issue: https://github.com/jellyfin/jellyfin/issues/8222
|
// Related issue: https://github.com/jellyfin/jellyfin/issues/8222
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/dweymouth/supersonic/sharedutil"
|
"github.com/dweymouth/supersonic/sharedutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) AlbumSortOrders() []string {
|
func (j *JellyfinMediaProvider) AlbumSortOrders() []string {
|
||||||
return []string{
|
return []string{
|
||||||
mediaprovider.AlbumSortRecentlyAdded,
|
mediaprovider.AlbumSortRecentlyAdded,
|
||||||
mediaprovider.AlbumSortRandom,
|
mediaprovider.AlbumSortRandom,
|
||||||
@@ -20,7 +20,7 @@ func (j *jellyfinMediaProvider) AlbumSortOrders() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) IterateAlbums(sortOrder string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator {
|
func (j *JellyfinMediaProvider) IterateAlbums(sortOrder string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator {
|
||||||
var jfSort jellyfin.Sort
|
var jfSort jellyfin.Sort
|
||||||
switch sortOrder {
|
switch sortOrder {
|
||||||
case mediaprovider.AlbumSortRecentlyAdded:
|
case mediaprovider.AlbumSortRecentlyAdded:
|
||||||
@@ -75,7 +75,7 @@ func (j *jellyfinMediaProvider) IterateAlbums(sortOrder string, filter mediaprov
|
|||||||
return helpers.NewAlbumIterator(fetcher, modifiedFilter, j.prefetchCoverCB)
|
return helpers.NewAlbumIterator(fetcher, modifiedFilter, j.prefetchCoverCB)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) SearchAlbums(searchQuery string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator {
|
func (j *JellyfinMediaProvider) SearchAlbums(searchQuery string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator {
|
||||||
fetcher := func(offs, limit int) ([]*mediaprovider.Album, error) {
|
fetcher := func(offs, limit int) ([]*mediaprovider.Album, error) {
|
||||||
var opts jellyfin.QueryOpts
|
var opts jellyfin.QueryOpts
|
||||||
opts.Paging = jellyfin.Paging{StartIndex: offs, Limit: limit}
|
opts.Paging = jellyfin.Paging{StartIndex: offs, Limit: limit}
|
||||||
@@ -89,7 +89,7 @@ func (j *jellyfinMediaProvider) SearchAlbums(searchQuery string, filter mediapro
|
|||||||
return helpers.NewAlbumIterator(fetcher, filter, j.prefetchCoverCB)
|
return helpers.NewAlbumIterator(fetcher, filter, j.prefetchCoverCB)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) IterateTracks(searchQuery string) mediaprovider.TrackIterator {
|
func (j *JellyfinMediaProvider) IterateTracks(searchQuery string) mediaprovider.TrackIterator {
|
||||||
var fetcher helpers.TrackFetchFn
|
var fetcher helpers.TrackFetchFn
|
||||||
if searchQuery == "" {
|
if searchQuery == "" {
|
||||||
fetcher = func(offs, limit int) ([]*mediaprovider.Track, error) {
|
fetcher = func(offs, limit int) ([]*mediaprovider.Track, error) {
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ func (j *JellyfinServer) MediaProvider() mediaprovider.MediaProvider {
|
|||||||
return newJellyfinMediaProvider(&j.Client)
|
return newJellyfinMediaProvider(&j.Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ mediaprovider.MediaProvider = (*jellyfinMediaProvider)(nil)
|
var _ mediaprovider.MediaProvider = (*JellyfinMediaProvider)(nil)
|
||||||
|
|
||||||
type jellyfinMediaProvider struct {
|
type JellyfinMediaProvider struct {
|
||||||
client *jellyfin.Client
|
client *jellyfin.Client
|
||||||
prefetchCoverCB func(coverArtID string)
|
prefetchCoverCB func(coverArtID string)
|
||||||
|
|
||||||
@@ -51,17 +51,17 @@ type jellyfinMediaProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newJellyfinMediaProvider(cli *jellyfin.Client) mediaprovider.MediaProvider {
|
func newJellyfinMediaProvider(cli *jellyfin.Client) mediaprovider.MediaProvider {
|
||||||
return &jellyfinMediaProvider{
|
return &JellyfinMediaProvider{
|
||||||
client: cli,
|
client: cli,
|
||||||
genresCached: make([]*mediaprovider.Genre, 0),
|
genresCached: make([]*mediaprovider.Genre, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) SetPrefetchCoverCallback(cb func(coverArtID string)) {
|
func (j *JellyfinMediaProvider) SetPrefetchCoverCallback(cb func(coverArtID string)) {
|
||||||
j.prefetchCoverCB = cb
|
j.prefetchCoverCB = cb
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetLibraries() ([]mediaprovider.Library, error) {
|
func (j *JellyfinMediaProvider) GetLibraries() ([]mediaprovider.Library, error) {
|
||||||
v, err := j.client.GetUserViews()
|
v, err := j.client.GetUserViews()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -71,41 +71,41 @@ func (j *jellyfinMediaProvider) GetLibraries() ([]mediaprovider.Library, error)
|
|||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) SetLibrary(id string) error {
|
func (j *JellyfinMediaProvider) SetLibrary(id string) error {
|
||||||
j.currentLibraryID = id
|
j.currentLibraryID = id
|
||||||
j.genresCached = nil
|
j.genresCached = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) CreatePlaylistWithTracks(name string, trackIDs []string) error {
|
func (j *JellyfinMediaProvider) CreatePlaylistWithTracks(name string, trackIDs []string) error {
|
||||||
return j.client.CreatePlaylist(name, "", false, trackIDs)
|
return j.client.CreatePlaylist(name, "", false, trackIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) DeletePlaylist(id string) error {
|
func (j *JellyfinMediaProvider) DeletePlaylist(id string) error {
|
||||||
return j.client.DeletePlaylist(id)
|
return j.client.DeletePlaylist(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) CanMakePublicPlaylist() bool {
|
func (j *JellyfinMediaProvider) CanMakePublicPlaylist() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) EditPlaylist(id, name, description string, public bool) error {
|
func (j *JellyfinMediaProvider) EditPlaylist(id, name, description string, public bool) error {
|
||||||
return j.client.UpdatePlaylistMetadata(id, name, description, false)
|
return j.client.UpdatePlaylistMetadata(id, name, description, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) CreatePlaylist(name, description string, public bool) error {
|
func (j *JellyfinMediaProvider) CreatePlaylist(name, description string, public bool) error {
|
||||||
return j.client.CreatePlaylist(name, description, public, nil)
|
return j.client.CreatePlaylist(name, description, public, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) AddPlaylistTracks(id string, trackIDsToAdd []string) error {
|
func (j *JellyfinMediaProvider) AddPlaylistTracks(id string, trackIDsToAdd []string) error {
|
||||||
return j.client.AddSongsToPlaylist(id, trackIDsToAdd)
|
return j.client.AddSongsToPlaylist(id, trackIDsToAdd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) RemovePlaylistTracks(playlistID string, removeIdxs []int) error {
|
func (j *JellyfinMediaProvider) RemovePlaylistTracks(playlistID string, removeIdxs []int) error {
|
||||||
return j.client.RemoveSongsFromPlaylist(playlistID, removeIdxs)
|
return j.client.RemoveSongsFromPlaylist(playlistID, removeIdxs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) ReplacePlaylistTracks(playlistID string, trackIDs []string) error {
|
func (j *JellyfinMediaProvider) ReplacePlaylistTracks(playlistID string, trackIDs []string) error {
|
||||||
pl, err := j.client.GetPlaylist(playlistID)
|
pl, err := j.client.GetPlaylist(playlistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -120,7 +120,7 @@ func (j *jellyfinMediaProvider) ReplacePlaylistTracks(playlistID string, trackID
|
|||||||
return j.client.AddSongsToPlaylist(playlistID, trackIDs)
|
return j.client.AddSongsToPlaylist(playlistID, trackIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetAlbum(albumID string) (*mediaprovider.AlbumWithTracks, error) {
|
func (j *JellyfinMediaProvider) GetAlbum(albumID string) (*mediaprovider.AlbumWithTracks, error) {
|
||||||
al, err := j.client.GetAlbum(albumID)
|
al, err := j.client.GetAlbum(albumID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -138,7 +138,7 @@ func (j *jellyfinMediaProvider) GetAlbum(albumID string) (*mediaprovider.AlbumWi
|
|||||||
return album, nil
|
return album, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetAlbumInfo(albumID string) (*mediaprovider.AlbumInfo, error) {
|
func (j *JellyfinMediaProvider) GetAlbumInfo(albumID string) (*mediaprovider.AlbumInfo, error) {
|
||||||
al, err := j.client.GetAlbum(albumID)
|
al, err := j.client.GetAlbum(albumID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -148,7 +148,7 @@ func (j *jellyfinMediaProvider) GetAlbumInfo(albumID string) (*mediaprovider.Alb
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetArtist(artistID string) (*mediaprovider.ArtistWithAlbums, error) {
|
func (j *JellyfinMediaProvider) GetArtist(artistID string) (*mediaprovider.ArtistWithAlbums, error) {
|
||||||
ar, err := j.client.GetArtist(artistID)
|
ar, err := j.client.GetArtist(artistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -167,11 +167,11 @@ func (j *jellyfinMediaProvider) GetArtist(artistID string) (*mediaprovider.Artis
|
|||||||
return artist, nil
|
return artist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetArtistTracks(artistID string) ([]*mediaprovider.Track, error) {
|
func (j *JellyfinMediaProvider) GetArtistTracks(artistID string) ([]*mediaprovider.Track, error) {
|
||||||
return helpers.GetArtistTracks(j, artistID)
|
return helpers.GetArtistTracks(j, artistID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetArtistInfo(artistID string) (*mediaprovider.ArtistInfo, error) {
|
func (j *JellyfinMediaProvider) GetArtistInfo(artistID string) (*mediaprovider.ArtistInfo, error) {
|
||||||
ar, err := j.client.GetArtist(artistID)
|
ar, err := j.client.GetArtist(artistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -186,7 +186,7 @@ func (j *jellyfinMediaProvider) GetArtistInfo(artistID string) (*mediaprovider.A
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetTrack(trackID string) (*mediaprovider.Track, error) {
|
func (j *JellyfinMediaProvider) GetTrack(trackID string) (*mediaprovider.Track, error) {
|
||||||
tr, err := j.client.GetSong(trackID)
|
tr, err := j.client.GetSong(trackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -194,7 +194,7 @@ func (j *jellyfinMediaProvider) GetTrack(trackID string) (*mediaprovider.Track,
|
|||||||
return toTrack(tr), nil
|
return toTrack(tr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetTopTracks(artist mediaprovider.Artist, limit int) ([]*mediaprovider.Track, error) {
|
func (j *JellyfinMediaProvider) GetTopTracks(artist mediaprovider.Artist, limit int) ([]*mediaprovider.Track, error) {
|
||||||
var opts jellyfin.QueryOpts
|
var opts jellyfin.QueryOpts
|
||||||
opts.Paging.Limit = limit
|
opts.Paging.Limit = limit
|
||||||
opts.Filter.ArtistID = artist.ID
|
opts.Filter.ArtistID = artist.ID
|
||||||
@@ -213,7 +213,7 @@ func (j *jellyfinMediaProvider) GetTopTracks(artist mediaprovider.Artist, limit
|
|||||||
return sharedutil.MapSlice(tr, toTrack), nil
|
return sharedutil.MapSlice(tr, toTrack), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetRandomTracks(genreName string, limit int) ([]*mediaprovider.Track, error) {
|
func (j *JellyfinMediaProvider) GetRandomTracks(genreName string, limit int) ([]*mediaprovider.Track, error) {
|
||||||
var opts jellyfin.QueryOpts
|
var opts jellyfin.QueryOpts
|
||||||
opts.Paging.Limit = limit
|
opts.Paging.Limit = limit
|
||||||
opts.Filter.Genres = []string{genreName}
|
opts.Filter.Genres = []string{genreName}
|
||||||
@@ -228,7 +228,7 @@ func (j *jellyfinMediaProvider) GetRandomTracks(genreName string, limit int) ([]
|
|||||||
return sharedutil.MapSlice(tr, toTrack), nil
|
return sharedutil.MapSlice(tr, toTrack), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetSimilarTracks(artistID string, limit int) ([]*mediaprovider.Track, error) {
|
func (j *JellyfinMediaProvider) GetSimilarTracks(artistID string, limit int) ([]*mediaprovider.Track, error) {
|
||||||
tr, err := j.client.GetInstantMix(artistID, jellyfin.TypeArtist, limit)
|
tr, err := j.client.GetInstantMix(artistID, jellyfin.TypeArtist, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -236,11 +236,11 @@ func (j *jellyfinMediaProvider) GetSimilarTracks(artistID string, limit int) ([]
|
|||||||
return sharedutil.MapSlice(tr, toTrack), nil
|
return sharedutil.MapSlice(tr, toTrack), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetCoverArt(id string, size int) (image.Image, error) {
|
func (j *JellyfinMediaProvider) GetCoverArt(id string, size int) (image.Image, error) {
|
||||||
return j.client.GetItemImage(id, "Primary", size, 92)
|
return j.client.GetItemImage(id, "Primary", size, 92)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetFavorites() (mediaprovider.Favorites, error) {
|
func (j *JellyfinMediaProvider) GetFavorites() (mediaprovider.Favorites, error) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var favorites mediaprovider.Favorites
|
var favorites mediaprovider.Favorites
|
||||||
|
|
||||||
@@ -278,7 +278,7 @@ func (j *jellyfinMediaProvider) GetFavorites() (mediaprovider.Favorites, error)
|
|||||||
return favorites, nil
|
return favorites, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetGenres() ([]*mediaprovider.Genre, error) {
|
func (j *JellyfinMediaProvider) GetGenres() ([]*mediaprovider.Genre, error) {
|
||||||
if j.genresCached != nil && time.Now().Unix()-j.genresCachedAt < cacheValidDurationSeconds {
|
if j.genresCached != nil && time.Now().Unix()-j.genresCachedAt < cacheValidDurationSeconds {
|
||||||
return j.genresCached, nil
|
return j.genresCached, nil
|
||||||
}
|
}
|
||||||
@@ -298,7 +298,7 @@ func (j *jellyfinMediaProvider) GetGenres() ([]*mediaprovider.Genre, error) {
|
|||||||
return j.genresCached, nil
|
return j.genresCached, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error) {
|
func (j *JellyfinMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error) {
|
||||||
pl, err := j.client.GetPlaylists()
|
pl, err := j.client.GetPlaylists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -306,7 +306,7 @@ func (j *jellyfinMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error
|
|||||||
return sharedutil.MapSlice(pl, j.toPlaylist), nil
|
return sharedutil.MapSlice(pl, j.toPlaylist), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.PlaylistWithTracks, error) {
|
func (j *JellyfinMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.PlaylistWithTracks, error) {
|
||||||
tr, err := j.client.GetPlaylistSongs(playlistID)
|
tr, err := j.client.GetPlaylistSongs(playlistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -323,7 +323,7 @@ func (j *jellyfinMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.P
|
|||||||
return playlist, nil
|
return playlist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) SetFavorite(params mediaprovider.RatingFavoriteParameters, favorite bool) error {
|
func (j *JellyfinMediaProvider) SetFavorite(params mediaprovider.RatingFavoriteParameters, favorite bool) error {
|
||||||
var allIDs []string
|
var allIDs []string
|
||||||
allIDs = append(allIDs, params.AlbumIDs...)
|
allIDs = append(allIDs, params.AlbumIDs...)
|
||||||
allIDs = append(allIDs, params.ArtistIDs...)
|
allIDs = append(allIDs, params.ArtistIDs...)
|
||||||
@@ -357,7 +357,7 @@ func (j *jellyfinMediaProvider) SetFavorite(params mediaprovider.RatingFavoriteP
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetStreamURL(trackID string, transcode *mediaprovider.TranscodeSettings, forceRaw bool) (string, error) {
|
func (j *JellyfinMediaProvider) GetStreamURL(trackID string, transcode *mediaprovider.TranscodeSettings, forceRaw bool) (string, error) {
|
||||||
var jfTranscode *jellyfin.TranscodeOptions
|
var jfTranscode *jellyfin.TranscodeOptions
|
||||||
if transcode != nil {
|
if transcode != nil {
|
||||||
jfTranscode = &jellyfin.TranscodeOptions{
|
jfTranscode = &jellyfin.TranscodeOptions{
|
||||||
@@ -371,7 +371,7 @@ func (j *jellyfinMediaProvider) GetStreamURL(trackID string, transcode *mediapro
|
|||||||
return j.client.GetStreamURL(trackID, jfTranscode)
|
return j.client.GetStreamURL(trackID, jfTranscode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) DownloadTrack(trackID string) (io.Reader, error) {
|
func (j *JellyfinMediaProvider) DownloadTrack(trackID string) (io.Reader, error) {
|
||||||
url, err := j.client.GetStreamURL(trackID, nil)
|
url, err := j.client.GetStreamURL(trackID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -383,23 +383,23 @@ func (j *jellyfinMediaProvider) DownloadTrack(trackID string) (io.Reader, error)
|
|||||||
return resp.Body, nil
|
return resp.Body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) ClientDecidesScrobble() bool { return false }
|
func (j *JellyfinMediaProvider) ClientDecidesScrobble() bool { return false }
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) TrackBeganPlayback(trackID string) error {
|
func (j *JellyfinMediaProvider) TrackBeganPlayback(trackID string) error {
|
||||||
return j.client.UpdatePlayStatus(trackID, jellyfin.Start, 0)
|
return j.client.UpdatePlayStatus(trackID, jellyfin.Start, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) TrackEndedPlayback(trackID string, position int, submission bool) error {
|
func (j *JellyfinMediaProvider) TrackEndedPlayback(trackID string, position int, submission bool) error {
|
||||||
return j.client.UpdatePlayStatus(trackID, jellyfin.Stop, int64(position)*runTimeTicksPerMicrosecond*1_000_000)
|
return j.client.UpdatePlayStatus(trackID, jellyfin.Stop, int64(position)*runTimeTicksPerMicrosecond*1_000_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) RescanLibrary() error {
|
func (j *JellyfinMediaProvider) RescanLibrary() error {
|
||||||
return j.client.RefreshLibrary()
|
return j.client.RefreshLibrary()
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ mediaprovider.LyricsProvider = (*jellyfinMediaProvider)(nil)
|
var _ mediaprovider.LyricsProvider = (*JellyfinMediaProvider)(nil)
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetLyrics(tr *mediaprovider.Track) (*mediaprovider.Lyrics, error) {
|
func (j *JellyfinMediaProvider) GetLyrics(tr *mediaprovider.Track) (*mediaprovider.Lyrics, error) {
|
||||||
l, err := j.client.GetLyrics(tr.ID)
|
l, err := j.client.GetLyrics(tr.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -515,13 +515,13 @@ func fillAlbum(a *jellyfin.Album, album *mediaprovider.Album) {
|
|||||||
album.ReleaseTypes = mediaprovider.ReleaseTypeAlbum
|
album.ReleaseTypes = mediaprovider.ReleaseTypeAlbum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) toPlaylist(p *jellyfin.Playlist) *mediaprovider.Playlist {
|
func (j *JellyfinMediaProvider) toPlaylist(p *jellyfin.Playlist) *mediaprovider.Playlist {
|
||||||
pl := &mediaprovider.Playlist{}
|
pl := &mediaprovider.Playlist{}
|
||||||
j.fillPlaylist(p, pl)
|
j.fillPlaylist(p, pl)
|
||||||
return pl
|
return pl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) fillPlaylist(p *jellyfin.Playlist, pl *mediaprovider.Playlist) {
|
func (j *JellyfinMediaProvider) fillPlaylist(p *jellyfin.Playlist, pl *mediaprovider.Playlist) {
|
||||||
pl.Name = p.Name
|
pl.Name = p.Name
|
||||||
pl.ID = p.ID
|
pl.ID = p.ID
|
||||||
pl.CoverArtID = p.ID
|
pl.CoverArtID = p.ID
|
||||||
@@ -533,7 +533,7 @@ func (j *jellyfinMediaProvider) fillPlaylist(p *jellyfin.Playlist, pl *mediaprov
|
|||||||
pl.Public = false
|
pl.Public = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) GetSongRadio(trackID string, count int) ([]*mediaprovider.Track, error) {
|
func (j *JellyfinMediaProvider) GetSongRadio(trackID string, count int) ([]*mediaprovider.Track, error) {
|
||||||
tr, err := j.client.GetInstantMix(trackID, jellyfin.TypeSong, count)
|
tr, err := j.client.GetInstantMix(trackID, jellyfin.TypeSong, count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/dweymouth/supersonic/sharedutil"
|
"github.com/dweymouth/supersonic/sharedutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) SearchAll(searchQuery string, maxResults int) ([]*mediaprovider.SearchResult, error) {
|
func (j *JellyfinMediaProvider) SearchAll(searchQuery string, maxResults int) ([]*mediaprovider.SearchResult, error) {
|
||||||
limit := maxResults / 3
|
limit := maxResults / 3
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var albums []*jellyfin.Album
|
var albums []*jellyfin.Album
|
||||||
@@ -75,7 +75,7 @@ func (j *jellyfinMediaProvider) SearchAll(searchQuery string, maxResults int) ([
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *jellyfinMediaProvider) mergeResults(
|
func (j *JellyfinMediaProvider) mergeResults(
|
||||||
albums []*jellyfin.Album,
|
albums []*jellyfin.Album,
|
||||||
artists []*jellyfin.Artist,
|
artists []*jellyfin.Artist,
|
||||||
songs []*jellyfin.Song,
|
songs []*jellyfin.Song,
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ type PlaybackManager struct {
|
|||||||
lastPlayingID string
|
lastPlayingID string
|
||||||
wfmUpdateImageCancel context.CancelFunc
|
wfmUpdateImageCancel context.CancelFunc
|
||||||
wfmImageJobs [3]*WaveformImageJob
|
wfmImageJobs [3]*WaveformImageJob
|
||||||
|
|
||||||
|
// whether autoplay tracks are currently being fetched/enqueued
|
||||||
|
pendingAutoplay bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemotePlaybackDevice struct {
|
type RemotePlaybackDevice struct {
|
||||||
@@ -100,8 +103,14 @@ func (p *PlaybackManager) addOnTrackChangeHook() {
|
|||||||
// On Windows, MPV sometimes fails to start playback when switching to a track
|
// On Windows, MPV sometimes fails to start playback when switching to a track
|
||||||
// with a different sample rate than the previous. If this is detected,
|
// with a different sample rate than the previous. If this is detected,
|
||||||
// send a command to the MPV player to force restart playback.
|
// send a command to the MPV player to force restart playback.
|
||||||
p.OnPlayTimeUpdate(func(curTime, _ float64, _ bool) {
|
p.OnPlayTimeUpdate(func(curTime, totalTime float64, _ bool) {
|
||||||
p.lastPlayTime = curTime
|
p.lastPlayTime = curTime
|
||||||
|
|
||||||
|
// enqueue autoplay tracks if enabled and nearing end of queue
|
||||||
|
if p.cfg.Autoplay && !p.pendingAutoplay && totalTime-curTime < 10.0 &&
|
||||||
|
p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
||||||
|
p.enqueueAutoplayTracks()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
p.engine.onBeforeSongChange = append(p.engine.onBeforeSongChange, func(item mediaprovider.MediaItem) {
|
p.engine.onBeforeSongChange = append(p.engine.onBeforeSongChange, func(item mediaprovider.MediaItem) {
|
||||||
@@ -116,10 +125,6 @@ func (p *PlaybackManager) addOnTrackChangeHook() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
||||||
// Autoplay if enabled and we are on the last track
|
|
||||||
if p.cfg.Autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
|
||||||
p.enqueueAutoplayTracks()
|
|
||||||
}
|
|
||||||
p.handleWaveformImageSongChange(item)
|
p.handleWaveformImageSongChange(item)
|
||||||
|
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
@@ -737,7 +742,10 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
|||||||
|
|
||||||
// since this func is invoked in a callback from the playback engine,
|
// since this func is invoked in a callback from the playback engine,
|
||||||
// need to do the rest async as it may take time and block other callbacks
|
// need to do the rest async as it may take time and block other callbacks
|
||||||
|
p.pendingAutoplay = true
|
||||||
go func() {
|
go func() {
|
||||||
|
defer func() { p.pendingAutoplay = false }()
|
||||||
|
|
||||||
// first 2 strategies - similar by artist, and similar by genres - only work for tracks
|
// first 2 strategies - similar by artist, and similar by genres - only work for tracks
|
||||||
if nowPlaying.Metadata().Type == mediaprovider.MediaItemTypeTrack {
|
if nowPlaying.Metadata().Type == mediaprovider.MediaItemTypeTrack {
|
||||||
tr := nowPlaying.(*mediaprovider.Track)
|
tr := nowPlaying.(*mediaprovider.Track)
|
||||||
|
|||||||
@@ -33,8 +33,9 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
||||||
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 // indirect
|
fyne.io/systray v1.12.0 // indirect
|
||||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||||
|
github.com/FyshOS/fancyfs v0.0.0-20251025194026-1f03098ff624 // indirect
|
||||||
github.com/danieljoos/wincred v1.2.2 // indirect
|
github.com/danieljoos/wincred v1.2.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fredbi/uri v1.1.1 // indirect
|
github.com/fredbi/uri v1.1.1 // indirect
|
||||||
@@ -67,7 +68,7 @@ require (
|
|||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace fyne.io/fyne/v2 v2.7.1 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20251115180245-c1288f7754ab
|
replace fyne.io/fyne/v2 v2.7.1 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20260121163844-a0f2c1dd0601
|
||||||
|
|
||||||
replace github.com/go-audio/wav v1.1.0 => github.com/dweymouth/go-wav v0.0.0-20250719173115-e60429a83eb0
|
replace github.com/go-audio/wav v1.1.0 => github.com/dweymouth/go-wav v0.0.0-20250719173115-e60429a83eb0
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
|
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
|
||||||
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
|
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
|
||||||
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 h1:eA5/u2XRd8OUkoMqEv3IBlFYSruNlXD8bRHDiqm0VNI=
|
fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM=
|
||||||
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||||
github.com/20after4/configdir v0.1.1 h1:ylL5dO+aGxBV4jDtG9Ej9hZfeE8Fw9jULQVq0N+ErJ8=
|
github.com/20after4/configdir v0.1.1 h1:ylL5dO+aGxBV4jDtG9Ej9hZfeE8Fw9jULQVq0N+ErJ8=
|
||||||
github.com/20after4/configdir v0.1.1/go.mod h1:kZ7yOiD6MFUABqBI2/N62QA1wyEB8sGeI+mygme1pl4=
|
github.com/20after4/configdir v0.1.1/go.mod h1:kZ7yOiD6MFUABqBI2/N62QA1wyEB8sGeI+mygme1pl4=
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/FyshOS/fancyfs v0.0.0-20251025194026-1f03098ff624 h1:ryzrudl71BPWW9jalH/6qSXOmj/B96lunzJEi6zuuR4=
|
||||||
|
github.com/FyshOS/fancyfs v0.0.0-20251025194026-1f03098ff624/go.mod h1:oLKntpN0BPY75aajV735V/14CnSF/GEHCa6mKNDhOjw=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/boxes-ltd/imaging v1.7.1 h1:JlfPQkr9sowLuPY24OLNw1Wa0UjriPVo2BN0YyxdCf0=
|
github.com/boxes-ltd/imaging v1.7.1 h1:JlfPQkr9sowLuPY24OLNw1Wa0UjriPVo2BN0YyxdCf0=
|
||||||
@@ -25,8 +27,8 @@ github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c h1:UX
|
|||||||
github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c/go.mod h1:Idgzr4LYzve8IPHF1stLO1bdGQZnDKt/bT9WfJflCGw=
|
github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c/go.mod h1:Idgzr4LYzve8IPHF1stLO1bdGQZnDKt/bT9WfJflCGw=
|
||||||
github.com/dweymouth/fyne-tooltip v0.3.0 h1:NKCyTkh9NtvnTsiHtTOtaJzRDOFYP8AckQ2tyhOh6JY=
|
github.com/dweymouth/fyne-tooltip v0.3.0 h1:NKCyTkh9NtvnTsiHtTOtaJzRDOFYP8AckQ2tyhOh6JY=
|
||||||
github.com/dweymouth/fyne-tooltip v0.3.0/go.mod h1:m04ShLW/Tp6LXrNieTumApvNgo7YSB+wi+jZTN+kDBU=
|
github.com/dweymouth/fyne-tooltip v0.3.0/go.mod h1:m04ShLW/Tp6LXrNieTumApvNgo7YSB+wi+jZTN+kDBU=
|
||||||
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20251115180245-c1288f7754ab h1:3a3Dop0y5uGYSgjoyReYlwZqpo7xFQu+tULfZueCjEQ=
|
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20260121163844-a0f2c1dd0601 h1:Z75H0KinbkuXn0y/+vFB4R6jvwgwHkgCivvPT5fIYcc=
|
||||||
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20251115180245-c1288f7754ab/go.mod h1:xClVlrhxl7D+LT+BWYmcrW4Nf+dJTvkhnPgji7spAwE=
|
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20260121163844-a0f2c1dd0601/go.mod h1:M3Fkb8eUU6se8E+coikK8x7f3vCIakxAW7bPyw3d9fA=
|
||||||
github.com/dweymouth/go-jellyfin v0.0.0-20250928223159-bd2fb9681ef5 h1:Or5VJodg7cGmdnBIcS+FrEH0twBi7mZsFCz6V0vCOQI=
|
github.com/dweymouth/go-jellyfin v0.0.0-20250928223159-bd2fb9681ef5 h1:Or5VJodg7cGmdnBIcS+FrEH0twBi7mZsFCz6V0vCOQI=
|
||||||
github.com/dweymouth/go-jellyfin v0.0.0-20250928223159-bd2fb9681ef5/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI=
|
github.com/dweymouth/go-jellyfin v0.0.0-20250928223159-bd2fb9681ef5/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI=
|
||||||
github.com/dweymouth/go-wav v0.0.0-20250719173115-e60429a83eb0 h1:mYcctuWgVArHhSLJxndlUM43C3hoE18BLDBkXKM2tl0=
|
github.com/dweymouth/go-wav v0.0.0-20250719173115-e60429a83eb0 h1:mYcctuWgVArHhSLJxndlUM43C3hoE18BLDBkXKM2tl0=
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
"github.com/deluan/sanitize"
|
"github.com/deluan/sanitize"
|
||||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
|
"github.com/dweymouth/supersonic/backend/mediaprovider/jellyfin"
|
||||||
"github.com/dweymouth/supersonic/sharedutil"
|
"github.com/dweymouth/supersonic/sharedutil"
|
||||||
"github.com/dweymouth/supersonic/ui/util"
|
"github.com/dweymouth/supersonic/ui/util"
|
||||||
)
|
)
|
||||||
@@ -49,7 +50,13 @@ func (sp *SelectPlaylist) fetchUserOwnedPlaylists() {
|
|||||||
log.Printf("error getting playlists: %s", err.Error())
|
log.Printf("error getting playlists: %s", err.Error())
|
||||||
}
|
}
|
||||||
userPlaylists := sharedutil.FilterSlice(playlists, func(playlist *mediaprovider.Playlist) bool {
|
userPlaylists := sharedutil.FilterSlice(playlists, func(playlist *mediaprovider.Playlist) bool {
|
||||||
return playlist.Owner == sp.loggedInUser
|
if _, isJellyfin := sp.mp.(*jellyfin.JellyfinMediaProvider); isJellyfin {
|
||||||
|
// Jellyfin usernames are case-insensitive
|
||||||
|
return strings.EqualFold(playlist.Owner, sp.loggedInUser)
|
||||||
|
} else {
|
||||||
|
// Subsonic usernames are case-sensitive
|
||||||
|
return playlist.Owner == sp.loggedInUser
|
||||||
|
}
|
||||||
})
|
})
|
||||||
sp.allPlaylistResuts = sharedutil.MapSlice(userPlaylists, sp.playlistToSearchResult)
|
sp.allPlaylistResuts = sharedutil.MapSlice(userPlaylists, sp.playlistToSearchResult)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ type PeakMeter struct {
|
|||||||
lPeakHoldFrame uint64
|
lPeakHoldFrame uint64
|
||||||
rPeakHoldFrame uint64
|
rPeakHoldFrame uint64
|
||||||
frameCounter uint64
|
frameCounter uint64
|
||||||
|
|
||||||
|
// true iff only a layout is needed, rathern than a full refresh.
|
||||||
|
// cleared by the renderer
|
||||||
|
refreshLayoutOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPeakMeter() *PeakMeter {
|
func NewPeakMeter() *PeakMeter {
|
||||||
@@ -68,6 +72,7 @@ func (p *PeakMeter) UpdatePeaks(lPeak, rPeak, lRMS, rRMS float64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.frameCounter++
|
p.frameCounter++
|
||||||
|
p.refreshLayoutOnly = true
|
||||||
p.Refresh()
|
p.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +80,11 @@ func (p *PeakMeter) CreateRenderer() fyne.WidgetRenderer {
|
|||||||
return newPeakMeterRenderer(p)
|
return newPeakMeterRenderer(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PeakMeter) Refresh() {
|
||||||
|
p.refreshLayoutOnly = false
|
||||||
|
p.BaseWidget.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
type peakMeterRenderer struct {
|
type peakMeterRenderer struct {
|
||||||
p *PeakMeter
|
p *PeakMeter
|
||||||
|
|
||||||
@@ -168,6 +178,12 @@ func (l *peakMeterRenderer) Layout(size fyne.Size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *peakMeterRenderer) Refresh() {
|
func (l *peakMeterRenderer) Refresh() {
|
||||||
|
if l.p.refreshLayoutOnly {
|
||||||
|
l.p.refreshLayoutOnly = false
|
||||||
|
l.Layout(l.p.Size())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
foreground := theme.ForegroundColor()
|
foreground := theme.ForegroundColor()
|
||||||
background := theme.BackgroundColor()
|
background := theme.BackgroundColor()
|
||||||
errC := theme.ErrorColor()
|
errC := theme.ErrorColor()
|
||||||
|
|||||||
+40
-17
@@ -410,6 +410,17 @@ func (t *tracklistRowBase) TrackID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *tracklistRowBase) Update(tm *util.TrackListModel, rowNum int, onUpdate func()) {
|
func (t *tracklistRowBase) Update(tm *util.TrackListModel, rowNum int, onUpdate func()) {
|
||||||
|
// Show only columns configured to be visible
|
||||||
|
for i := 2; i < len(t.tracklist.columns); i++ {
|
||||||
|
t.setColVisibility(i, !t.tracklist.visibleColumns[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ealy return if nothing else needs updating
|
||||||
|
if !t.needsUpdate(tm, rowNum) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throttle updates when many are happening in a short time
|
||||||
if tracklistUpdateCounter.NumEventsSince(time.Now().Add(-300*time.Millisecond)) > 10 {
|
if tracklistUpdateCounter.NumEventsSince(time.Now().Add(-300*time.Millisecond)) > 10 {
|
||||||
t.doUpdate(&emptyTrack, 1)
|
t.doUpdate(&emptyTrack, 1)
|
||||||
if t.nextUpdateModel == nil {
|
if t.nextUpdateModel == nil {
|
||||||
@@ -434,11 +445,31 @@ func (t *tracklistRowBase) Update(tm *util.TrackListModel, rowNum int, onUpdate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tracklistRowBase) needsUpdate(tm *util.TrackListModel, rowNum int) bool {
|
||||||
|
tr := tm.Track()
|
||||||
|
if tm.Selected != t.Selected {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if t.trackID != tr.ID || t.trackNum != rowNum {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if t.playCount != tr.PlayCount || t.isFavorite != tr.Favorite || t.rating.Rating != tr.Rating {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if t.rating.IsDisabled != t.tracklist.Options.DisableRating {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if isPlaying := t.tracklist.nowPlayingID == tr.ID; isPlaying != t.isPlaying {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tracklistRowBase) doUpdate(tm *util.TrackListModel, rowNum int) {
|
func (t *tracklistRowBase) doUpdate(tm *util.TrackListModel, rowNum int) {
|
||||||
changed := false
|
fullRefresh := false
|
||||||
if tm.Selected != t.Selected {
|
if tm.Selected != t.Selected {
|
||||||
t.Selected = tm.Selected
|
t.Selected = tm.Selected
|
||||||
changed = true
|
fullRefresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update info that can change if this row is bound to
|
// Update info that can change if this row is bound to
|
||||||
@@ -473,7 +504,7 @@ func (t *tracklistRowBase) doUpdate(tm *util.TrackListModel, rowNum int) {
|
|||||||
t.dateAdded.Text = util.FormatDate(tr.DateAdded)
|
t.dateAdded.Text = util.FormatDate(tr.DateAdded)
|
||||||
t.path.Text = tr.FilePath
|
t.path.Text = tr.FilePath
|
||||||
t.path.SetToolTip(tr.FilePath)
|
t.path.SetToolTip(tr.FilePath)
|
||||||
changed = true
|
fullRefresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update track num if needed
|
// Update track num if needed
|
||||||
@@ -493,21 +524,20 @@ func (t *tracklistRowBase) doUpdate(tm *util.TrackListModel, rowNum int) {
|
|||||||
} else {
|
} else {
|
||||||
str = strconv.Itoa(rowNum)
|
str = strconv.Itoa(rowNum)
|
||||||
}
|
}
|
||||||
t.num.Text = str
|
t.num.SetText(str)
|
||||||
changed = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update play count if needed
|
// Update play count if needed
|
||||||
if tr.PlayCount != t.playCount {
|
if tr.PlayCount != t.playCount {
|
||||||
t.playCount = tr.PlayCount
|
t.playCount = tr.PlayCount
|
||||||
t.plays.Text = strconv.Itoa(int(tr.PlayCount))
|
t.plays.SetText(strconv.Itoa(int(tr.PlayCount)))
|
||||||
changed = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render whether track is playing or not
|
// Render whether track is playing or not
|
||||||
if isPlaying := t.tracklist.nowPlayingID == tr.ID; isPlaying != t.isPlaying {
|
if isPlaying := t.tracklist.nowPlayingID == tr.ID; isPlaying != t.isPlaying {
|
||||||
t.isPlaying = isPlaying
|
t.isPlaying = isPlaying
|
||||||
t.name.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying
|
t.name.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = isPlaying
|
||||||
|
t.name.Refresh()
|
||||||
|
|
||||||
if isPlaying {
|
if isPlaying {
|
||||||
t.originalNumColContent = t.Content.(*fyne.Container).Objects[0]
|
t.originalNumColContent = t.Content.(*fyne.Container).Objects[0]
|
||||||
@@ -515,14 +545,14 @@ func (t *tracklistRowBase) doUpdate(tm *util.TrackListModel, rowNum int) {
|
|||||||
} else {
|
} else {
|
||||||
t.Content.(*fyne.Container).Objects[0] = t.originalNumColContent
|
t.Content.(*fyne.Container).Objects[0] = t.originalNumColContent
|
||||||
}
|
}
|
||||||
changed = true
|
canvas.Refresh(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update favorite column
|
// Update favorite column
|
||||||
if tr.Favorite != t.isFavorite {
|
if tr.Favorite != t.isFavorite {
|
||||||
t.isFavorite = tr.Favorite
|
t.isFavorite = tr.Favorite
|
||||||
t.favorite.Objects[0].(*FavoriteIcon).Favorite = tr.Favorite
|
t.favorite.Objects[0].(*FavoriteIcon).Favorite = tr.Favorite
|
||||||
changed = true
|
canvas.Refresh(t.favorite)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update rating column
|
// Update rating column
|
||||||
@@ -535,14 +565,7 @@ func (t *tracklistRowBase) doUpdate(tm *util.TrackListModel, rowNum int) {
|
|||||||
t.rating.Refresh()
|
t.rating.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show only columns configured to be visible
|
if fullRefresh {
|
||||||
for i := 2; i < len(t.tracklist.columns); i++ {
|
|
||||||
if ch := t.setColVisibility(i, !t.tracklist.visibleColumns[i]); ch {
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
tracklistUpdateCounter.Add()
|
tracklistUpdateCounter.Add()
|
||||||
t.Refresh()
|
t.Refresh()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user