Skip to content

Commit bc5c441

Browse files
committed
fix: skip track when Spotify refuses its audio key instead of freezing
Spotify makes a per-track, context-dependent decision on granting the legacy AES audio key. License-gated tracks are refused (AesKeyError, e.g. code 1) during ordinary playlist playback even though they play fine in DJ/dynamic-session mode and on official clients (which establish a licensed context). Confirmed empirically: in one session, adjacent OGG_VORBIS_320 tracks were granted/refused per-track, the refused ones consistently (prefetch + load), and they play via DJ. Restart/bitrate/fresh-connection make no difference — it is a server-side decision. Previously a refused track froze the player. Treat a key refusal like a restricted/ unsupported track and skip to the next one so playback keeps going. Band-aid: the real fix is proper key licensing (PlayPlay / dynamic-session context) so these tracks play. Tracked separately.
1 parent b7c6df4 commit bc5c441

1 file changed

Lines changed: 14 additions & 2 deletions

File tree

daemon/controls.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
librespot "github.com/devgianlu/go-librespot"
16+
"github.com/devgianlu/go-librespot/audio"
1617
"github.com/devgianlu/go-librespot/mpris"
1718
"github.com/devgianlu/go-librespot/player"
1819
connectpb "github.com/devgianlu/go-librespot/proto/spotify/connectstate"
@@ -1018,8 +1019,19 @@ func (p *AppPlayer) advanceNext(ctx context.Context, forceNext, drop bool) (bool
10181019

10191020
// load current track into stream
10201021
isDJSession := p.state.player.PlayOrigin != nil && p.state.player.PlayOrigin.FeatureIdentifier == "dynamic-sessions"
1021-
if err := p.loadCurrentTrack(ctx, !hasNextTrack, drop); errors.Is(err, librespot.ErrMediaRestricted) || errors.Is(err, librespot.ErrNoSupportedFormats) || (isDJSession && err != nil && !forceNext) {
1022-
p.app.log.WithError(err).Infof("skipping unplayable media: %s", uri)
1022+
// BAND-AID: Spotify makes a per-track, context-dependent decision on granting the legacy
1023+
// AES audio key. License-gated tracks are refused (AesKeyError, e.g. code 1) in ordinary
1024+
// playlist playback — even though they play in DJ/dynamic-session mode and on official
1025+
// clients, which establish a licensed context. We cannot decrypt a refused track, so skip
1026+
// it instead of freezing the player. Remove once proper key licensing (PlayPlay /
1027+
// dynamic-session context) is implemented — tracked separately.
1028+
var keyErr *audio.KeyProviderError
1029+
if err := p.loadCurrentTrack(ctx, !hasNextTrack, drop); errors.Is(err, librespot.ErrMediaRestricted) || errors.Is(err, librespot.ErrNoSupportedFormats) || errors.As(err, &keyErr) || (isDJSession && err != nil && !forceNext) {
1030+
if keyErr != nil {
1031+
p.app.log.WithError(err).Warnf("skipping track: Spotify refused the audio key (code %d) for this playback context: %s", keyErr.Code, uri)
1032+
} else {
1033+
p.app.log.WithError(err).Infof("skipping unplayable media: %s", uri)
1034+
}
10231035
if forceNext {
10241036
if isDJSession {
10251037
// Two consecutive unplayable DJ tracks (e.g. back-to-back narration clips).

0 commit comments

Comments
 (0)