From 0c2ef746059c093500324bf6a5565a9781411e59 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 13 Oct 2024 19:40:19 +0200 Subject: [PATCH] feat: support smart shuffle --- cmd/daemon/player.go | 56 ++++++++++++++++++++++++++++++++++++++++---- tracks/tracks.go | 27 +++++++++++++++++++++ 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/cmd/daemon/player.go b/cmd/daemon/player.go index f3b7710..9a96ecc 100644 --- a/cmd/daemon/player.go +++ b/cmd/daemon/player.go @@ -5,7 +5,11 @@ import ( "encoding/json" "encoding/xml" "fmt" + lenspb "github.com/devgianlu/go-librespot/proto/spotify/lens" + signalpb "github.com/devgianlu/go-librespot/proto/spotify/playlist/signal" + playlist4pb "github.com/devgianlu/go-librespot/proto/spotify/playlist4" "io" + "net/url" "strconv" "strings" "sync" @@ -303,12 +307,54 @@ func (p *AppPlayer) handlePlayerCommand(req dealer.RequestPayload) error { return nil } + p.state.player.ContextUrl = req.Command.Context.Url p.state.player.ContextRestrictions = req.Command.Context.Restrictions - if p.state.player.ContextMetadata == nil { - p.state.player.ContextMetadata = map[string]string{} - } - for k, v := range req.Command.Context.Metadata { - p.state.player.ContextMetadata[k] = v + p.state.player.ContextMetadata = req.Command.Context.Metadata + + if strings.HasPrefix(req.Command.Context.Url, "context://") { + parts := strings.Split(req.Command.Context.Url, "?") + if len(parts) > 1 { + query, err := url.ParseQuery(parts[1]) + if err != nil { + return fmt.Errorf("failed to parse context URL query: %w", err) + } + + applyLenses := query["spotify-apply-lenses"] + if len(applyLenses) > 0 { + if len(applyLenses) > 1 { + log.Warnf("ignoring multiple spotify-apply-lenses: %v", applyLenses) + } + + lensBytes, err := proto.Marshal(&lenspb.Lens{Identifier: applyLenses[0]}) + if err != nil { + return fmt.Errorf("failed to marshal lens: %w", err) + } + + resp, err := p.sess.Spclient().PlaylistSignals( + librespot.SpotifyIdFromUri(p.state.player.ContextUri), + &playlist4pb.ListSignals{ + BaseRevision: nil, // FIXME? + EmittedSignals: []*signalpb.Signal{{ + Identifier: "reset", + Data: lensBytes, + }}, + }, + applyLenses, + ) + if err != nil { + return fmt.Errorf("failed to list playlist signals: %w", err) + } + + if p.state.player.ContextMetadata == nil { + p.state.player.ContextMetadata = make(map[string]string) + } + for _, attr := range resp.Attributes.FormatAttributes { + p.state.player.ContextMetadata[*attr.Key] = *attr.Value + } + + p.state.tracks.ApplySelectedListContent(resp) + } + } } p.updateState() diff --git a/tracks/tracks.go b/tracks/tracks.go index d9bb412..9e1dba8 100644 --- a/tracks/tracks.go +++ b/tracks/tracks.go @@ -2,7 +2,9 @@ package tracks import ( "fmt" + playlist4pb "github.com/devgianlu/go-librespot/proto/spotify/playlist4" "slices" + "strconv" librespot "github.com/devgianlu/go-librespot" connectpb "github.com/devgianlu/go-librespot/proto/spotify/connectstate" @@ -318,3 +320,28 @@ func (tl *List) ToggleShuffle(shuffle bool) error { } } } + +func (tl *List) ApplySelectedListContent(content *playlist4pb.SelectedListContent) { + for _, item := range content.Contents.Items { + spotId, err := librespot.SpotifyIdFromUri(item.GetUri()) + if err != nil { + log.WithError(err).Errorf("failed parsing uri %s", item.GetUri()) + continue + } + + track := connectpb.ContextTrack{ + Uri: spotId.Uri(), + Gid: spotId.Id(), + Metadata: map[string]string{ + "added_at": strconv.FormatInt(item.Attributes.GetTimestamp(), 10), + "added_by_username": item.Attributes.GetAddedBy(), + }, + } + + for _, attr := range item.Attributes.FormatAttributes { + track.Metadata[*attr.Key] = *attr.Value + } + + log.Infof("%v", &track) // TODO: no clue where these should be added + } +}