Skip to content

Commit e4d80fc

Browse files
committed
Implemented autoplay functionality
1 parent 1c559d0 commit e4d80fc

File tree

8 files changed

+116
-18
lines changed

8 files changed

+116
-18
lines changed

conf.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ cache.enabled=false
2323
# Preload enabled
2424
preload.enabled=true
2525
### Player ###
26+
# Autoplay similar songs when your music ends
27+
player.autoplayEnabled=true
2628
# Preferred audio quality (VORBIS_96, VORBIS_160, VORBIS_320)
2729
player.preferredAudioQuality=VORBIS_160
2830
# Normalisation pregain

core/src/main/java/xyz/gianlu/librespot/DefaultConfiguration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public int initialVolume() {
4848
return PlayerRunner.VOLUME_MAX;
4949
}
5050

51+
@Override
52+
public boolean autoplayEnabled() {
53+
return true;
54+
}
55+
5156
@Override
5257
public boolean preloadEnabled() {
5358
return true;

core/src/main/java/xyz/gianlu/librespot/FileConfiguration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ public int initialVolume() {
146146
}
147147
}
148148

149+
@Override
150+
public boolean autoplayEnabled() {
151+
return getBoolean("player.autoplayEnabled", defaults.autoplayEnabled());
152+
}
153+
149154
@Override
150155
public @Nullable String deviceName() {
151156
return properties.getProperty("deviceName", null);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public final JsonObject obj() {
2424
public final JsonArray array() {
2525
return elm.getAsJsonArray();
2626
}
27+
28+
@Override
29+
public String toString() {
30+
return elm.toString();
31+
}
2732
}

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

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,19 @@
44
import com.google.gson.JsonElement;
55
import com.google.gson.JsonObject;
66
import com.google.protobuf.AbstractMessage;
7+
import com.google.protobuf.ByteString;
78
import com.google.protobuf.ProtocolStringList;
89
import org.jetbrains.annotations.Contract;
910
import org.jetbrains.annotations.NotNull;
1011
import org.jetbrains.annotations.Nullable;
1112
import xyz.gianlu.librespot.common.Utils;
12-
import xyz.gianlu.librespot.common.proto.Mercury;
13-
import xyz.gianlu.librespot.common.proto.Metadata;
14-
import xyz.gianlu.librespot.common.proto.Playlist4Changes;
15-
import xyz.gianlu.librespot.common.proto.Playlist4Content;
13+
import xyz.gianlu.librespot.common.proto.*;
1614
import xyz.gianlu.librespot.mercury.model.AlbumId;
1715
import xyz.gianlu.librespot.mercury.model.ArtistId;
1816
import xyz.gianlu.librespot.mercury.model.PlaylistId;
1917
import xyz.gianlu.librespot.mercury.model.TrackId;
2018

19+
import java.util.ArrayList;
2120
import java.util.List;
2221

2322
/**
@@ -265,6 +264,11 @@ public static ProtobufMercuryRequest<Mercury.MercuryMultiGetReply> multiGet(@Not
265264
return new ProtobufMercuryRequest<>(request.build(), Mercury.MercuryMultiGetReply.parser());
266265
}
267266

267+
@NotNull
268+
public static JsonMercuryRequest<StationsWrapper> getStationFor(@NotNull String context) {
269+
return new JsonMercuryRequest<>(RawMercuryRequest.get("hm://radio-apollo/v3/stations/" + context), StationsWrapper.class);
270+
}
271+
268272
@NotNull
269273
public static JsonMercuryRequest<ResolvedContextWrapper> resolveContext(@NotNull String uri) {
270274
return new JsonMercuryRequest<>(RawMercuryRequest.get(String.format("hm://context-resolve/v1/%s", uri)), ResolvedContextWrapper.class);
@@ -284,6 +288,34 @@ private static String getAsString(@NotNull JsonObject obj, @NotNull String key,
284288
else return elm.getAsString();
285289
}
286290

291+
public static final class StationsWrapper extends JsonWrapper {
292+
293+
public StationsWrapper(@NotNull JsonElement elm) {
294+
super(elm);
295+
}
296+
297+
@NotNull
298+
public String uri() {
299+
return getAsString(obj(), "uri");
300+
}
301+
302+
@NotNull
303+
public List<Spirc.TrackRef> tracks() {
304+
JsonArray array = obj().getAsJsonArray("tracks");
305+
List<Spirc.TrackRef> list = new ArrayList<>(array.size());
306+
for (JsonElement elm : array) {
307+
JsonObject obj = elm.getAsJsonObject();
308+
String uri = getAsString(obj, "uri");
309+
list.add(Spirc.TrackRef.newBuilder()
310+
.setUri(uri)
311+
.setGid(ByteString.copyFrom(TrackId.fromUri(uri).getGid()))
312+
.build());
313+
}
314+
315+
return list;
316+
}
317+
}
318+
287319
public static final class ResolvedContextWrapper extends JsonWrapper {
288320

289321
public ResolvedContextWrapper(@NotNull JsonElement elm) {

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

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
import org.apache.log4j.Logger;
44
import org.jetbrains.annotations.NotNull;
5+
import org.jetbrains.annotations.Nullable;
56
import xyz.gianlu.librespot.common.Utils;
67
import xyz.gianlu.librespot.common.proto.Spirc;
78
import xyz.gianlu.librespot.core.Session;
9+
import xyz.gianlu.librespot.mercury.MercuryClient;
10+
import xyz.gianlu.librespot.mercury.MercuryRequests;
811
import xyz.gianlu.librespot.mercury.model.TrackId;
912
import xyz.gianlu.librespot.player.tracks.PlaylistProvider;
1013
import xyz.gianlu.librespot.player.tracks.StationProvider;
@@ -206,8 +209,8 @@ private void updatedTracks(@NotNull Spirc.Frame frame) {
206209
state.update(frame);
207210
String context = frame.getState().getContextUri();
208211

209-
if (context.startsWith("spotify:station:")) tracksProvider = new StationProvider(session, state.state, frame);
210-
else tracksProvider = new PlaylistProvider(session, state.state, frame, conf);
212+
if (context.startsWith("spotify:station:")) tracksProvider = new StationProvider(session, state.state);
213+
else tracksProvider = new PlaylistProvider(session, state.state, conf);
211214

212215
state.setRepeat(frame.getState().getRepeat());
213216
state.setShuffle(frame.getState().getShuffle());
@@ -332,8 +335,18 @@ private void handleNext() {
332335
int newTrack = tracksProvider.getNextTrackIndex(true);
333336
boolean play = true;
334337
if (newTrack >= state.getTrackCount()) {
335-
newTrack = 0;
336-
play = state.getRepeat();
338+
if (state.getRepeat()) {
339+
newTrack = 0;
340+
play = true;
341+
} else {
342+
if (conf.autoplayEnabled()) {
343+
loadAutoplay();
344+
return;
345+
} else {
346+
newTrack = 0;
347+
play = false;
348+
}
349+
}
337350
}
338351

339352
state.setPlayingTrackIndex(newTrack);
@@ -343,6 +356,31 @@ private void handleNext() {
343356
loadTrack(play);
344357
}
345358

359+
private void loadAutoplay() {
360+
String context = state.getContextUri();
361+
if (context == null) {
362+
LOGGER.fatal("Cannot load autoplay with null context!");
363+
state.setStatus(Spirc.PlayStatus.kPlayStatusStop);
364+
stateUpdated();
365+
return;
366+
}
367+
368+
try {
369+
MercuryRequests.StationsWrapper json = session.mercury().sendSync(MercuryRequests.getStationFor(context));
370+
state.update(json);
371+
372+
state.setPositionMs(0);
373+
state.setPositionMeasuredAt(System.currentTimeMillis());
374+
375+
tracksProvider = new StationProvider(session, state.state);
376+
loadTrack(true);
377+
378+
LOGGER.debug(String.format("Loading context for autoplay, uri: %s", json.uri()));
379+
} catch (IOException | MercuryClient.MercuryException e) {
380+
e.printStackTrace();
381+
}
382+
}
383+
346384
private void handlePrev() {
347385
if (getPosition() < 3000) {
348386
state.setPlayingTrackIndex(tracksProvider.getPrevTrackIndex(true));
@@ -389,6 +427,8 @@ public interface Configuration {
389427
boolean logAvailableMixers();
390428

391429
int initialVolume();
430+
431+
boolean autoplayEnabled();
392432
}
393433

394434
private class StateWrapper {
@@ -398,6 +438,11 @@ private class StateWrapper {
398438
this.state = state;
399439
}
400440

441+
@Nullable
442+
String getContextUri() {
443+
return state.getContextUri();
444+
}
445+
401446
@NotNull
402447
Spirc.PlayStatus getStatus() {
403448
return state.getStatus();
@@ -421,6 +466,18 @@ void setShuffle(boolean shuffle) {
421466

422467
void update(@NotNull Spirc.Frame frame) {
423468
state.setContextUri(frame.getState().getContextUri());
469+
470+
state.setPlayingTrackIndex(frame.getState().getPlayingTrackIndex());
471+
state.clearTrack();
472+
state.addAllTrack(frame.getState().getTrackList());
473+
}
474+
475+
void update(@NotNull MercuryRequests.StationsWrapper json) {
476+
state.setContextUri(json.uri());
477+
478+
state.setPlayingTrackIndex(0);
479+
state.clearTrack();
480+
state.addAllTrack(json.tracks());
424481
}
425482

426483
long getPositionMeasuredAt() {

core/src/main/java/xyz/gianlu/librespot/player/tracks/PlaylistProvider.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,10 @@ public class PlaylistProvider implements TracksProvider {
2626
private final MercuryClient mercury;
2727
private long shuffleSeed = 0;
2828

29-
public PlaylistProvider(@NotNull Session session, @NotNull Spirc.State.Builder state, @NotNull Spirc.Frame frame, @NotNull Player.Configuration conf) {
29+
public PlaylistProvider(@NotNull Session session, @NotNull Spirc.State.Builder state, @NotNull Player.Configuration conf) {
3030
this.state = state;
3131
this.conf = conf;
3232
this.mercury = session.mercury();
33-
34-
state.setPlayingTrackIndex(frame.getState().getPlayingTrackIndex());
35-
state.clearTrack();
36-
state.addAllTrack(frame.getState().getTrackList());
3733
}
3834

3935
private static int[] getShuffleExchanges(int size, long seed) {

core/src/main/java/xyz/gianlu/librespot/player/tracks/StationProvider.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,9 @@ public class StationProvider implements TracksProvider {
2727
private final Spirc.State.Builder state;
2828
private String nextPageUri;
2929

30-
public StationProvider(@NotNull Session session, @NotNull Spirc.State.Builder state, @NotNull Spirc.Frame frame) {
30+
public StationProvider(@NotNull Session session, @NotNull Spirc.State.Builder state) {
3131
this.mercury = session.mercury();
3232
this.state = state;
33-
34-
state.setPlayingTrackIndex(frame.getState().getPlayingTrackIndex());
35-
state.clearTrack();
36-
state.addAllTrack(frame.getState().getTrackList());
3733
}
3834

3935
@Override

0 commit comments

Comments
 (0)