Skip to content

Commit 929c694

Browse files
committed
Unshuffle kind of working (#15)
1 parent 5c585d3 commit 929c694

7 files changed

Lines changed: 172 additions & 2 deletions

File tree

common/src/main/java/xyz/gianlu/librespot/common/Utils.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.apache.log4j.Logger;
55
import org.jetbrains.annotations.NotNull;
66
import xyz.gianlu.librespot.common.proto.Metadata;
7+
import xyz.gianlu.librespot.common.proto.Spirc;
78

89
import java.io.ByteArrayOutputStream;
910
import java.io.IOException;
@@ -33,6 +34,18 @@ public static String toBase64(@NotNull ByteString bytes) {
3334
return Base64.getEncoder().encodeToString(bytes.toByteArray());
3435
}
3536

37+
public static int indexOf(@NotNull List<Spirc.TrackRef> list, @NotNull Spirc.TrackRef ref) {
38+
for (int i = 0; i < list.size(); i++) {
39+
Spirc.TrackRef item = list.get(i);
40+
if (item.hasGid() && ref.hasGid() && item.getGid().equals(ref.getGid()))
41+
return i;
42+
else if (item.hasUri() && ref.hasUri() && item.getUri().equals(ref.getUri()))
43+
return i;
44+
}
45+
46+
return -1;
47+
}
48+
3649
@NotNull
3750
public static String readLine(@NotNull InputStream in) throws IOException {
3851
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package xyz.gianlu.librespot.mercury;
2+
3+
import com.google.gson.JsonElement;
4+
import com.google.gson.JsonParser;
5+
import org.jetbrains.annotations.NotNull;
6+
7+
import java.io.InputStreamReader;
8+
import java.lang.reflect.InvocationTargetException;
9+
10+
/**
11+
* @author Gianlu
12+
*/
13+
public class JsonMercuryRequest<W extends JsonWrapper> {
14+
private static final JsonParser PARSER = new JsonParser();
15+
final RawMercuryRequest request;
16+
private final Class<W> wrapperClass;
17+
18+
JsonMercuryRequest(@NotNull RawMercuryRequest request, @NotNull Class<W> wrapperClass) {
19+
this.request = request;
20+
this.wrapperClass = wrapperClass;
21+
}
22+
23+
@NotNull
24+
public W instantiate(@NotNull MercuryClient.Response resp) {
25+
try {
26+
JsonElement elm = PARSER.parse(new InputStreamReader(resp.payload.stream()));
27+
return wrapperClass.getConstructor(JsonElement.class).newInstance(elm);
28+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
29+
throw new RuntimeException(ex);
30+
}
31+
}
32+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package xyz.gianlu.librespot.mercury;
2+
3+
import com.google.gson.JsonArray;
4+
import com.google.gson.JsonElement;
5+
import com.google.gson.JsonObject;
6+
import org.jetbrains.annotations.NotNull;
7+
8+
/**
9+
* @author Gianlu
10+
*/
11+
public abstract class JsonWrapper {
12+
private final JsonElement elm;
13+
14+
public JsonWrapper(@NotNull JsonElement elm) {
15+
this.elm = elm;
16+
}
17+
18+
@NotNull
19+
public final JsonObject obj() {
20+
return elm.getAsJsonObject();
21+
}
22+
23+
@NotNull
24+
public final JsonArray array() {
25+
return elm.getAsJsonArray();
26+
}
27+
}

core/src/main/java/xyz/gianlu/librespot/mercury/MercuryClient.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ public Response sendSync(@NotNull RawMercuryRequest request) throws IOException
7070
return Utils.wait(reference);
7171
}
7272

73+
@NotNull
74+
public <W extends JsonWrapper> W sendSync(@NotNull JsonMercuryRequest<W> request) throws IOException, MercuryException {
75+
Response resp = sendSync(request.request);
76+
if (resp.statusCode >= 200 && resp.statusCode < 300) return request.instantiate(resp);
77+
else throw new MercuryException(resp);
78+
}
79+
7380
@NotNull
7481
public <P extends AbstractMessageLite> P sendSync(@NotNull ProtobufMercuryRequest<P> request) throws IOException, MercuryException {
7582
Response resp = sendSync(request.request);

core/src/main/java/xyz/gianlu/librespot/mercury/MercuryRequests.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import com.google.gson.JsonObject;
66
import com.google.protobuf.AbstractMessage;
77
import com.google.protobuf.ProtocolStringList;
8+
import org.jetbrains.annotations.Contract;
89
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.Nullable;
911
import xyz.gianlu.librespot.common.Utils;
1012
import xyz.gianlu.librespot.common.proto.Mercury;
1113
import xyz.gianlu.librespot.common.proto.Metadata;
@@ -262,4 +264,50 @@ public static ProtobufMercuryRequest<Mercury.MercuryMultiGetReply> multiGet(@Not
262264
request.addProtobufPayload(multi.build());
263265
return new ProtobufMercuryRequest<>(request.build(), Mercury.MercuryMultiGetReply.parser());
264266
}
267+
268+
@NotNull
269+
public static JsonMercuryRequest<ResolvedContextWrapper> resolveContext(@NotNull String uri) {
270+
return new JsonMercuryRequest<>(RawMercuryRequest.get(String.format("hm://context-resolve/v1/%s", uri)), ResolvedContextWrapper.class);
271+
}
272+
273+
@NotNull
274+
private static String getAsString(@NotNull JsonObject obj, @NotNull String key) {
275+
JsonElement elm = obj.get(key);
276+
if (elm == null) throw new NullPointerException("Unexpected null value for " + key);
277+
else return elm.getAsString();
278+
}
279+
280+
@Contract("_, _, !null -> !null")
281+
private static String getAsString(@NotNull JsonObject obj, @NotNull String key, @Nullable String fallback) {
282+
JsonElement elm = obj.get(key);
283+
if (elm == null) return fallback;
284+
else return elm.getAsString();
285+
}
286+
287+
public static final class ResolvedContextWrapper extends JsonWrapper {
288+
289+
public ResolvedContextWrapper(@NotNull JsonElement elm) {
290+
super(elm);
291+
}
292+
293+
@NotNull
294+
public JsonArray pages() {
295+
return obj().getAsJsonArray("pages");
296+
}
297+
298+
@NotNull
299+
public JsonObject metadata() {
300+
return obj().getAsJsonObject("metadata");
301+
}
302+
303+
@NotNull
304+
public String uri() {
305+
return getAsString(obj(), "uri");
306+
}
307+
308+
@NotNull
309+
public String url() {
310+
return getAsString(obj(), "url");
311+
}
312+
}
265313
}

core/src/main/java/xyz/gianlu/librespot/mercury/model/TrackId.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,13 @@ public static TrackId fromBase62(@NotNull String base62) {
3939

4040
@NotNull
4141
public static TrackId fromTrackRef(@NotNull Spirc.TrackRef ref) {
42-
return new TrackId(Utils.bytesToHex(ref.getGid().toByteArray()));
42+
if (ref.hasGid()) {
43+
return new TrackId(Utils.bytesToHex(ref.getGid().toByteArray()));
44+
} else if (ref.hasUri()) {
45+
return fromUri(ref.getUri());
46+
} else {
47+
throw new IllegalArgumentException("Not enough data to extract the track ID!");
48+
}
4349
}
4450

4551
@NotNull
@@ -56,4 +62,8 @@ public static TrackId fromHex(@NotNull String hex) {
5662
public @NotNull String toSpotifyUri() {
5763
return "spotify:track:" + new String(BASE62.encode(new BigInteger(hexId, 16).toByteArray()));
5864
}
65+
66+
public byte[] getGid() {
67+
return Utils.toByteArray(new BigInteger(hexId, 16));
68+
}
5969
}

core/src/main/java/xyz/gianlu/librespot/player/Player.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package xyz.gianlu.librespot.player;
22

3+
import com.google.gson.JsonArray;
4+
import com.google.gson.JsonObject;
5+
import com.google.protobuf.ByteString;
36
import org.apache.log4j.Logger;
47
import org.jetbrains.annotations.NotNull;
58
import xyz.gianlu.librespot.common.Utils;
69
import xyz.gianlu.librespot.common.proto.Metadata;
710
import xyz.gianlu.librespot.common.proto.Spirc;
811
import xyz.gianlu.librespot.core.Session;
12+
import xyz.gianlu.librespot.mercury.MercuryClient;
13+
import xyz.gianlu.librespot.mercury.MercuryRequests;
14+
import xyz.gianlu.librespot.mercury.model.TrackId;
915
import xyz.gianlu.librespot.spirc.FrameListener;
1016
import xyz.gianlu.librespot.spirc.SpotifyIrc;
1117

@@ -192,8 +198,35 @@ private void shuffleTracks() {
192198
private void handleShuffle() {
193199
if (state.getShuffle()) {
194200
shuffleTracks();
195-
stateUpdated();
201+
} else {
202+
String contextUri = state.getContextUri();
203+
204+
JsonArray tracks;
205+
try {
206+
MercuryRequests.ResolvedContextWrapper context = session.mercury().sendSync(MercuryRequests.resolveContext(contextUri));
207+
tracks = context.pages().get(0).getAsJsonObject().getAsJsonArray("tracks");
208+
} catch (IOException | MercuryClient.MercuryException ex) {
209+
LOGGER.fatal("Failed requesting context!", ex);
210+
return;
211+
}
212+
213+
int num = Math.min(100, tracks.size());
214+
List<Spirc.TrackRef> rebuildState = new ArrayList<>(num);
215+
for (int i = 0; i < num; i++) {
216+
JsonObject track = tracks.get(i).getAsJsonObject();
217+
rebuildState.add(Spirc.TrackRef.newBuilder()
218+
.setGid(ByteString.copyFrom(TrackId.fromUri(track.get("uri").getAsString()).getGid()))
219+
.build());
220+
}
221+
222+
Spirc.TrackRef current = state.getTrack(state.getPlayingTrackIndex());
223+
224+
state.clearTrack();
225+
state.addAllTrack(rebuildState);
226+
state.setPlayingTrackIndex(Utils.indexOf(rebuildState, current));
196227
}
228+
229+
stateUpdated();
197230
}
198231

199232
private void handleSeek(int pos) {

0 commit comments

Comments
 (0)