Skip to content

Commit

Permalink
Merge pull request #34 from librespot-org/radio-dev
Browse files Browse the repository at this point in the history
Radio implementation
  • Loading branch information
devgianlu authored Jan 5, 2019
2 parents 147b1f0 + a2560df commit a7b18bb
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
205 changes: 107 additions & 98 deletions core/src/main/java/xyz/gianlu/librespot/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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();
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -169,58 +162,34 @@ private void handleVolumeUp() {
}

private void stateUpdated() {
spirc.deviceStateUpdated(state);
spirc.deviceStateUpdated(state.state);
}

private int getPosition() {
int diff = (int) (System.currentTimeMillis() - state.getPositionMeasuredAt());
return state.getPositionMs() + diff;
}

private void shuffleTracks() {
shuffleSeed = session.random().nextLong();

List<Spirc.TrackRef> 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<Spirc.TrackRef> 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());
Expand All @@ -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();
Expand All @@ -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) {
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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);
}

Expand All @@ -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());
Expand All @@ -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);

Expand All @@ -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;
Expand All @@ -365,26 +338,7 @@ private void handleNext() {

private void handlePrev() {
if (getPosition() < 3000) {
List<Spirc.TrackRef> queueTracks = new ArrayList<>();
Iterator<Spirc.TrackRef> 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());

Expand All @@ -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();
Expand All @@ -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();
}
}
}
Loading

0 comments on commit a7b18bb

Please sign in to comment.