Skip to content

Commit 9c9842a

Browse files
committed
Added connectionDropped and connectionEstablished events + better response codes for API + updated api/README (#172)
1 parent 71a8dbb commit 9c9842a

5 files changed

Lines changed: 70 additions & 24 deletions

File tree

api/README.md

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
This module depends on `librespot-core` and provides an API to interact with the Spotify client.
55

66
## Available endpoints
7-
8-
All the endpoints will respond with `200` if successful or `204` if there isn't any active session.
7+
All the endpoints will respond with `200` if successful or:
8+
- `204`, if there isn't any active session (Zeroconf only)
9+
- `500`, if the session is invalid
10+
- `503`, if the session is reconnecting (`Retry-After` is always 10 seconds)
911

1012
### Player
11-
- `POST \player\load` Load a track from a given uri. The request body should contain two parameters: `uri` and `play`.
13+
- `POST \player\load` Load a track from a given URI. The request body should contain two parameters: `uri` and `play`.
1214
- `POST \player\pause` Pause playback.
1315
- `POST \player\resume` Resume playback.
1416
- `POST \player\next` Skip to next track.
@@ -28,22 +30,22 @@ All the endpoints will respond with `200` if successful or `204` if there isn't
2830
- `POST \token\{scope}` Request an access token for a specific scope.
2931

3032
### Events
31-
3233
You can subscribe for players events by creating a WebSocket connection to `/events`.
3334
The currently available events are:
34-
- `contextChanged`
35-
- `trackChanged`
36-
- `playbackPaused`
37-
- `playbackResumed`
38-
- `trackSeeked`
39-
- `metadataAvailable`
40-
- `playbackHaltStateChanged`
41-
- `sessionCleared`
42-
- `sessionChanged`
43-
- `inactiveSession`
35+
- `contextChanged`, the Spotify context URI changed
36+
- `trackChanged`, the Spotify track URI changed
37+
- `playbackPaused`, playback has been paused
38+
- `playbackResumed`, playback has been resumed
39+
- `trackSeeked`, track has been seeked
40+
- `metadataAvailable`, metadata for the current track is available
41+
- `playbackHaltStateChanged`, playback halted or resumed from halt
42+
- `sessionCleared`, (Zeroconf only) current session went away
43+
- `sessionChanged`, (Zeroconf only) current session changed
44+
- `inactiveSession`, current session is now inactive (no audio)
45+
- `connectionDropped`, a network error occurred and we're trying to reconnect
46+
- `connectionEstablished`, successfully reconnected
4447

4548
## Examples
46-
4749
`curl -X POST -d "uri=spotify:track:xxxxxxxxxxxxxxxxxxxxxx&play=true" http://localhost:24879/player/load`
4850

4951
`curl -X POST http://localhost:24879/metadata/track/spotify:track:xxxxxxxxxxxxxxxxxxxxxx`

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.undertow.server.HttpHandler;
44
import io.undertow.server.HttpServerExchange;
5+
import io.undertow.util.Headers;
56
import io.undertow.util.StatusCodes;
67
import org.jetbrains.annotations.NotNull;
78
import xyz.gianlu.librespot.api.SessionWrapper;
@@ -25,6 +26,17 @@ public final void handleRequest(HttpServerExchange exchange) throws Exception {
2526
return;
2627
}
2728

29+
if (s.reconnecting()) {
30+
exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE);
31+
exchange.getResponseHeaders().add(Headers.RETRY_AFTER, 10);
32+
return;
33+
}
34+
35+
if (!s.valid()) {
36+
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
37+
return;
38+
}
39+
2840
handleRequest(exchange, s);
2941
}
3042

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import xyz.gianlu.librespot.mercury.model.PlayableId;
1616
import xyz.gianlu.librespot.player.Player;
1717

18-
public final class EventsHandler extends WebSocketProtocolHandshakeHandler implements Player.EventsListener, SessionWrapper.Listener {
18+
public final class EventsHandler extends WebSocketProtocolHandshakeHandler implements Player.EventsListener, SessionWrapper.Listener, Session.ReconnectionListener {
1919
private static final Logger LOGGER = Logger.getLogger(EventsHandler.class);
2020

2121
public EventsHandler() {
@@ -110,5 +110,20 @@ public void onNewSession(@NotNull Session session) {
110110
dispatch(obj);
111111

112112
session.player().addEventsListener(this);
113+
session.addReconnectionListener(this);
114+
}
115+
116+
@Override
117+
public void onConnectionDropped() {
118+
JsonObject obj = new JsonObject();
119+
obj.addProperty("event", "connectionDropped");
120+
dispatch(obj);
121+
}
122+
123+
@Override
124+
public void onConnectionEstablished() {
125+
JsonObject obj = new JsonObject();
126+
obj.addProperty("event", "connectionEstablished");
127+
dispatch(obj);
113128
}
114129
}

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,6 @@ protected void appendToQueue(@NotNull Packet packet) {
4545

4646
protected abstract void exception(@NotNull Exception ex);
4747

48-
private static final class LooperException extends Exception {
49-
private LooperException(Throwable cause) {
50-
super(cause);
51-
}
52-
}
53-
5448
private final class Looper implements Runnable {
5549
private volatile boolean shouldStop = false;
5650

@@ -66,8 +60,7 @@ public void run() {
6660
exception(ex);
6761
}
6862
});
69-
} catch (InterruptedException ex) {
70-
executorService.execute(() -> exception(new LooperException(ex)));
63+
} catch (InterruptedException ignored) {
7164
}
7265
}
7366
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public final class Session implements Closeable {
7676
private final AtomicBoolean authLock = new AtomicBoolean(false);
7777
private final OkHttpClient client;
7878
private final List<CloseListener> closeListeners = Collections.synchronizedList(new ArrayList<>());
79+
private final List<ReconnectionListener> reconnectionListeners = Collections.synchronizedList(new ArrayList<>());
7980
private ConnectionHolder conn;
8081
private CipherPair cipherPair;
8182
private Receiver receiver;
@@ -528,6 +529,10 @@ public boolean valid() {
528529
return apWelcome != null && conn != null && !conn.socket.isClosed();
529530
}
530531

532+
public boolean reconnecting() {
533+
return !closed && conn == null;
534+
}
535+
531536
@NotNull
532537
public String deviceId() {
533538
return inner.deviceId;
@@ -554,6 +559,10 @@ public Random random() {
554559
}
555560

556561
private void reconnect() {
562+
synchronized (reconnectionListeners) {
563+
reconnectionListeners.forEach(ReconnectionListener::onConnectionDropped);
564+
}
565+
557566
try {
558567
if (conn != null) {
559568
conn.socket.close();
@@ -569,7 +578,12 @@ private void reconnect() {
569578
.build(), true);
570579

571580
LOGGER.info(String.format("Re-authenticated as %s!", apWelcome.getCanonicalUsername()));
581+
582+
synchronized (reconnectionListeners) {
583+
reconnectionListeners.forEach(ReconnectionListener::onConnectionEstablished);
584+
}
572585
} catch (IOException | GeneralSecurityException | SpotifyAuthenticationException ex) {
586+
conn = null;
573587
LOGGER.error("Failed reconnecting, retrying in 10 seconds...", ex);
574588
scheduler.schedule(this::reconnect, 10, TimeUnit.SECONDS);
575589
}
@@ -589,6 +603,16 @@ public void addCloseListener(@NotNull CloseListener listener) {
589603
if (!closeListeners.contains(listener)) closeListeners.add(listener);
590604
}
591605

606+
public void addReconnectionListener(@NotNull ReconnectionListener listener) {
607+
if (!reconnectionListeners.contains(listener)) reconnectionListeners.add(listener);
608+
}
609+
610+
public interface ReconnectionListener {
611+
void onConnectionDropped();
612+
613+
void onConnectionEstablished();
614+
}
615+
592616
public interface ProxyConfiguration {
593617
boolean proxyEnabled();
594618

0 commit comments

Comments
 (0)