diff --git a/core/src/main/java/xyz/gianlu/librespot/mercury/model/TrackId.java b/core/src/main/java/xyz/gianlu/librespot/mercury/model/TrackId.java index a8c3cc6e..7d748396 100644 --- a/core/src/main/java/xyz/gianlu/librespot/mercury/model/TrackId.java +++ b/core/src/main/java/xyz/gianlu/librespot/mercury/model/TrackId.java @@ -17,7 +17,9 @@ public final class TrackId implements SpotifyId { private final String hexId; private TrackId(@NotNull String hex) { - this.hexId = hex; + if (hex.length() == 32) this.hexId = hex; + else if (hex.length() == 34 && hex.startsWith("00")) this.hexId = hex.substring(2); + else throw new IllegalArgumentException("Illegal track id: " + hex); } @NotNull diff --git a/core/src/main/java/xyz/gianlu/librespot/player/Player.java b/core/src/main/java/xyz/gianlu/librespot/player/Player.java index 8ef608d2..96ea8b5d 100644 --- a/core/src/main/java/xyz/gianlu/librespot/player/Player.java +++ b/core/src/main/java/xyz/gianlu/librespot/player/Player.java @@ -5,11 +5,14 @@ import xyz.gianlu.librespot.common.Utils; import xyz.gianlu.librespot.common.proto.Spirc; import xyz.gianlu.librespot.core.Session; +import xyz.gianlu.librespot.mercury.model.TrackId; +import xyz.gianlu.librespot.player.tracks.PlaylistProvider; +import xyz.gianlu.librespot.player.tracks.StationProvider; +import xyz.gianlu.librespot.player.tracks.TracksProvider; import xyz.gianlu.librespot.spirc.FrameListener; import xyz.gianlu.librespot.spirc.SpotifyIrc; import java.io.IOException; -import java.util.*; /** * @author Gianlu @@ -18,18 +21,18 @@ public class Player implements FrameListener, TrackHandler.Listener { private static final Logger LOGGER = Logger.getLogger(Player.class); private final Session session; private final SpotifyIrc spirc; - private final Spirc.State.Builder state; + private final StateWrapper state; private final PlayerConfiguration conf; private final CacheManager cacheManager; + private TracksProvider tracksProvider; private TrackHandler trackHandler; private TrackHandler preloadTrackHandler; - private long shuffleSeed = 0; public Player(@NotNull PlayerConfiguration conf, @NotNull CacheManager.CacheConfiguration cacheConfiguration, @NotNull Session session) { this.conf = conf; this.session = session; this.spirc = session.spirc(); - this.state = initState(); + this.state = new StateWrapper(initState()); try { this.cacheManager = new CacheManager(cacheConfiguration); @@ -40,16 +43,6 @@ public Player(@NotNull PlayerConfiguration conf, @NotNull CacheManager.CacheConf spirc.addListener(this); } - private static int[] getShuffleExchanges(int size, long seed) { - int[] exchanges = new int[size - 1]; - Random rand = new Random(seed); - for (int i = size - 1; i > 0; i--) { - int n = rand.nextInt(i + 1); - exchanges[size - 1 - i] = n; - } - return exchanges; - } - public void playPause() { handlePlayPause(); } @@ -137,8 +130,8 @@ public void frame(@NotNull Spirc.Frame frame) { } private void handlePlayPause() { - if (state.getStatus() == Spirc.PlayStatus.kPlayStatusPlay) handlePause(); - else if (state.getStatus() == Spirc.PlayStatus.kPlayStatusPause) handlePlay(); + if (state.isStatus(Spirc.PlayStatus.kPlayStatusPlay)) handlePause(); + else if (state.isStatus(Spirc.PlayStatus.kPlayStatusPause)) handlePlay(); } private void handleSetVolume(int volume) { @@ -169,7 +162,7 @@ private void handleVolumeUp() { } private void stateUpdated() { - spirc.deviceStateUpdated(state); + spirc.deviceStateUpdated(state.state); } private int getPosition() { @@ -177,50 +170,26 @@ private int getPosition() { return state.getPositionMs() + diff; } - private void shuffleTracks() { - shuffleSeed = session.random().nextLong(); - - List tracks = new ArrayList<>(state.getTrackList()); - if (state.getPlayingTrackIndex() != 0) { - Collections.swap(tracks, 0, state.getPlayingTrackIndex()); - state.setPlayingTrackIndex(0); - } - - int size = tracks.size() - 1; - int[] exchanges = getShuffleExchanges(size, shuffleSeed); - for (int i = size - 1; i > 1; i--) { - int n = exchanges[size - 1 - i]; - Collections.swap(tracks, i, n + 1); - } - - state.clearTrack(); - state.addAllTrack(tracks); - } - - private void unshuffleTracks() { - List tracks = new ArrayList<>(state.getTrackList()); - if (state.getPlayingTrackIndex() != 0) { - Collections.swap(tracks, 0, state.getPlayingTrackIndex()); - state.setPlayingTrackIndex(0); - } - - int size = tracks.size() - 1; - int[] exchanges = getShuffleExchanges(size, shuffleSeed); - for (int i = 2; i < size; i++) { - int n = exchanges[size - i - 1]; - Collections.swap(tracks, i, n + 1); - } - - state.clearTrack(); - state.addAllTrack(tracks); - } - private void handleShuffle() { if (state.getShuffle()) shuffleTracks(); else unshuffleTracks(); stateUpdated(); } + private void shuffleTracks() { + if (tracksProvider instanceof PlaylistProvider) + ((PlaylistProvider) tracksProvider).shuffleTracks(session.random()); + else + LOGGER.warn("Cannot shuffle TracksProvider: " + tracksProvider); + } + + private void unshuffleTracks() { + if (tracksProvider instanceof PlaylistProvider) + ((PlaylistProvider) tracksProvider).unshuffleTracks(); + else + LOGGER.warn("Cannot unshuffle TracksProvider: " + tracksProvider); + } + private void handleSeek(int pos) { state.setPositionMs(pos); state.setPositionMeasuredAt(System.currentTimeMillis()); @@ -229,10 +198,12 @@ private void handleSeek(int pos) { } private void updatedTracks(@NotNull Spirc.Frame frame) { - state.setPlayingTrackIndex(frame.getState().getPlayingTrackIndex()); - state.clearTrack(); - state.addAllTrack(frame.getState().getTrackList()); - state.setContextUri(frame.getState().getContextUri()); + state.update(frame); + String context = frame.getState().getContextUri(); + + if (context.startsWith("spotify:station:")) tracksProvider = new StationProvider(session, state.state, frame); + else tracksProvider = new PlaylistProvider(state.state, frame); + state.setRepeat(frame.getState().getRepeat()); state.setShuffle(frame.getState().getShuffle()); if (state.getShuffle()) shuffleTracks(); @@ -250,9 +221,9 @@ public void finishedLoading(@NotNull TrackHandler handler, boolean play) { } @Override - public void loadingError(@NotNull TrackHandler handler, @NotNull Exception ex) { + public void loadingError(@NotNull TrackHandler handler, @NotNull TrackId id, @NotNull Exception ex) { if (handler == trackHandler) { - LOGGER.fatal("Failed loading track!", ex); + LOGGER.fatal(String.format("Failed loading track, gid: %s", Utils.bytesToHex(id.getGid())), ex); state.setStatus(Spirc.PlayStatus.kPlayStatusStop); stateUpdated(); } else if (handler == preloadTrackHandler) { @@ -272,7 +243,7 @@ public void endOfTrack(@NotNull TrackHandler handler) { @Override public void preloadNextTrack(@NotNull TrackHandler handler) { if (handler == trackHandler) { - Spirc.TrackRef next = state.getTrack(getQueuedTrack(false)); + TrackId next = tracksProvider.getTrackAt(tracksProvider.getNextTrackIndex(false)); preloadTrackHandler = new TrackHandler(session, cacheManager, conf, this); preloadTrackHandler.sendLoad(next, false, 0); @@ -287,6 +258,8 @@ private void handleLoad(@NotNull Spirc.Frame frame) { .setBecameActiveAt(System.currentTimeMillis()); } + LOGGER.debug(String.format("Loading context, uri: %s", frame.getState().getContextUri())); + updatedTracks(frame); if (state.getTrackCount() > 0) { @@ -304,14 +277,14 @@ private void handleLoad(@NotNull Spirc.Frame frame) { private void loadTrack(boolean play) { if (trackHandler != null) trackHandler.close(); - Spirc.TrackRef ref = state.getTrack(state.getPlayingTrackIndex()); - if (preloadTrackHandler != null && preloadTrackHandler.isTrack(ref)) { + TrackId id = tracksProvider.getCurrentTrack(); + if (preloadTrackHandler != null && preloadTrackHandler.isTrack(id)) { trackHandler = preloadTrackHandler; preloadTrackHandler = null; trackHandler.sendSeek(state.getPositionMs()); } else { trackHandler = new TrackHandler(session, cacheManager, conf, this); - trackHandler.sendLoad(ref, play, state.getPositionMs()); + trackHandler.sendLoad(id, play, state.getPositionMs()); state.setStatus(Spirc.PlayStatus.kPlayStatusLoading); } @@ -326,7 +299,7 @@ private void loadTrack(boolean play) { } private void handlePlay() { - if (state.getStatus() == Spirc.PlayStatus.kPlayStatusPause) { + if (state.isStatus(Spirc.PlayStatus.kPlayStatusPause)) { if (trackHandler != null) trackHandler.sendPlay(); state.setStatus(Spirc.PlayStatus.kPlayStatusPlay); state.setPositionMeasuredAt(System.currentTimeMillis()); @@ -335,7 +308,7 @@ private void handlePlay() { } private void handlePause() { - if (state.getStatus() == Spirc.PlayStatus.kPlayStatusPlay) { + if (state.isStatus(Spirc.PlayStatus.kPlayStatusPlay)) { if (trackHandler != null) trackHandler.sendPause(); state.setStatus(Spirc.PlayStatus.kPlayStatusPause); @@ -349,7 +322,7 @@ private void handlePause() { } private void handleNext() { - int newTrack = getQueuedTrack(true); + int newTrack = tracksProvider.getNextTrackIndex(true); boolean play = true; if (newTrack >= state.getTrackCount()) { newTrack = 0; @@ -365,26 +338,7 @@ private void handleNext() { private void handlePrev() { if (getPosition() < 3000) { - List queueTracks = new ArrayList<>(); - Iterator iter = state.getTrackList().iterator(); - while (iter.hasNext()) { - Spirc.TrackRef track = iter.next(); - if (track.getQueued()) { - queueTracks.add(track); - iter.remove(); - } - } - - int current = state.getPlayingTrackIndex(); - int newIndex; - if (current > 0) newIndex = current - 1; - else if (state.getRepeat()) newIndex = state.getTrackCount() - 1; - else newIndex = 0; - - for (int i = 0; i < queueTracks.size(); i++) - state.getTrackList().add(newIndex + 1 + i, queueTracks.get(i)); - - state.setPlayingTrackIndex(newIndex); + state.setPlayingTrackIndex(tracksProvider.getPrevTrackIndex(true)); state.setPositionMs(0); state.setPositionMeasuredAt(System.currentTimeMillis()); @@ -397,16 +351,6 @@ private void handlePrev() { } } - private int getQueuedTrack(boolean consume) { - int current = state.getPlayingTrackIndex(); - if (state.getTrack(current).getQueued()) { - if (consume) state.removeTrack(current); - return current; - } - - return current + 1; - } - public interface PlayerConfiguration { @NotNull StreamFeeder.AudioQuality preferredQuality(); @@ -415,4 +359,69 @@ public interface PlayerConfiguration { float normalisationPregain(); } + + private class StateWrapper { + private final Spirc.State.Builder state; + + StateWrapper(@NotNull Spirc.State.Builder state) { + this.state = state; + } + + @NotNull + Spirc.PlayStatus getStatus() { + return state.getStatus(); + } + + void setStatus(@NotNull Spirc.PlayStatus status) { + state.setStatus(status); + } + + boolean isStatus(@NotNull Spirc.PlayStatus status) { + return status == getStatus(); + } + + boolean getShuffle() { + return state.getShuffle(); + } + + void setShuffle(boolean shuffle) { + state.setShuffle(shuffle && (tracksProvider == null || tracksProvider.canShuffle())); + } + + void update(@NotNull Spirc.Frame frame) { + state.setContextUri(frame.getState().getContextUri()); + } + + long getPositionMeasuredAt() { + return state.getPositionMeasuredAt(); + } + + void setPositionMeasuredAt(long ms) { + state.setPositionMeasuredAt(ms); + } + + int getPositionMs() { + return state.getPositionMs(); + } + + void setPositionMs(int pos) { + state.setPositionMs(pos); + } + + boolean getRepeat() { + return state.getRepeat(); + } + + void setRepeat(boolean repeat) { + state.setRepeat(repeat && (tracksProvider == null || tracksProvider.canRepeat())); + } + + void setPlayingTrackIndex(int i) { + state.setPlayingTrackIndex(i); + } + + int getTrackCount() { + return state.getTrackCount(); + } + } } diff --git a/core/src/main/java/xyz/gianlu/librespot/player/TrackHandler.java b/core/src/main/java/xyz/gianlu/librespot/player/TrackHandler.java index 6a1ab082..27bcd651 100644 --- a/core/src/main/java/xyz/gianlu/librespot/player/TrackHandler.java +++ b/core/src/main/java/xyz/gianlu/librespot/player/TrackHandler.java @@ -5,12 +5,13 @@ import org.jetbrains.annotations.Nullable; import xyz.gianlu.librespot.common.Utils; import xyz.gianlu.librespot.common.proto.Metadata; -import xyz.gianlu.librespot.common.proto.Spirc; import xyz.gianlu.librespot.core.Session; import xyz.gianlu.librespot.mercury.MercuryClient; +import xyz.gianlu.librespot.mercury.model.TrackId; import java.io.Closeable; import java.io.IOException; +import java.util.Arrays; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -37,11 +38,11 @@ public class TrackHandler implements PlayerRunner.Listener, Closeable { new Thread(looper = new Looper()).start(); } - private void load(@NotNull Spirc.TrackRef ref, boolean play, int pos) throws IOException, MercuryClient.MercuryException { - StreamFeeder.LoadedStream stream = feeder.load(ref, new StreamFeeder.VorbisOnlyAudioQuality(conf.preferredQuality())); + private void load(@NotNull TrackId id, boolean play, int pos) throws IOException, MercuryClient.MercuryException { + StreamFeeder.LoadedStream stream = feeder.load(id, new StreamFeeder.VorbisOnlyAudioQuality(conf.preferredQuality())); track = stream.track; - LOGGER.info(String.format("Loading track, name: '%s', artists: '%s'", track.getName(), Utils.toString(track.getArtistList()))); + LOGGER.info(String.format("Loaded track, name: '%s', artists: '%s', gid: %s", track.getName(), Utils.toString(track.getArtistList()), Utils.bytesToHex(id.getGid()))); try { if (playerRunner != null) playerRunner.stop(); @@ -56,7 +57,7 @@ private void load(@NotNull Spirc.TrackRef ref, boolean play, int pos) throws IOE if (play) playerRunner.play(); } catch (PlayerRunner.PlayerException ex) { LOGGER.fatal("Failed starting playback!", ex); - listener.loadingError(this, ex); + listener.loadingError(this, id, ex); } } @@ -80,8 +81,8 @@ void sendStop() { sendCommand(Command.Stop); } - void sendLoad(@NotNull Spirc.TrackRef ref, boolean play, int pos) { - sendCommand(Command.Load, ref, play, pos); + void sendLoad(@NotNull TrackId track, boolean play, int pos) { + sendCommand(Command.Load, track, play, pos); } @Override @@ -115,8 +116,8 @@ public Metadata.Track track() { return track; } - boolean isTrack(Spirc.TrackRef ref) { - return track != null && ref.getGid().equals(track.getGid()); + boolean isTrack(@NotNull TrackId id) { + return track != null && track.hasGid() && Arrays.equals(id.getGid(), track.getGid().toByteArray()); } public enum Command { @@ -127,7 +128,7 @@ public enum Command { public interface Listener { void finishedLoading(@NotNull TrackHandler handler, boolean play); - void loadingError(@NotNull TrackHandler handler, @NotNull Exception ex); + void loadingError(@NotNull TrackHandler handler, @NotNull TrackId track, @NotNull Exception ex); void endOfTrack(@NotNull TrackHandler handler); @@ -144,10 +145,12 @@ public void run() { CommandBundle cmd = commands.take(); switch (cmd.cmd) { case Load: + TrackId id = (TrackId) cmd.args[0]; + try { - load((Spirc.TrackRef) cmd.args[0], (Boolean) cmd.args[1], (Integer) cmd.args[2]); + load(id, (Boolean) cmd.args[1], (Integer) cmd.args[2]); } catch (IOException | MercuryClient.MercuryException ex) { - listener.loadingError(TrackHandler.this, ex); + listener.loadingError(TrackHandler.this, id, ex); } break; case Play: diff --git a/core/src/main/java/xyz/gianlu/librespot/player/tracks/PlaylistProvider.java b/core/src/main/java/xyz/gianlu/librespot/player/tracks/PlaylistProvider.java new file mode 100644 index 00000000..e1bea444 --- /dev/null +++ b/core/src/main/java/xyz/gianlu/librespot/player/tracks/PlaylistProvider.java @@ -0,0 +1,127 @@ +package xyz.gianlu.librespot.player.tracks; + +import org.jetbrains.annotations.NotNull; +import xyz.gianlu.librespot.common.proto.Spirc; +import xyz.gianlu.librespot.mercury.model.TrackId; + +import java.util.*; + +/** + * @author Gianlu + */ +public class PlaylistProvider implements TracksProvider { + private final Spirc.State.Builder state; + private long shuffleSeed = 0; + + public PlaylistProvider(@NotNull Spirc.State.Builder state, @NotNull Spirc.Frame frame) { + this.state = state; + + state.setPlayingTrackIndex(frame.getState().getPlayingTrackIndex()); + state.clearTrack(); + state.addAllTrack(frame.getState().getTrackList()); + } + + private static int[] getShuffleExchanges(int size, long seed) { + int[] exchanges = new int[size - 1]; + Random rand = new Random(seed); + for (int i = size - 1; i > 0; i--) { + int n = rand.nextInt(i + 1); + exchanges[size - 1 - i] = n; + } + return exchanges; + } + + public void shuffleTracks(@NotNull Random random) { + shuffleSeed = random.nextLong(); + + List tracks = new ArrayList<>(state.getTrackList()); + if (state.getPlayingTrackIndex() != 0) { + Collections.swap(tracks, 0, state.getPlayingTrackIndex()); + state.setPlayingTrackIndex(0); + } + + int size = tracks.size() - 1; + int[] exchanges = getShuffleExchanges(size, shuffleSeed); + for (int i = size - 1; i > 1; i--) { + int n = exchanges[size - 1 - i]; + Collections.swap(tracks, i, n + 1); + } + + state.clearTrack(); + state.addAllTrack(tracks); + } + + public void unshuffleTracks() { + List tracks = new ArrayList<>(state.getTrackList()); + if (state.getPlayingTrackIndex() != 0) { + Collections.swap(tracks, 0, state.getPlayingTrackIndex()); + state.setPlayingTrackIndex(0); + } + + int size = tracks.size() - 1; + int[] exchanges = getShuffleExchanges(size, shuffleSeed); + for (int i = 2; i < size; i++) { + int n = exchanges[size - i - 1]; + Collections.swap(tracks, i, n + 1); + } + + state.clearTrack(); + state.addAllTrack(tracks); + } + + @Override + public int getNextTrackIndex(boolean consume) { + int current = state.getPlayingTrackIndex(); + if (state.getTrack(current).getQueued()) { + if (consume) state.removeTrack(current); + return current; + } + + return current + 1; + } + + @Override + public int getPrevTrackIndex(boolean consume) { + List queueTracks = new ArrayList<>(); + Iterator iter = state.getTrackList().iterator(); + while (iter.hasNext()) { + Spirc.TrackRef track = iter.next(); + if (track.getQueued()) { + queueTracks.add(track); + iter.remove(); + } + } + + int current = state.getPlayingTrackIndex(); + int newIndex; + if (current > 0) newIndex = current - 1; + else if (state.getRepeat()) newIndex = state.getTrackCount() - 1; + else newIndex = 0; + + for (int i = 0; i < queueTracks.size(); i++) + state.getTrackList().add(newIndex + 1 + i, queueTracks.get(i)); + + return newIndex; + } + + @NotNull + @Override + public TrackId getCurrentTrack() { + return TrackId.fromTrackRef(state.getTrack(state.getPlayingTrackIndex())); + } + + @NotNull + public TrackId getTrackAt(int index) throws IndexOutOfBoundsException { + return TrackId.fromTrackRef(state.getTrack(index)); + } + + @Override + public boolean canShuffle() { + return true; + } + + @Override + public boolean canRepeat() { + return true; + } +} diff --git a/core/src/main/java/xyz/gianlu/librespot/player/tracks/StationProvider.java b/core/src/main/java/xyz/gianlu/librespot/player/tracks/StationProvider.java new file mode 100644 index 00000000..3348586f --- /dev/null +++ b/core/src/main/java/xyz/gianlu/librespot/player/tracks/StationProvider.java @@ -0,0 +1,116 @@ +package xyz.gianlu.librespot.player.tracks; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.protobuf.ByteString; +import org.apache.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import xyz.gianlu.librespot.common.proto.Spirc; +import xyz.gianlu.librespot.core.Session; +import xyz.gianlu.librespot.mercury.MercuryClient; +import xyz.gianlu.librespot.mercury.MercuryRequests; +import xyz.gianlu.librespot.mercury.RawMercuryRequest; +import xyz.gianlu.librespot.mercury.model.TrackId; + +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * @author Gianlu + */ +public class StationProvider implements TracksProvider { + private static final Logger LOGGER = Logger.getLogger(StationProvider.class); + private static final int LOAD_NEXT_PAGE_THRESHOLD = 3; + private final MercuryClient mercury; + private final Spirc.State.Builder state; + private String nextPageUri; + + public StationProvider(@NotNull Session session, @NotNull Spirc.State.Builder state, @NotNull Spirc.Frame frame) { + this.mercury = session.mercury(); + this.state = state; + + state.setPlayingTrackIndex(frame.getState().getPlayingTrackIndex()); + state.clearTrack(); + state.addAllTrack(frame.getState().getTrackList()); + } + + @Override + public int getNextTrackIndex(boolean consume) { + int next = state.getPlayingTrackIndex() + 1; + if (next >= state.getTrackCount() - LOAD_NEXT_PAGE_THRESHOLD) { + try { + requestMore(); + } catch (IOException | MercuryClient.MercuryException ex) { + LOGGER.fatal("Failed requesting more tracks!", ex); + return state.getPlayingTrackIndex(); + } + } + + return next; + } + + private void requestMore() throws IOException, MercuryClient.MercuryException { + if (nextPageUri == null) resolveContext(); + getNextPage(); + } + + private void getNextPage() throws IOException { + MercuryClient.Response resp = mercury.sendSync(RawMercuryRequest.newBuilder() + .setUri(nextPageUri) + .setMethod("GET") + .build()); + + JsonObject obj = new JsonParser().parse(new InputStreamReader(resp.payload.stream())).getAsJsonObject(); + nextPageUri = obj.get("next_page_url").getAsString(); + LOGGER.trace("Next page URI: " + nextPageUri); + + JsonArray tracks = obj.getAsJsonArray("tracks"); + for (JsonElement elm : tracks) { + JsonObject track = elm.getAsJsonObject(); + String uri = track.get("uri").getAsString(); + state.addTrack(Spirc.TrackRef.newBuilder() + .setUri(uri) + .setGid(ByteString.copyFrom(TrackId.fromUri(uri).getGid())) + .build()); + } + } + + private void resolveContext() throws IOException, MercuryClient.MercuryException { + if (!state.hasContextUri()) + throw new IOException("Missing context URI!"); + + MercuryRequests.ResolvedContextWrapper json = mercury.sendSync(MercuryRequests.resolveContext(state.getContextUri())); + JsonObject firstPage = json.pages().get(0).getAsJsonObject(); + nextPageUri = firstPage.get("next_page_url").getAsString(); + LOGGER.trace("Next page URI: " + nextPageUri); + } + + @Override + public int getPrevTrackIndex(boolean consume) { + int prev = state.getPlayingTrackIndex() - 1; + if (prev < 0) return state.getPlayingTrackIndex(); + else return prev; + } + + @Override + public @NotNull TrackId getCurrentTrack() { + return TrackId.fromTrackRef(state.getTrack(state.getPlayingTrackIndex())); + } + + @Override + public @NotNull TrackId getTrackAt(int index) { + return TrackId.fromTrackRef(state.getTrack(index)); + } + + @Override + public boolean canShuffle() { + return false; + } + + @Override + public boolean canRepeat() { + return false; + } +} diff --git a/core/src/main/java/xyz/gianlu/librespot/player/tracks/TracksProvider.java b/core/src/main/java/xyz/gianlu/librespot/player/tracks/TracksProvider.java new file mode 100644 index 00000000..cfd2b48b --- /dev/null +++ b/core/src/main/java/xyz/gianlu/librespot/player/tracks/TracksProvider.java @@ -0,0 +1,24 @@ +package xyz.gianlu.librespot.player.tracks; + +import org.jetbrains.annotations.NotNull; +import xyz.gianlu.librespot.mercury.model.TrackId; + +/** + * @author Gianlu + */ +public interface TracksProvider { + + int getNextTrackIndex(boolean consume); + + int getPrevTrackIndex(boolean consume); + + @NotNull + TrackId getCurrentTrack(); + + @NotNull + TrackId getTrackAt(int index); + + boolean canShuffle(); + + boolean canRepeat(); +}