Skip to content

Commit eff9c47

Browse files
committed
Added playback events listener
1 parent 142f409 commit eff9c47

1 file changed

Lines changed: 87 additions & 5 deletions

File tree

  • core/src/main/java/xyz/gianlu/librespot/player

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

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@
2525
import java.io.Closeable;
2626
import java.io.File;
2727
import java.io.IOException;
28+
import java.util.ArrayList;
2829
import java.util.List;
2930
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;
31+
import java.util.concurrent.*;
3432

3533
import static spotify.player.proto.ContextTrackOuterClass.ContextTrack;
3634

@@ -43,6 +41,7 @@ public class Player implements Closeable, DeviceStateHandler.Listener, PlayerRun
4341
private final Session session;
4442
private final Configuration conf;
4543
private final PlayerRunner runner;
44+
private final EventsDispatcher events = new EventsDispatcher();
4645
private StateWrapper state;
4746
private TrackHandler trackHandler;
4847
private TrackHandler crossfadeHandler;
@@ -55,6 +54,14 @@ public Player(@NotNull Player.Configuration conf, @NotNull Session session) {
5554
new Thread(runner = new PlayerRunner(session, conf, this), "player-runner-" + runner.hashCode()).start();
5655
}
5756

57+
public void addEventsListener(@NotNull EventsListener listener) {
58+
events.listeners.add(listener);
59+
}
60+
61+
public void removeEventsListener(@NotNull EventsListener listener) {
62+
events.listeners.remove(listener);
63+
}
64+
5865
public void initState() {
5966
this.state = new StateWrapper(session);
6067
state.addListener(this);
@@ -108,6 +115,8 @@ public void load(@NotNull String uri, boolean play) {
108115
}
109116

110117
loadTrack(play, PushToMixerReason.None);
118+
events.dispatchContextChanged();
119+
events.dispatchTrackChanged();
111120
}
112121

113122
private void transferState(TransferStateOuterClass.@NotNull TransferState cmd) {
@@ -126,6 +135,8 @@ private void transferState(TransferStateOuterClass.@NotNull TransferState cmd) {
126135
}
127136

128137
loadTrack(!cmd.getPlayback().getIsPaused(), PushToMixerReason.None);
138+
events.dispatchContextChanged();
139+
events.dispatchTrackChanged();
129140
}
130141

131142
private void handleLoad(@NotNull JsonObject obj) {
@@ -146,6 +157,9 @@ private void handleLoad(@NotNull JsonObject obj) {
146157
Boolean play = PlayCommandHelper.isInitiallyPaused(obj);
147158
if (play == null) play = true;
148159
loadTrack(play, PushToMixerReason.None);
160+
161+
events.dispatchContextChanged();
162+
events.dispatchTrackChanged();
149163
}
150164

151165
@Override
@@ -408,7 +422,12 @@ private void loadTrack(boolean play, @NotNull PushToMixerReason reason) {
408422
state.updated();
409423
}
410424

411-
if (!play) runner.pauseMixer();
425+
if (!play) {
426+
runner.pauseMixer();
427+
events.dispatchPlaybackPaused();
428+
} else {
429+
events.dispatchPlaybackResumed();
430+
}
412431
} else {
413432
if (preloadTrackHandler != null && preloadTrackHandler.isTrack(id)) {
414433
trackHandler = preloadTrackHandler;
@@ -433,6 +452,9 @@ private void loadTrack(boolean play, @NotNull PushToMixerReason reason) {
433452
if (play) {
434453
trackHandler.pushToMixer(reason);
435454
runner.playMixer();
455+
events.dispatchPlaybackResumed();
456+
} else {
457+
events.dispatchPlaybackPaused();
436458
}
437459
}
438460

@@ -454,6 +476,7 @@ private void handleResume() {
454476
}
455477

456478
state.updated();
479+
events.dispatchPlaybackResumed();
457480

458481
if (releaseLineFuture != null) {
459482
releaseLineFuture.cancel(true);
@@ -473,6 +496,7 @@ private void handlePause() {
473496
}
474497

475498
state.updated();
499+
events.dispatchPlaybackPaused();
476500

477501
if (releaseLineFuture != null) releaseLineFuture.cancel(true);
478502
releaseLineFuture = scheduler.schedule(() -> {
@@ -507,6 +531,7 @@ private void handleNext(@Nullable JsonObject obj) {
507531
if (track != null) {
508532
state.skipTo(track);
509533
loadTrack(true, PushToMixerReason.Next);
534+
events.dispatchTrackChanged();
510535
return;
511536
}
512537

@@ -519,6 +544,7 @@ private void handleNext(@Nullable JsonObject obj) {
519544
if (next.isOk()) {
520545
state.setPosition(0);
521546
loadTrack(next == NextPlayable.OK_PLAY || next == NextPlayable.OK_REPEAT, PushToMixerReason.Next);
547+
events.dispatchTrackChanged();
522548
} else {
523549
LOGGER.fatal("Failed loading next song: " + next);
524550
panicState();
@@ -543,6 +569,8 @@ private void loadAutoplay() {
543569
state.setContextMetadata("context_description", contextDesc);
544570

545571
loadTrack(true, PushToMixerReason.None);
572+
events.dispatchContextChanged();
573+
events.dispatchTrackChanged();
546574

547575
LOGGER.debug(String.format("Loading context for autoplay, uri: %s", newContext));
548576
} else if (resp.statusCode == 204) {
@@ -551,6 +579,8 @@ private void loadAutoplay() {
551579
state.setContextMetadata("context_description", contextDesc);
552580

553581
loadTrack(true, PushToMixerReason.None);
582+
events.dispatchContextChanged();
583+
events.dispatchTrackChanged();
554584

555585
LOGGER.debug(String.format("Loading context for autoplay (using radio-apollo), uri: %s", state.getContextUri()));
556586
} else {
@@ -575,6 +605,7 @@ private void handlePrev() {
575605
if (prev.isOk()) {
576606
state.setPosition(0);
577607
loadTrack(true, PushToMixerReason.Prev);
608+
events.dispatchTrackChanged();
578609
} else {
579610
LOGGER.fatal("Failed loading previous song: " + prev);
580611
panicState();
@@ -662,4 +693,55 @@ public interface Configuration {
662693

663694
int releaseLineDelay();
664695
}
696+
697+
private interface EventsListener {
698+
void onContextChanged(@NotNull String newUri);
699+
700+
void onTrackChanged(@NotNull PlayableId id, @Nullable Metadata.Track track, @Nullable Metadata.Episode episode);
701+
702+
void onPlaybackPaused();
703+
704+
void onPlaybackResumed();
705+
}
706+
707+
private class EventsDispatcher {
708+
private final ExecutorService executorService = Executors.newSingleThreadExecutor(new NameThreadFactory((r) -> "player-events-" + r.hashCode()));
709+
private final List<EventsListener> listeners = new ArrayList<>();
710+
711+
void dispatchPlaybackPaused() {
712+
for (EventsListener l : new ArrayList<>(listeners))
713+
executorService.execute(l::onPlaybackPaused);
714+
}
715+
716+
void dispatchPlaybackResumed() {
717+
for (EventsListener l : new ArrayList<>(listeners))
718+
executorService.execute(l::onPlaybackResumed);
719+
}
720+
721+
void dispatchContextChanged() {
722+
String uri = state.getContextUri();
723+
if (uri == null) return;
724+
725+
for (EventsListener l : new ArrayList<>(listeners))
726+
executorService.execute(() -> l.onContextChanged(uri));
727+
}
728+
729+
void dispatchTrackChanged() {
730+
PlayableId id = state.getCurrentPlayable();
731+
if (id == null) return;
732+
733+
Metadata.Track track;
734+
Metadata.Episode episode;
735+
if (trackHandler.isTrack(id)) {
736+
track = trackHandler.track();
737+
episode = trackHandler.episode();
738+
} else {
739+
track = null;
740+
episode = null;
741+
}
742+
743+
for (EventsListener l : new ArrayList<>(listeners))
744+
executorService.execute(() -> l.onTrackChanged(id, track, episode));
745+
}
746+
}
665747
}

0 commit comments

Comments
 (0)