Skip to content

Commit 01c0d3a

Browse files
committed
Merge branch 'dev'
2 parents e9ab43f + fdacbb9 commit 01c0d3a

File tree

14 files changed

+211
-39
lines changed

14 files changed

+211
-39
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ deploy:
2828
- api/target/librespot-api-jar-with-dependencies.jar
2929
- api/target/librespot-api-javadoc.jar
3030
- api/target/librespot-api-sources.jar
31-
- api-client/target/librespot-api-client-jar-with-dependencies.jar
3231
on:
3332
repo: librespot-org/librespot-java
3433
tags: true

api/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ This module depends on `librespot-core` and provides an API to interact with the
1919
### Metadata
2020
- `POST \metadata\{type}\{uri}` Retrieve metadata. `type` can be one of `episode`, `track`, `album`, `show`, `artist`, `uri` is the standard Spotify uri.
2121

22+
### Events
23+
24+
You can subscribe for players events by creating a WebSocket connection to `/events`.
25+
The currently available events are:
26+
- `contextChanged`
27+
- `trackChanged`
28+
- `playbackPaused`
29+
- `playbackResumed`
30+
2231

2332
## Examples
2433

api/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<plugin>
2020
<groupId>org.apache.maven.plugins</groupId>
2121
<artifactId>maven-assembly-plugin</artifactId>
22+
<version>3.2.0</version>
2223
<executions>
2324
<execution>
2425
<phase>package</phase>

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.undertow.server.RoutingHandler;
55
import org.apache.log4j.Logger;
66
import org.jetbrains.annotations.NotNull;
7+
import xyz.gianlu.librespot.api.handlers.EventsHandler;
78
import xyz.gianlu.librespot.api.handlers.MetadataHandler;
89
import xyz.gianlu.librespot.api.handlers.PlayerHandler;
910
import xyz.gianlu.librespot.core.Session;
@@ -23,7 +24,8 @@ public ApiServer(int port) {
2324

2425
private static void prepareHandlers(@NotNull RoutingHandler root, @NotNull Session session) {
2526
root.post("/player/{cmd}", new PlayerHandler(session))
26-
.post("/metadata/{type}/{uri}", new MetadataHandler(session));
27+
.post("/metadata/{type}/{uri}", new MetadataHandler(session))
28+
.get("/events", new EventsHandler(session));
2729
}
2830

2931
public void start(@NotNull Session session) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
public class Main {
1717

1818
public static void main(String[] args) throws IOException, MercuryClient.MercuryException, GeneralSecurityException, Session.SpotifyAuthenticationException {
19-
ApiServer server = new ApiServer(24879);
20-
2119
AbsConfiguration conf = new FileConfiguration(args);
20+
21+
ApiServer server = new ApiServer(conf.getCustomOptionInt("api.port", 24879));
2222
if (conf.authStrategy() == AuthConfiguration.Strategy.ZEROCONF) {
2323
ZeroconfServer.create(conf).addSessionListener(server::restart);
2424
} else {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package xyz.gianlu.librespot.api.handlers;
2+
3+
import com.google.gson.JsonObject;
4+
import com.spotify.metadata.proto.Metadata;
5+
import io.undertow.websockets.WebSocketConnectionCallback;
6+
import io.undertow.websockets.WebSocketProtocolHandshakeHandler;
7+
import io.undertow.websockets.core.WebSocketChannel;
8+
import io.undertow.websockets.core.WebSockets;
9+
import org.apache.log4j.Logger;
10+
import org.jetbrains.annotations.NotNull;
11+
import org.jetbrains.annotations.Nullable;
12+
import xyz.gianlu.librespot.common.ProtobufToJson;
13+
import xyz.gianlu.librespot.core.Session;
14+
import xyz.gianlu.librespot.mercury.model.PlayableId;
15+
import xyz.gianlu.librespot.player.Player;
16+
17+
public class EventsHandler extends WebSocketProtocolHandshakeHandler implements Player.EventsListener {
18+
private static final Logger LOGGER = Logger.getLogger(EventsHandler.class);
19+
20+
public EventsHandler(@NotNull Session session) {
21+
super((WebSocketConnectionCallback) (exchange, channel) -> LOGGER.info(String.format("Accepted new websocket connection from %s.", channel.getSourceAddress().getAddress())));
22+
session.player().addEventsListener(this);
23+
}
24+
25+
private void dispatch(@NotNull JsonObject obj) {
26+
for (WebSocketChannel channel : getPeerConnections())
27+
WebSockets.sendText(obj.toString(), channel, null);
28+
}
29+
30+
@Override
31+
public void onContextChanged(@NotNull String newUri) {
32+
JsonObject obj = new JsonObject();
33+
obj.addProperty("event", "contextChanged");
34+
obj.addProperty("uri", newUri);
35+
dispatch(obj);
36+
}
37+
38+
@Override
39+
public void onTrackChanged(@NotNull PlayableId id, Metadata.@Nullable Track track, Metadata.@Nullable Episode episode) {
40+
JsonObject obj = new JsonObject();
41+
obj.addProperty("event", "trackChanged");
42+
obj.addProperty("uri", id.toSpotifyUri());
43+
if (track != null) obj.add("track", ProtobufToJson.convert(track));
44+
if (episode != null) obj.add("episode", ProtobufToJson.convert(episode));
45+
dispatch(obj);
46+
}
47+
48+
@Override
49+
public void onPlaybackPaused() {
50+
JsonObject obj = new JsonObject();
51+
obj.addProperty("event", "playbackPaused");
52+
dispatch(obj);
53+
}
54+
55+
@Override
56+
public void onPlaybackResumed() {
57+
JsonObject obj = new JsonObject();
58+
obj.addProperty("event", "playbackResumed");
59+
dispatch(obj);
60+
}
61+
}

core/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<plugin>
2121
<groupId>org.apache.maven.plugins</groupId>
2222
<artifactId>maven-assembly-plugin</artifactId>
23+
<version>3.2.0</version>
2324
<executions>
2425
<execution>
2526
<phase>package</phase>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
*/
1515
public abstract class AbsConfiguration implements TimeProvider.Configuration, Player.Configuration, CacheManager.Configuration, AuthConfiguration, ZeroconfServer.Configuration {
1616

17+
public abstract int getCustomOptionInt(@NotNull String key, int fallback);
18+
1719
@Nullable
1820
public abstract String deviceName();
1921

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,12 @@ public int releaseLineDelay() {
290290
return config.get("player.releaseLineDelay");
291291
}
292292

293+
@Override
294+
public int getCustomOptionInt(@NotNull String key, int fallback) {
295+
Integer val = config.get(key);
296+
return val == null ? fallback : val;
297+
}
298+
293299
@Override
294300
public @Nullable String deviceName() {
295301
return config.get("deviceName");

core/src/main/java/xyz/gianlu/librespot/dealer/ApiClient.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,24 +74,31 @@ public Response send(@NotNull String method, @NotNull String suffix, @Nullable H
7474
IOException lastEx;
7575
do {
7676
try {
77-
return session.client().newCall(buildRequest(method, suffix, headers, body)).execute();
77+
Response resp = session.client().newCall(buildRequest(method, suffix, headers, body)).execute();
78+
if (resp.code() == 503) {
79+
lastEx = new StatusCodeException(resp);
80+
continue;
81+
}
82+
83+
return resp;
7884
} catch (IOException ex) {
7985
lastEx = ex;
8086
}
81-
} while (tries-- > 0);
87+
} while (tries-- > 1);
8288

8389
throw lastEx;
8490
}
8591

8692
@NotNull
8793
public Response send(@NotNull String method, @NotNull String suffix, @Nullable Headers headers, @Nullable RequestBody body) throws IOException, MercuryClient.MercuryException {
88-
return send(method, suffix, headers, body, 0);
94+
return send(method, suffix, headers, body, 1);
8995
}
9096

9197
public void putConnectState(@NotNull String connectionId, @NotNull Connect.PutStateRequest proto) throws IOException, MercuryClient.MercuryException {
9298
try (Response resp = send("PUT", "/connect-state/v1/devices/" + session.deviceId(), new Headers.Builder()
9399
.add("X-Spotify-Connection-Id", connectionId).build(), protoBody(proto), 5 /* We want this to succeed */)) {
94-
if (resp.code() != 200) LOGGER.warn(String.format("PUT %s returned %d", resp.request().url(), resp.code()));
100+
if (resp.code() != 200)
101+
LOGGER.warn(String.format("PUT %s returned %d. {headers: %s}", resp.request().url(), resp.code(), resp.headers()));
95102
}
96103
}
97104

0 commit comments

Comments
 (0)