From 0ebbf621b1b1e56b1cbce983f7a84ff7fc9a603d Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sun, 14 May 2023 18:59:38 -0700 Subject: [PATCH] finish mediaprovider interface for subsonic --- backend/mediaprovider/mediaprovider.go | 4 +- .../subsonic/subsonicmediaprovider.go | 70 ++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/backend/mediaprovider/mediaprovider.go b/backend/mediaprovider/mediaprovider.go index d9244e5..c428f15 100644 --- a/backend/mediaprovider/mediaprovider.go +++ b/backend/mediaprovider/mediaprovider.go @@ -58,9 +58,11 @@ type MediaProvider interface { GetFavorites() (Favorites, error) + GetStreamURL(trackID string) (string, error) + SetFavorite(params RatingFavoriteParameters) error - SetRating(params RatingFavoriteParameters) error + SetRating(params RatingFavoriteParameters, rating int) error GetPlaylists() ([]Playlist, error) diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index 90009be..876e79d 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -3,7 +3,10 @@ package subsonic import ( "image" "log" + "math" "strconv" + "sync" + "time" "github.com/dweymouth/go-subsonic/subsonic" "github.com/dweymouth/supersonic/backend/mediaprovider" @@ -25,7 +28,7 @@ type subsonicMediaProvider struct { client *subsonic.Client } -func SubsonicMediaProvider(subsonicClient *subsonic.Client) *subsonicMediaProvider /*mediaprovider.MediaProvider*/ { +func SubsonicMediaProvider(subsonicClient *subsonic.Client) mediaprovider.MediaProvider { return &subsonicMediaProvider{client: subsonicClient} } @@ -195,6 +198,71 @@ func (s *subsonicMediaProvider) GetSimilarTracks(artistID string, count int) ([] return sharedutil.MapSlice(tr, toTrack), nil } +func (s *subsonicMediaProvider) GetStreamURL(trackID string) (string, error) { + u, err := s.client.GetStreamURL(trackID, map[string]string{}) + if err != nil { + return "", err + } + return u.String(), nil +} + +func (s *subsonicMediaProvider) ReplacePlaylistTracks(playlistID string, trackIDs []string) error { + return s.client.CreatePlaylistWithTracks(trackIDs, map[string]string{"playlistId": playlistID}) +} + +func (s *subsonicMediaProvider) Scrobble(trackID string, submission bool) error { + return s.client.Scrobble(trackID, map[string]string{ + "time": strconv.FormatInt(time.Now().UnixMilli(), 10), + "submission": strconv.FormatBool(submission)}) +} + +func (s *subsonicMediaProvider) SetFavorite(params mediaprovider.RatingFavoriteParameters) error { + return s.client.Star(subsonic.StarParameters{ + AlbumIDs: params.AlbumIDs, + ArtistIDs: params.ArtistIDs, + SongIDs: params.TrackIDs, + }) +} + +func (s *subsonicMediaProvider) SetRating(params mediaprovider.RatingFavoriteParameters, rating int) error { + // Subsonic doesn't allow bulk setting ratings. + // To not overwhelm the server with requests, set rating for + // only 5 tracks at a time concurrently + batchSize := 5 + var err error + batchSetRating := func(offs int, wg *sync.WaitGroup) { + for i := 0; i < batchSize && offs+i < len(params.TrackIDs); i++ { + if wg != nil { + wg.Add(1) + } + go func(idx int) { + newErr := s.client.SetRating(params.TrackIDs[idx], rating) + if err == nil && newErr != nil { + err = newErr + } + if wg != nil { + wg.Done() + } + }(offs + i) + } + } + + if len(params.TrackIDs) <= 5 { + // one batch only - no need to use wait group + batchSetRating(0, nil) + } else { + go func() { + numBatches := int(math.Ceil(float64(len(params.TrackIDs)) / float64(batchSize))) + for i := 0; i < numBatches; i++ { + var wg sync.WaitGroup + batchSetRating(i*batchSize, &wg) + wg.Wait() + } + }() + } + return err +} + func toTrack(ch *subsonic.Child) mediaprovider.Track { if ch == nil { log.Println("subsonicMediaProvider: toTrack called on nil track")