Skip to content

Commit 0663079

Browse files
committed
Merge branch 'dev' into use-zeroconf-java
# Conflicts: # core/src/main/java/xyz/gianlu/librespot/core/ZeroconfServer.java
2 parents 2df817a + a129e3b commit 0663079

10 files changed

Lines changed: 201 additions & 25 deletions

File tree

api/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This module depends on `librespot-core` and provides an API to interact with the
1414
- `POST \player\set-volume` Set volume to a given `volume` value from 0 to 65536.
1515
- `POST \player\volume-up` Up the volume a little bit.
1616
- `POST \player\volume-down` Lower the volume a little bit.
17+
- `POST \player\current` Retrieve information about the current track.
1718

1819
### Metadata
1920
- `POST \metadata\{type}\{uri}` Retrieve metadata. `type` can be one of `episode`, `track`, `album`, `show`, `artist`, `uri` is the standard Spotify uri.

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ public static void invalidParameter(@NotNull HttpServerExchange exchange, @NotNu
6060
}
6161

6262
public static void internalError(@NotNull HttpServerExchange exchange, @NotNull Exception ex) {
63+
internalError(exchange, ex.getMessage());
64+
}
65+
66+
public static void internalError(@NotNull HttpServerExchange exchange, @NotNull String reason) {
6367
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
64-
exchange.getResponseSender().send(String.format(INTERNAL_ERROR_BODY, ex.getMessage()));
68+
exchange.getResponseSender().send(String.format(INTERNAL_ERROR_BODY, reason));
6569
}
6670
}

api/src/main/java/xyz/gianlu/librespot/api/handlers/PlayerHandler.java

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

3+
import com.google.gson.JsonObject;
4+
import com.spotify.metadata.proto.Metadata;
35
import io.undertow.server.HttpHandler;
46
import io.undertow.server.HttpServerExchange;
57
import org.jetbrains.annotations.NotNull;
68
import org.jetbrains.annotations.Nullable;
79
import xyz.gianlu.librespot.api.Utils;
10+
import xyz.gianlu.librespot.common.ProtobufToJson;
811
import xyz.gianlu.librespot.core.Session;
12+
import xyz.gianlu.librespot.mercury.model.EpisodeId;
13+
import xyz.gianlu.librespot.mercury.model.PlayableId;
14+
import xyz.gianlu.librespot.mercury.model.TrackId;
915
import xyz.gianlu.librespot.player.PlayerRunner;
1016

1117
import java.util.Deque;
@@ -50,6 +56,36 @@ private void load(HttpServerExchange exchange, @Nullable String uri, boolean pla
5056
session.player().load(uri, play);
5157
}
5258

59+
private void current(HttpServerExchange exchange) {
60+
PlayableId id = session.player().currentPlayableId();
61+
62+
JsonObject obj;
63+
if (id instanceof TrackId) {
64+
Metadata.Track track = session.player().currentTrack();
65+
if (track == null) {
66+
Utils.internalError(exchange, "Missing track metadata. Try again.");
67+
return;
68+
}
69+
70+
obj = ProtobufToJson.convert(track);
71+
obj.addProperty("uri", id.toSpotifyUri());
72+
} else if (id instanceof EpisodeId) {
73+
Metadata.Episode episode = session.player().currentEpisode();
74+
if (episode == null) {
75+
Utils.internalError(exchange, "Missing episode metadata. Try again.");
76+
return;
77+
}
78+
79+
obj = ProtobufToJson.convert(episode);
80+
obj.addProperty("uri", id.toSpotifyUri());
81+
} else {
82+
Utils.internalError(exchange, "Invalid PlayableId: " + id);
83+
return;
84+
}
85+
86+
exchange.getResponseSender().send(obj.toString());
87+
}
88+
5389
@Override
5490
public void handleRequest(HttpServerExchange exchange) throws Exception {
5591
exchange.startBlocking();
@@ -72,6 +108,9 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
72108
}
73109

74110
switch (cmd) {
111+
case CURRENT:
112+
current(exchange);
113+
return;
75114
case SET_VOLUME:
76115
setVolume(exchange, Utils.getFirstString(params, "volume"));
77116
return;
@@ -104,7 +143,7 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
104143
private enum Command {
105144
LOAD("load"), PAUSE("pause"), RESUME("resume"),
106145
NEXT("next"), PREV("prev"), SET_VOLUME("set-volume"),
107-
VOLUME_UP("volume-up"), VOLUME_DOWN("volume-down");
146+
VOLUME_UP("volume-up"), VOLUME_DOWN("volume-down"), CURRENT("current");
108147

109148
private String name;
110149

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,11 @@ public int crossfadeDuration() {
285285
return config.get("player.crossfadeDuration");
286286
}
287287

288+
@Override
289+
public int releaseLineDelay() {
290+
return config.get("player.releaseLineDelay");
291+
}
292+
288293
@Override
289294
public @Nullable String deviceName() {
290295
return config.get("deviceName");

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ public class ZeroconfServer implements Closeable {
7171
DEFAULT_GET_INFO_FIELDS.addProperty("accountReq", "PREMIUM");
7272
DEFAULT_GET_INFO_FIELDS.addProperty("brandDisplayName", "librespot-org");
7373
DEFAULT_GET_INFO_FIELDS.addProperty("modelDisplayName", "librespot-java");
74+
DEFAULT_GET_INFO_FIELDS.addProperty("voiceSupport", "NO");
75+
DEFAULT_GET_INFO_FIELDS.addProperty("availability", "");
76+
DEFAULT_GET_INFO_FIELDS.addProperty("productID", 0);
77+
DEFAULT_GET_INFO_FIELDS.addProperty("tokenType", "default");
78+
DEFAULT_GET_INFO_FIELDS.addProperty("groupStatus", "NONE");
79+
DEFAULT_GET_INFO_FIELDS.addProperty("resolverVersion", "0");
80+
DEFAULT_GET_INFO_FIELDS.addProperty("scope", "streaming,client-authorization-universal");
7481

7582
DEFAULT_SUCCESSFUL_ADD_USER.addProperty("status", 101);
7683
DEFAULT_SUCCESSFUL_ADD_USER.addProperty("spotifyError", 0);
@@ -133,6 +140,7 @@ private ZeroconfServer(Session.Inner inner, Configuration conf) throws IOExcepti
133140
Map<String, String> txt = new HashMap<>();
134141
txt.put("CPath", "/");
135142
txt.put("VERSION", "1.0");
143+
txt.put("Stack", "SP");
136144
Service service = new Service(inner.deviceName, "spotify-connect", port);
137145
service.setText(txt);
138146

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ public void close() {
252252
} else {
253253
synchronized (callbacks) {
254254
try {
255-
callbacks.wait();
255+
callbacks.wait(100);
256256
} catch (InterruptedException ignored) {
257257
}
258258
}

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.jetbrains.annotations.NotNull;
88
import org.jetbrains.annotations.Nullable;
99
import spotify.player.proto.transfer.TransferStateOuterClass;
10+
import xyz.gianlu.librespot.common.NameThreadFactory;
1011
import xyz.gianlu.librespot.common.Utils;
1112
import xyz.gianlu.librespot.connectstate.DeviceStateHandler;
1213
import xyz.gianlu.librespot.connectstate.DeviceStateHandler.PlayCommandHelper;
@@ -26,6 +27,10 @@
2627
import java.io.IOException;
2728
import java.util.List;
2829
import java.util.Map;
30+
import java.util.concurrent.Executors;
31+
import java.util.concurrent.ScheduledExecutorService;
32+
import java.util.concurrent.ScheduledFuture;
33+
import java.util.concurrent.TimeUnit;
2934

3035
import static spotify.player.proto.ContextTrackOuterClass.ContextTrack;
3136

@@ -34,13 +39,15 @@
3439
*/
3540
public class Player implements Closeable, DeviceStateHandler.Listener, PlayerRunner.Listener {
3641
private static final Logger LOGGER = Logger.getLogger(Player.class);
42+
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new NameThreadFactory((r) -> "release-line-scheduler-" + r.hashCode()));
3743
private final Session session;
3844
private final Configuration conf;
3945
private final PlayerRunner runner;
4046
private StateWrapper state;
4147
private TrackHandler trackHandler;
4248
private TrackHandler crossfadeHandler;
4349
private TrackHandler preloadTrackHandler;
50+
private ScheduledFuture releaseLineFuture = null;
4451

4552
public Player(@NotNull Player.Configuration conf, @NotNull Session session) {
4653
this.conf = conf;
@@ -206,7 +213,7 @@ public void volumeChanged() {
206213

207214
@Override
208215
public void notActive() {
209-
runner.stopMixer();
216+
if (runner.stopAndRelease()) LOGGER.debug("Released line due to inactivity.");
210217
}
211218

212219
@Override
@@ -428,6 +435,11 @@ private void loadTrack(boolean play, @NotNull PushToMixerReason reason) {
428435
runner.playMixer();
429436
}
430437
}
438+
439+
if (releaseLineFuture != null) {
440+
releaseLineFuture.cancel(true);
441+
releaseLineFuture = null;
442+
}
431443
}
432444

433445
private void handleResume() {
@@ -442,6 +454,11 @@ private void handleResume() {
442454
}
443455

444456
state.updated();
457+
458+
if (releaseLineFuture != null) {
459+
releaseLineFuture.cancel(true);
460+
releaseLineFuture = null;
461+
}
445462
}
446463
}
447464

@@ -456,6 +473,13 @@ private void handlePause() {
456473
}
457474

458475
state.updated();
476+
477+
if (releaseLineFuture != null) releaseLineFuture.cancel(true);
478+
releaseLineFuture = scheduler.schedule(() -> {
479+
if (!state.isPaused()) return;
480+
481+
if (runner.pauseAndRelease()) LOGGER.debug("Released line after a period of inactivity.");
482+
}, conf.releaseLineDelay(), TimeUnit.SECONDS);
459483
}
460484
}
461485

@@ -509,18 +533,22 @@ private void loadAutoplay() {
509533
return;
510534
}
511535

536+
String contextDesc = state.getContextMetadata("context_description");
537+
512538
try {
513539
MercuryClient.Response resp = session.mercury().sendSync(MercuryRequests.autoplayQuery(context));
514540
if (resp.statusCode == 200) {
515541
String newContext = resp.payload.readIntoString(0);
516542
state.loadContext(newContext);
543+
state.setContextMetadata("context_description", contextDesc);
517544

518545
loadTrack(true, PushToMixerReason.None);
519546

520547
LOGGER.debug(String.format("Loading context for autoplay, uri: %s", newContext));
521548
} else if (resp.statusCode == 204) {
522549
MercuryRequests.StationsWrapper station = session.mercury().sendSync(MercuryRequests.getStationFor(context));
523550
state.loadContextWithTracks(station.uri(), station.tracks());
551+
state.setContextMetadata("context_description", contextDesc);
524552

525553
loadTrack(true, PushToMixerReason.None);
526554

@@ -631,5 +659,7 @@ public interface Configuration {
631659
boolean autoplayEnabled();
632660

633661
int crossfadeDuration();
662+
663+
int releaseLineDelay();
634664
}
635665
}

0 commit comments

Comments
 (0)