Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Radio implementation #34

Merged
merged 4 commits into from
Jan 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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