Skip to content

Commit e70fb43

Browse files
committed
Diminished usage of URIs because Spotify doesn't send them anymore (#277)
1 parent 8f19c2f commit e70fb43

File tree

2 files changed

+97
-34
lines changed

2 files changed

+97
-34
lines changed

lib/src/main/java/xyz/gianlu/librespot/common/ProtoUtils.java

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.gson.JsonArray;
44
import com.google.gson.JsonElement;
55
import com.google.gson.JsonObject;
6+
import com.google.protobuf.ByteString;
67
import com.google.protobuf.Descriptors;
78
import com.spotify.connectstate.Player;
89
import com.spotify.connectstate.Player.ContextPlayerOptions;
@@ -14,6 +15,7 @@
1415
import org.jetbrains.annotations.Contract;
1516
import org.jetbrains.annotations.NotNull;
1617
import org.jetbrains.annotations.Nullable;
18+
import xyz.gianlu.librespot.metadata.PlayableId;
1719

1820
import java.lang.reflect.Field;
1921
import java.util.*;
@@ -61,8 +63,8 @@ private static JsonObject trackToJson(@NotNull Player.ProvidedTrack track) {
6163
@NotNull
6264
private static JsonObject trackToJson(@NotNull ContextTrack track) {
6365
JsonObject obj = new JsonObject();
64-
obj.addProperty("uri", track.getUri());
65-
obj.addProperty("uid", track.getUid());
66+
if (track.hasUri() && !track.getUri().isEmpty()) obj.addProperty("uri", track.getUri());
67+
if (track.hasUid()) obj.addProperty("uid", track.getUid());
6668
obj.add("metadata", mapToJson(track.getMetadataMap()));
6769
return obj;
6870
}
@@ -106,7 +108,10 @@ public static JsonObject craftContextStateCombo(@NotNull Player.PlayerStateOrBui
106108
@NotNull
107109
public static ContextTrack jsonToContextTrack(@NotNull JsonObject obj) {
108110
ContextTrack.Builder builder = ContextTrack.newBuilder();
109-
Optional.ofNullable(Utils.optString(obj, "uri", null)).ifPresent(builder::setUri);
111+
Optional.ofNullable(Utils.optString(obj, "uri", null)).ifPresent(uri -> {
112+
builder.setUri(uri);
113+
builder.setGid(ByteString.copyFrom(PlayableId.fromUri(uri).getGid()));
114+
});
110115
Optional.ofNullable(Utils.optString(obj, "uid", null)).ifPresent(builder::setUid);
111116

112117
JsonObject metadata = obj.getAsJsonObject("metadata");
@@ -280,7 +285,7 @@ public static void enrichTrack(@NotNull Player.ProvidedTrack.Builder subject, @N
280285

281286
@Nullable
282287
@Contract("null -> null")
283-
public static Player.ProvidedTrack convertToProvidedTrack(@Nullable ContextTrack track) {
288+
public static Player.ProvidedTrack toProvidedTrack(@Nullable ContextTrack track) {
284289
if (track == null) return null;
285290

286291
Player.ProvidedTrack.Builder builder = Player.ProvidedTrack.newBuilder();
@@ -338,4 +343,56 @@ public static void copyOverMetadata(@NotNull ContextTrack from, @NotNull Context
338343
public static void copyOverMetadata(@NotNull ContextTrack from, @NotNull Player.ProvidedTrack.Builder to) {
339344
to.putAllMetadata(from.getMetadataMap());
340345
}
346+
347+
public static boolean trackEquals(ContextTrack first, ContextTrack second) {
348+
if (first == null || second == null) return false;
349+
if (first == second) return true;
350+
351+
if (first.hasUri() && !first.getUri().isEmpty() && second.hasUri() && !second.getUri().isEmpty())
352+
return first.getUri().equals(second.getUri());
353+
354+
if (first.hasGid() && second.hasGid())
355+
return first.getGid().equals(second.getGid());
356+
357+
if (first.hasUid() && !first.getUid().isEmpty() && second.hasUid() && !second.getUid().isEmpty())
358+
return first.getUid().equals(second.getUid());
359+
360+
return false;
361+
}
362+
363+
public static int indexOfTrack(List<ContextTrack> list, ContextTrack track) {
364+
for (int i = 0; i < list.size(); i++)
365+
if (trackEquals(list.get(i), track))
366+
return i;
367+
368+
return -1;
369+
}
370+
371+
public static boolean isTrack(Player.ProvidedTrack track, Metadata.Track metadata) {
372+
return track.getUri().equals(PlayableId.from(metadata).toSpotifyUri());
373+
}
374+
375+
public static boolean isEpisode(Player.ProvidedTrack track, Metadata.Episode metadata) {
376+
return track.getUri().equals(PlayableId.from(metadata).toSpotifyUri());
377+
}
378+
379+
@NotNull
380+
public static String toString(ContextTrack track) {
381+
return String.format("ContextTrack{uri: %s, uid: %s, gid: %s}", track.getUri(), track.getUid(), Utils.bytesToHex(track.getGid()));
382+
}
383+
384+
@NotNull
385+
public static String toString(Player.ProvidedTrack track) {
386+
return String.format("ProvidedTrack{uri: %s, uid: %s}", track.getUri(), track.getUid());
387+
}
388+
389+
@NotNull
390+
public static String toString(Metadata.Track metadata) {
391+
return String.format("Metadata.Track{%s,%s}", Utils.bytesToHex(metadata.getGid()), PlayableId.from(metadata).toSpotifyUri());
392+
}
393+
394+
@NotNull
395+
public static String toString(Metadata.Episode metadata) {
396+
return String.format("Metadata.Episode{%s,%s}", Utils.bytesToHex(metadata.getGid()), PlayableId.from(metadata).toSpotifyUri());
397+
}
341398
}

player/src/main/java/xyz/gianlu/librespot/player/StateWrapper.java

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.io.Closeable;
4141
import java.io.IOException;
4242
import java.util.*;
43+
import java.util.function.Function;
4344

4445
/**
4546
* @author Gianlu
@@ -335,8 +336,8 @@ void enrichWithMetadata(@NotNull TrackOrEpisode metadata) {
335336

336337
private synchronized void enrichWithMetadata(@NotNull Metadata.Track track) {
337338
if (state.getTrack() == null) throw new IllegalStateException();
338-
if (!state.getTrack().getUri().equals(PlayableId.from(track).toSpotifyUri())) {
339-
LOGGER.warn("Failed updating metadata: tracks do not match. {current: {}, expected: {}}", state.getTrack().getUri(), PlayableId.from(track).toSpotifyUri());
339+
if (!ProtoUtils.isTrack(state.getTrack(), track)) {
340+
LOGGER.warn("Failed updating metadata: tracks do not match. {current: {}, expected: {}}", ProtoUtils.toString(state.getTrack()), ProtoUtils.toString(track));
340341
return;
341342
}
342343

@@ -396,8 +397,8 @@ private synchronized void enrichWithMetadata(@NotNull Metadata.Track track) {
396397

397398
private synchronized void enrichWithMetadata(@NotNull Metadata.Episode episode) {
398399
if (state.getTrack() == null) throw new IllegalStateException();
399-
if (!state.getTrack().getUri().equals(PlayableId.from(episode).toSpotifyUri())) {
400-
LOGGER.warn("Failed updating metadata: episodes do not match. {current: {}, expected: {}}", state.getTrack().getUri(), PlayableId.from(episode).toSpotifyUri());
400+
if (!ProtoUtils.isEpisode(state.getTrack(), episode)) {
401+
LOGGER.warn("Failed updating metadata: episodes do not match. {current: {}, expected: {}}", ProtoUtils.toString(state.getTrack()), ProtoUtils.toString(episode));
401402
return;
402403
}
403404

@@ -472,7 +473,15 @@ String transfer(@NotNull TransferStateOuterClass.TransferState cmd) throws AbsSp
472473

473474
PlaybackOuterClass.Playback pb = cmd.getPlayback();
474475
try {
475-
tracksKeeper.initializeFrom(tracks -> ProtoUtils.indexOfTrackByUid(tracks, ps.getCurrentUid()), pb.getCurrentTrack(), cmd.getQueue());
476+
tracksKeeper.initializeFrom(tracks -> {
477+
for (int i = 0; i < tracks.size(); i++) {
478+
ContextTrack track = tracks.get(i);
479+
if ((track.hasUid() && ps.getCurrentUid().equals(track.getUid())) || ProtoUtils.trackEquals(track, pb.getCurrentTrack()))
480+
return i;
481+
}
482+
483+
return -1;
484+
}, pb.getCurrentTrack(), cmd.getQueue());
476485
} catch (IllegalStateException ex) {
477486
LOGGER.warn("Failed initializing tracks, falling back to start. {uid: {}}", ps.getCurrentUid());
478487
tracksKeeper.initializeStart();
@@ -848,10 +857,6 @@ public boolean isOk() {
848857
}
849858
}
850859

851-
private interface TrackFinder {
852-
int find(@NotNull List<ContextTrack> tracks);
853-
}
854-
855860
private static class PlayableIdWithIndex {
856861
private final PlayableId id;
857862
private final int index;
@@ -925,14 +930,14 @@ private void updatePrevNextTracks() {
925930

926931
state.clearPrevTracks();
927932
for (int i = Math.max(0, index - MAX_PREV_TRACKS); i < index; i++)
928-
state.addPrevTracks(ProtoUtils.convertToProvidedTrack(tracks.get(i)));
933+
state.addPrevTracks(ProtoUtils.toProvidedTrack(tracks.get(i)));
929934

930935
state.clearNextTracks();
931936
for (ContextTrack track : queue)
932-
state.addNextTracks(ProtoUtils.convertToProvidedTrack(track));
937+
state.addNextTracks(ProtoUtils.toProvidedTrack(track));
933938

934939
for (int i = index + 1; i < Math.min(tracks.size(), index + 1 + MAX_NEXT_TRACKS); i++)
935-
state.addNextTracks(ProtoUtils.convertToProvidedTrack(tracks.get(i)));
940+
state.addNextTracks(ProtoUtils.toProvidedTrack(tracks.get(i)));
936941
}
937942

938943
void updateTrackDuration(int duration) {
@@ -971,8 +976,8 @@ private void updateLikeDislike() {
971976
* <b>This will also REMOVE a track from the queue if needed. Calling this twice will break the queue.</b>
972977
*/
973978
private void updateState() {
974-
if (isPlayingQueue) state.setTrack(ProtoUtils.convertToProvidedTrack(queue.remove()));
975-
else state.setTrack(ProtoUtils.convertToProvidedTrack(tracks.get(getCurrentTrackIndex())));
979+
if (isPlayingQueue) state.setTrack(ProtoUtils.toProvidedTrack(queue.remove()));
980+
else state.setTrack(ProtoUtils.toProvidedTrack(tracks.get(getCurrentTrackIndex())));
976981

977982
updateLikeDislike();
978983

@@ -1025,7 +1030,7 @@ synchronized void setQueue(@Nullable List<ContextTrack> prevTracks, @Nullable Li
10251030
synchronized void updateContext(@NotNull List<ContextPage> updatedPages) {
10261031
List<ContextTrack> updatedTracks = ProtoUtils.join(updatedPages);
10271032
for (ContextTrack track : updatedTracks) {
1028-
int index = ProtoUtils.indexOfTrackByUri(tracks, track.getUri());
1033+
int index = ProtoUtils.indexOfTrack(tracks, track);
10291034
if (index == -1) continue;
10301035

10311036
ContextTrack.Builder builder = tracks.get(index).toBuilder();
@@ -1058,16 +1063,16 @@ synchronized void initializeStart() throws IOException, MercuryClient.MercuryExc
10581063
setCurrentTrackIndex(0);
10591064
}
10601065

1061-
synchronized void initializeFrom(@NotNull TrackFinder finder, @Nullable ContextTrack track, @Nullable QueueOuterClass.Queue contextQueue) throws IOException, MercuryClient.MercuryException, AbsSpotifyContext.UnsupportedContextException {
1066+
synchronized void initializeFrom(@NotNull Function<List<ContextTrack>, Integer> finder, @Nullable ContextTrack track, @Nullable QueueOuterClass.Queue contextQueue) throws IOException, MercuryClient.MercuryException, AbsSpotifyContext.UnsupportedContextException {
10621067
tracks.clear();
10631068
queue.clear();
10641069

10651070
while (true) {
10661071
if (pages.nextPage()) {
10671072
List<ContextTrack> newTracks = pages.currentPage();
1068-
int index = finder.find(newTracks);
1073+
int index = finder.apply(newTracks);
10691074
if (index == -1) {
1070-
LOGGER.trace("Did not find track, going to next page, finder: {}", finder);
1075+
LOGGER.trace("Did not find track, going to next page.");
10711076
tracks.addAll(newTracks);
10721077
continue;
10731078
}
@@ -1076,7 +1081,7 @@ synchronized void initializeFrom(@NotNull TrackFinder finder, @Nullable ContextT
10761081
tracks.addAll(newTracks);
10771082

10781083
setCurrentTrackIndex(index);
1079-
LOGGER.trace("Initialized current track index to {}, finder: {}", index, finder);
1084+
LOGGER.trace("Initialized current track index to {}.", index);
10801085
break;
10811086
} else {
10821087
cannotLoadMore = true;
@@ -1111,17 +1116,17 @@ private void enrichCurrentTrack(@NotNull ContextTrack track) {
11111116
ContextTrack.Builder current = tracks.get(index).toBuilder();
11121117
ProtoUtils.enrichTrack(current, track);
11131118
tracks.set(index, current.build());
1114-
state.setTrack(ProtoUtils.convertToProvidedTrack(current.build()));
1119+
state.setTrack(ProtoUtils.toProvidedTrack(current.build()));
11151120
}
11161121
}
11171122

1118-
synchronized void skipTo(@NotNull String uri) {
1123+
synchronized void skipTo(@NotNull ContextTrack track) {
11191124
if (!queue.isEmpty()) {
11201125
List<ContextTrack> queueCopy = new ArrayList<>(queue);
11211126

11221127
Iterator<ContextTrack> iterator = queue.iterator();
11231128
while (iterator.hasNext()) {
1124-
if (Objects.equals(iterator.next().getUri(), uri)) {
1129+
if (ProtoUtils.trackEquals(iterator.next(), track)) {
11251130
isPlayingQueue = true;
11261131
updateState();
11271132
return;
@@ -1134,15 +1139,15 @@ synchronized void skipTo(@NotNull String uri) {
11341139
queue.addAll(queueCopy);
11351140
}
11361141

1137-
int index = ProtoUtils.indexOfTrackByUri(tracks, uri);
1138-
if (index == -1) throw new IllegalStateException("Did not find track to skip to: " + uri);
1139-
1140-
setCurrentTrackIndex(index);
1141-
}
1142+
for (int i = 0; i < tracks.size(); i++) {
1143+
if (ProtoUtils.trackEquals(tracks.get(i), track)) {
1144+
setCurrentTrackIndex(i);
1145+
enrichCurrentTrack(track);
1146+
return;
1147+
}
1148+
}
11421149

1143-
synchronized void skipTo(@NotNull ContextTrack track) {
1144-
skipTo(track.getUri());
1145-
enrichCurrentTrack(track);
1150+
throw new IllegalStateException("Did not find track to skip to: " + ProtoUtils.toString(track));
11461151
}
11471152

11481153
/**
@@ -1372,6 +1377,7 @@ void addToTracks(int from, @NotNull List<Playlist4ApiProto.Item> items) {
13721377
for (int i = 0; i < items.size(); i++) {
13731378
Playlist4ApiProto.Item item = items.get(i);
13741379
tracks.add(i + from, ContextTrack.newBuilder()
1380+
.setGid(ByteString.copyFrom(PlayableId.fromUri(item.getUri()).getGid()))
13751381
.setUri(item.getUri())
13761382
.build());
13771383
}

0 commit comments

Comments
 (0)