Skip to content

Commit 0a08ba3

Browse files
authored
Revamped logic behind state keeping (#99)
* Started implementation of #95 * Going away from providers + more StateWrapper logic * Load restrictions * Handle context update * A lot of broken stuff, but also quite done * Fixed shuffle/unshuffle * More fixes + handling unsupported playable correctly * Cleaned up shuffling algorithm + fixed shuffle/unshuffle (for real hopefully) * Check that context is finite before loading all tracks as it's not possible otherwise (#99, issuecomment-498045060) * Minor fixes
1 parent ce45435 commit 0a08ba3

40 files changed

Lines changed: 1079 additions & 904 deletions

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*
1010
* @author Sebastian Ruhleder, [email protected]
1111
*/
12-
public class Base62 {
12+
public final class Base62 {
1313
private static final int STANDARD_BASE = 256;
1414
private static final int TARGET_BASE = 62;
1515
private final byte[] alphabet;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package xyz.gianlu.librespot.common;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
import java.util.Collections;
6+
import java.util.List;
7+
import java.util.Random;
8+
9+
/**
10+
* @author Gianlu
11+
*/
12+
public final class FisherYatesShuffle<I> {
13+
private final Random random;
14+
private volatile long currentSeed;
15+
private volatile int sizeForSeed = -1;
16+
17+
public FisherYatesShuffle(@NotNull Random random) {
18+
this.random = random;
19+
}
20+
21+
private static int[] getShuffleExchanges(int size, long seed) {
22+
int[] exchanges = new int[size - 1];
23+
Random rand = new Random(seed);
24+
for (int i = size - 1; i > 0; i--) {
25+
int n = rand.nextInt(i + 1);
26+
exchanges[size - 1 - i] = n;
27+
}
28+
29+
return exchanges;
30+
}
31+
32+
public void shuffle(@NotNull List<I> list, boolean saveSeed) {
33+
shuffle(list, 0, list.size(), saveSeed);
34+
}
35+
36+
/**
37+
* Shuffle the given list.
38+
*
39+
* @param list the list.
40+
* @param from lower bound index (inclusive).
41+
* @param to top bound index (exclusive).
42+
* @param saveSeed whether the seed should be saved.
43+
*/
44+
public void shuffle(@NotNull List<I> list, int from, int to, boolean saveSeed) {
45+
long seed = random.nextLong();
46+
if (saveSeed) currentSeed = seed;
47+
48+
int size = to - from;
49+
if (saveSeed) sizeForSeed = size;
50+
51+
int[] exchanges = getShuffleExchanges(size, seed);
52+
for (int i = size - 1; i > 0; i--) {
53+
int n = exchanges[size - 1 - i];
54+
Collections.swap(list, from + n, from + i);
55+
}
56+
}
57+
58+
public void unshuffle(@NotNull List<I> list) {
59+
unshuffle(list, 0, list.size());
60+
}
61+
62+
/**
63+
* Unshuffle the give list. The seed will be zeroed after this call.
64+
*
65+
* @param list the list.
66+
* @param from lower bound index (inclusive).
67+
* @param to top bound index (exclusive).
68+
*/
69+
public void unshuffle(@NotNull List<I> list, int from, int to) {
70+
if (currentSeed == 0) throw new IllegalStateException("Current seed is zero!");
71+
if (sizeForSeed != to - from) throw new IllegalStateException("Size mismatch! Cannot unshuffle.");
72+
73+
int size = to - from;
74+
int[] exchanges = getShuffleExchanges(size, currentSeed);
75+
for (int i = 1; i < size; i++) {
76+
int n = exchanges[size - i - 1];
77+
Collections.swap(list, from + n, from + i);
78+
}
79+
80+
currentSeed = 0;
81+
sizeForSeed = -1;
82+
}
83+
84+
public boolean canUnshuffle(int size) {
85+
return currentSeed != 0 && sizeForSeed == size;
86+
}
87+
}

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
import org.jetbrains.annotations.NotNull;
44

55
import java.io.IOException;
6-
import java.io.InputStream;
7-
import java.util.HashMap;
8-
import java.util.Map;
96

107
/**
118
* @author Gianlu
@@ -30,18 +27,6 @@ public static StatusLine parseStatusLine(@NotNull String line) throws IOExceptio
3027
}
3128
}
3229

33-
@NotNull
34-
public static Map<String, String> parseHeaders(@NotNull InputStream in) throws IOException {
35-
Map<String, String> headers = new HashMap<>();
36-
String header;
37-
while (!(header = Utils.readLine(in)).isEmpty()) {
38-
String[] split = Utils.split(header, ':');
39-
headers.put(split[0], split[1].trim());
40-
}
41-
42-
return headers;
43-
}
44-
4530
public static class StatusLine {
4631
public final String httpVersion;
4732
public final int statusCode;

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
/**
1414
* @author Gianlu
1515
*/
16-
public class ProtobufToJson {
16+
public final class ProtobufToJson {
17+
18+
private ProtobufToJson() {
19+
}
1720

1821
@NotNull
1922
public static JsonObject convert(@NotNull Message message) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package xyz.gianlu.librespot.common;
2+
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.ArrayList;
7+
import java.util.Arrays;
8+
import java.util.List;
9+
import java.util.Random;
10+
11+
/**
12+
* @author Gianlu
13+
*/
14+
class FisherYatesTest {
15+
private final List<String> original = Arrays.asList("A", "B", "C", "D", "E", "F", "G", "H");
16+
17+
@Test
18+
void testShuffle() {
19+
FisherYatesShuffle<String> fy = new FisherYatesShuffle<>(new Random());
20+
List<String> list = new ArrayList<>(original);
21+
System.out.println(list);
22+
23+
fy.shuffle(list, 2, 6, true);
24+
System.out.println(list);
25+
26+
fy.unshuffle(list, 2, 6);
27+
System.out.println(list);
28+
29+
Assertions.assertIterableEquals(list, original);
30+
}
31+
}

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,35 @@ public <P extends Message> ProtoWrapperResponse<P> sendSync(@NotNull ProtobufMer
9090
throw new MercuryException(resp);
9191
}
9292

93+
public <W extends JsonWrapper> void send(@NotNull JsonMercuryRequest<W> request, @NotNull JsonCallback<W> callback) {
94+
try {
95+
send(request.request, resp -> {
96+
if (resp.statusCode >= 200 && resp.statusCode < 300) callback.response(request.instantiate(resp));
97+
else callback.exception(new MercuryException(resp));
98+
});
99+
} catch (IOException ex) {
100+
callback.exception(ex);
101+
}
102+
}
103+
104+
public <P extends Message> void send(@NotNull ProtobufMercuryRequest<P> request, @NotNull ProtoCallback<P> callback) {
105+
try {
106+
send(request.request, resp -> {
107+
if (resp.statusCode >= 200 && resp.statusCode < 300) {
108+
try {
109+
callback.response(new ProtoWrapperResponse<>(request.parser.parseFrom(resp.payload.stream())));
110+
} catch (InvalidProtocolBufferException ex) {
111+
callback.exception(ex);
112+
}
113+
} else {
114+
callback.exception(new MercuryException(resp));
115+
}
116+
});
117+
} catch (IOException ex) {
118+
callback.exception(ex);
119+
}
120+
}
121+
93122
public void send(@NotNull RawMercuryRequest request, @NotNull Callback callback) throws IOException {
94123
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
95124
DataOutputStream out = new DataOutputStream(bytesOut);
@@ -238,6 +267,18 @@ public void close() {
238267
super.close();
239268
}
240269

270+
public interface JsonCallback<W extends JsonWrapper> {
271+
void response(@NotNull W json);
272+
273+
void exception(@NotNull Exception ex);
274+
}
275+
276+
public interface ProtoCallback<M extends Message> {
277+
void response(@NotNull ProtoWrapperResponse<M> proto);
278+
279+
void exception(@NotNull Exception ex);
280+
}
281+
241282
public interface Callback {
242283
void response(@NotNull Response response);
243284
}

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

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@
33
import com.google.gson.JsonArray;
44
import com.google.gson.JsonElement;
55
import com.google.gson.JsonObject;
6-
import com.google.protobuf.ByteString;
76
import org.jetbrains.annotations.Contract;
87
import org.jetbrains.annotations.NotNull;
98
import org.jetbrains.annotations.Nullable;
109
import xyz.gianlu.librespot.common.proto.Mercury;
1110
import xyz.gianlu.librespot.common.proto.Metadata;
1211
import xyz.gianlu.librespot.common.proto.Playlist4Changes;
13-
import xyz.gianlu.librespot.common.proto.Spirc;
1412
import xyz.gianlu.librespot.mercury.model.*;
1513
import xyz.gianlu.librespot.player.remote.Remote3Page;
14+
import xyz.gianlu.librespot.player.remote.Remote3Track;
1615

1716
import java.util.ArrayList;
1817
import java.util.List;
@@ -124,17 +123,10 @@ public String uri() {
124123
}
125124

126125
@NotNull
127-
public List<Spirc.TrackRef> tracks() {
126+
public List<Remote3Track> tracks() {
128127
JsonArray array = obj.getAsJsonArray("tracks");
129-
List<Spirc.TrackRef> list = new ArrayList<>(array.size());
130-
for (JsonElement elm : array) {
131-
String uri = getAsString(elm.getAsJsonObject(), "uri");
132-
list.add(Spirc.TrackRef.newBuilder()
133-
.setUri(uri)
134-
.setGid(ByteString.copyFrom(TrackId.fromUri(uri).getGid()))
135-
.build());
136-
}
137-
128+
List<Remote3Track> list = new ArrayList<>(array.size());
129+
for (JsonElement elm : array) list.add(new Remote3Track(elm.getAsJsonObject()));
138130
return list;
139131
}
140132
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,9 @@ public static EpisodeId fromHex(@NotNull String hex) {
7272
public byte[] getGid() {
7373
return Utils.hexToBytes(hexId);
7474
}
75+
76+
@Override
77+
public String toString() {
78+
return "EpisodeId{" + toSpotifyUri() + '}';
79+
}
7580
}

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

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
public interface PlayableId {
1212
@NotNull
1313
static PlayableId fromUri(@NotNull String uri) {
14+
if (!isSupported(uri)) return new UnsupportedId(uri);
15+
1416
if (TrackId.PATTERN.matcher(uri).matches()) {
1517
return TrackId.fromUri(uri);
1618
} else if (EpisodeId.PATTERN.matcher(uri).matches()) {
@@ -20,26 +22,16 @@ static PlayableId fromUri(@NotNull String uri) {
2022
}
2123
}
2224

23-
static boolean isSupported(@NotNull String uri) {
24-
return !uri.startsWith("spotify:local:") && !uri.equals("spotify:delimiter");
25-
}
26-
27-
static int removeUnsupported(@NotNull List<Remote3Track> tracks, int updateIndex) {
28-
for (int i = tracks.size() - 1; i >= 0; i--) {
29-
Remote3Track track = tracks.get(i);
30-
if (!isSupported(track.uri)) {
31-
tracks.remove(i);
25+
static boolean hasAtLeastOneSupportedId(@NotNull List<Remote3Track> tracks) {
26+
for (Remote3Track track : tracks)
27+
if (track.isSupported())
28+
return true;
3229

33-
if (updateIndex != -1) {
34-
if (updateIndex == i) updateIndex = -1;
35-
36-
if (updateIndex > i)
37-
updateIndex--;
38-
}
39-
}
40-
}
30+
return false;
31+
}
4132

42-
return updateIndex;
33+
static boolean isSupported(@NotNull String uri) {
34+
return !uri.startsWith("spotify:local:") && !uri.equals("spotify:delimiter");
4335
}
4436

4537
@NotNull byte[] getGid();

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,9 @@ public static TrackId fromHex(@NotNull String hex) {
7272
public byte[] getGid() {
7373
return Utils.hexToBytes(hexId);
7474
}
75+
76+
@Override
77+
public String toString() {
78+
return "TrackId{" + toSpotifyUri() + '}';
79+
}
7580
}

0 commit comments

Comments
 (0)