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

Major player rewrite + event service implementation #155

Merged
merged 38 commits into from
Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f0893f6
Event service framework (not working)
devgianlu Nov 27, 2019
c0c74b4
Fixed 400 + report language
devgianlu Nov 30, 2019
ff71ec6
Merge branch 'dev' into listen-reporting
devgianlu Jan 12, 2020
af00564
Better logging + minor changes
devgianlu Jan 12, 2020
814ed88
Merge branch 'dev' into listen-reporting
devgianlu Mar 3, 2020
62f94a9
Merge branch 'dev' into listen-reporting
devgianlu Apr 12, 2020
ce1596b
Working! (with a lot of improvements to make)
devgianlu Apr 13, 2020
dcd7a35
Merge branch 'dev' into listen-reporting
devgianlu Apr 13, 2020
255b83a
Event dispatch is now asynchronous
devgianlu Apr 13, 2020
5025eb8
More clean up
devgianlu Apr 13, 2020
bd597d6
Send events properly + removed CDN_REQUEST + minor refactoring
devgianlu Apr 14, 2020
e8c50bc
Compress all HTTP requests with GZip
devgianlu Apr 18, 2020
d1f2858
Fixed fields for bitrate and file size
devgianlu Apr 18, 2020
c1492fe
Send track played when shutting down or panicking
devgianlu Apr 18, 2020
db5a89a
Better handling of play origin + count decoded size
devgianlu Apr 19, 2020
887286a
Minor refactoring
devgianlu Apr 20, 2020
48a8416
Rewritten player (missing crossfade) + fixed issue with cache + gener…
devgianlu Apr 21, 2020
3a77d53
Merge branch 'dev' into listen-reporting
devgianlu Apr 21, 2020
39c7b76
Fixed merge issues
devgianlu Apr 21, 2020
f3fd6d1
Refactored instants logic
devgianlu Apr 21, 2020
80e9138
Fixed LookupInterpolator + implemented crossfade
devgianlu Apr 21, 2020
b4e305d
Added transition metrics
devgianlu Apr 21, 2020
35a0f86
Fixed cache clean up + fixed build
devgianlu Apr 22, 2020
5421e38
Fixed interpolator tests
devgianlu Apr 22, 2020
003d78c
Major player rewrite done (metrics are still missing)
devgianlu Apr 24, 2020
0198c43
Fixed crossfade not working on first track + fixed possible leaked ou…
devgianlu Apr 24, 2020
73ca627
Audio key metrics + preload flag + minor fixes
devgianlu Apr 24, 2020
b78cd68
Fixed build
devgianlu Apr 25, 2020
ec72d05
Removed default fade to respect original client behaviour
devgianlu Apr 25, 2020
8f134bc
Fixed crossfade issue + added logging for time offset + send metrics
devgianlu Apr 25, 2020
65b11c2
Fixed memory leak
devgianlu Apr 26, 2020
5baafe9
Fixed cache failing if first chunk is missing
devgianlu Apr 26, 2020
f9564ca
Merge branch 'dev' into listen-reporting
devgianlu Apr 26, 2020
eb3b954
Await termination of EventService to sends all events
devgianlu Apr 26, 2020
5fea1ab
Fixed repeating track + added 0x82 packet
devgianlu Apr 27, 2020
2985888
CDN request + remote device ID
devgianlu Apr 27, 2020
34365a4
Fixed IllegalStateException when output has been already cleared
devgianlu Apr 27, 2020
190aa07
Clean up
devgianlu Apr 27, 2020
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
89 changes: 40 additions & 49 deletions core/src/main/java/xyz/gianlu/librespot/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.*;

/**
Expand All @@ -55,7 +52,7 @@ public class Player implements Closeable, DeviceStateHandler.Listener, PlayerSes
private StateWrapper state;
private PlayerSession playerSession;
private ScheduledFuture<?> releaseLineFuture = null;
private PlaybackMetrics metrics = null;
private Map<String, PlaybackMetrics> metrics = new HashMap<>(5);

public Player(@NotNull Player.Configuration conf, @NotNull Session session) {
this.conf = conf;
Expand Down Expand Up @@ -154,12 +151,8 @@ private void panicState(@Nullable PlaybackMetrics.Reason reason) {

if (reason == null) {
metrics = null;
} else if (metrics != null) {
metrics.endedHow(reason, null);
metrics.endInterval(state.getPosition());
metrics.update(playerSession != null ? playerSession.currentMetrics() : null);
session.eventService().trackPlayed(metrics);
metrics = null;
} else if (playerSession != null) {
endMetrics(playerSession.currentPlaybackId(), reason, playerSession.currentMetrics(), state.getPosition());
}
}

Expand All @@ -173,13 +166,7 @@ private void loadSession(@NotNull String sessionId, boolean play, boolean withSk
TransitionInfo trans = TransitionInfo.contextChange(state, withSkip);

if (playerSession != null) {
if (metrics != null) {
metrics.endedHow(trans.endedReason, state.getPlayOrigin().getFeatureIdentifier());
metrics.endInterval(trans.endedWhen);
metrics.update(playerSession.currentMetrics());
session.eventService().trackPlayed(metrics);
metrics = null;
}
endMetrics(playerSession.currentPlaybackId(), trans.endedReason, playerSession.currentMetrics(), trans.endedWhen);

playerSession.close();
playerSession = null;
Expand All @@ -200,13 +187,7 @@ private void loadSession(@NotNull String sessionId, boolean play, boolean withSk
* @param trans A {@link TransitionInfo} object containing information about this track change
*/
private void loadTrack(boolean play, @NotNull TransitionInfo trans) {
if (metrics != null) {
metrics.endedHow(trans.endedReason, state.getPlayOrigin().getFeatureIdentifier());
metrics.endInterval(trans.endedWhen);
metrics.update(playerSession.currentMetrics());
session.eventService().trackPlayed(metrics);
metrics = null;
}
endMetrics(playerSession.currentPlaybackId(), trans.endedReason, playerSession.currentMetrics(), trans.endedWhen);

String playbackId = playerSession.play(state.getCurrentPlayableOrThrow(), state.getPosition(), trans.startedReason);
state.setPlaybackId(playbackId);
Expand All @@ -222,9 +203,7 @@ private void loadTrack(boolean play, @NotNull TransitionInfo trans) {
if (play) events.playbackResumed();
else events.playbackPaused();

metrics = new PlaybackMetrics(state.getCurrentPlayableOrThrow(), playbackId, state);
metrics.startedHow(trans.startedReason, state.getPlayOrigin().getFeatureIdentifier());
metrics.startInterval(state.getPosition());
startMetrics(playbackId, trans.startedReason, state.getPosition());

if (releaseLineFuture != null) {
releaseLineFuture.cancel(true);
Expand Down Expand Up @@ -341,9 +320,10 @@ private void handleSeek(int pos) {
state.setPosition(pos);
events.seeked(pos);

if (metrics != null) {
metrics.endInterval(state.getPosition());
metrics.startInterval(pos);
PlaybackMetrics pm = metrics.get(playerSession.currentPlaybackId());
if (pm != null) {
pm.endInterval(state.getPosition());
pm.startInterval(pos);
}
}

Expand Down Expand Up @@ -497,6 +477,30 @@ private void loadAutoplay() {
}


// ================================ //
// =========== Metrics ============ //
// ================================ //

private void startMetrics(String playbackId, @NotNull PlaybackMetrics.Reason reason, int pos) {
PlaybackMetrics pm = new PlaybackMetrics(state.getCurrentPlayableOrThrow(), playbackId, state);
pm.startedHow(reason, state.getPlayOrigin().getFeatureIdentifier());
pm.startInterval(pos);
metrics.put(playbackId, pm);
}

private void endMetrics(String playbackId, @NotNull PlaybackMetrics.Reason reason, @Nullable PlayerMetrics playerMetrics, int when) {
if (playbackId == null) return;

PlaybackMetrics pm = metrics.remove(playbackId);
if (pm == null) return;

pm.endedHow(reason, state.getPlayOrigin().getFeatureIdentifier());
pm.endInterval(when);
pm.update(playerMetrics);
session.eventService().trackPlayed(pm);
devgianlu marked this conversation as resolved.
Show resolved Hide resolved
}


// ================================ //
// ======== Player events ========= //
// ================================ //
Expand Down Expand Up @@ -554,20 +558,12 @@ public void trackChanged(@NotNull String playbackId, @Nullable TrackOrEpisode me
events.trackChanged();

session.eventService().newPlaybackId(state, playbackId);
metrics = new PlaybackMetrics(state.getCurrentPlayableOrThrow(), playbackId, state);
metrics.startedHow(startedReason, state.getPlayOrigin().getFeatureIdentifier());
metrics.startInterval(pos);
startMetrics(playbackId, startedReason, pos);
}

@Override
public void trackPlayed(@NotNull PlaybackMetrics.Reason endReason, @NotNull PlayerMetrics playerMetrics, int willEndAt) {
if (metrics != null) {
metrics.endedHow(endReason, state.getPlayOrigin().getFeatureIdentifier());
metrics.endInterval(willEndAt);
metrics.update(playerMetrics);
session.eventService().trackPlayed(metrics);
metrics = null;
}
public void trackPlayed(@NotNull String playbackId, @NotNull PlaybackMetrics.Reason endReason, @NotNull PlayerMetrics playerMetrics, int when) {
endMetrics(playbackId, endReason, playerMetrics, when);
devgianlu marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
Expand Down Expand Up @@ -716,13 +712,8 @@ public long time() {

@Override
public void close() {
if (metrics != null && playerSession != null) {
metrics.endedHow(PlaybackMetrics.Reason.LOGOUT, state.getPlayOrigin().getFeatureIdentifier());
metrics.endInterval(state.getPosition());
metrics.update(playerSession.currentMetrics());
session.eventService().trackPlayed(metrics);
metrics = null;
}
if (playerSession != null)
endMetrics(playerSession.currentPlaybackId(), PlaybackMetrics.Reason.LOGOUT, playerSession.currentMetrics(), state.getPosition());

state.close();
events.listeners.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ private void populateFadeOut(@NotNull Map<String, String> metadata) {

if (fadeOutDuration != 0 && fadeOutCurves.size() > 0)
fadeOutMap.put(Reason.TRACK_DONE, new FadeInterval(fadeOutStartTime, fadeOutDuration, LookupInterpolator.fromJson(getFadeCurve(fadeOutCurves))));
else if (defaultFadeDuration > 0)
fadeOutMap.put(Reason.TRACK_DONE, new FadeInterval(trackDuration - defaultFadeDuration, defaultFadeDuration, new LinearDecreasingInterpolator()));


int backFadeOutDuration = Integer.parseInt(metadata.getOrDefault("audio.backbtn.fade_out_duration", "-1"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
public final class PlayableContentFeeder {
private static final Logger LOGGER = Logger.getLogger(PlayableContentFeeder.class);
private static final String STORAGE_RESOLVE_INTERACTIVE = "/storage-resolve/files/audio/interactive/%s";
private static final String STORAGE_RESOLVE_INTERACTIVE_PREFETCH = "/storage-resolve/files/audio/interactive-prefetch/%s";
private static final String STORAGE_RESOLVE_INTERACTIVE_PREFETCH = "/storage-resolve/files/audio/interactive_prefetch/%s";
protected final Session session;

public PlayableContentFeeder(@NotNull Session session) {
Expand All @@ -58,10 +58,12 @@ private static Metadata.Track pickAlternativeIfNecessary(@NotNull Metadata.Track

@NotNull
public final LoadedStream load(@NotNull PlayableId id, @NotNull AudioQualityPreference audioQualityPreference, boolean preload, @Nullable HaltListener haltListener) throws CdnManager.CdnException, ContentRestrictedException, MercuryClient.MercuryException, IOException {
if (id instanceof TrackId) return loadTrack((TrackId) id, audioQualityPreference, preload, haltListener);
if (id instanceof TrackId)
return loadTrack((TrackId) id, audioQualityPreference, preload, haltListener);
else if (id instanceof EpisodeId)
return loadEpisode((EpisodeId) id, audioQualityPreference, preload, haltListener);
else throw new IllegalArgumentException("Unknown PlayableId: " + id);
else
throw new IllegalArgumentException("Unknown content: " + id);
}

@NotNull
Expand All @@ -83,7 +85,7 @@ private StorageResolveResponse resolveStorageInteractive(@NotNull ByteString fil
String country = session.countryCode();
if (country != null) ContentRestrictedException.checkRestrictions(country, original.getRestrictionList());

LOGGER.fatal("Couldn't find playable track: " + Utils.bytesToHex(id.getGid()));
LOGGER.fatal("Couldn't find playable track: " + id.toSpotifyUri());
throw new FeederException();
}

Expand Down Expand Up @@ -187,7 +189,7 @@ public static class Metrics {
public final boolean preloadedAudioKey;
public final int audioKeyTime;

public Metrics(boolean preloadedAudioKey, int audioKeyTime) { // TODO: Check values
public Metrics(boolean preloadedAudioKey, int audioKeyTime) {
this.preloadedAudioKey = preloadedAudioKey;
this.audioKeyTime = audioKeyTime;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.gianlu.librespot.common.Utils;
import xyz.gianlu.librespot.core.EventService.PlaybackMetrics;
import xyz.gianlu.librespot.core.Session;
import xyz.gianlu.librespot.mercury.MercuryClient;
import xyz.gianlu.librespot.mercury.model.EpisodeId;
Expand Down Expand Up @@ -52,6 +53,7 @@ class PlayerQueueEntry extends PlayerQueue.Entry implements Closeable, Runnable,
private final Session session;
private final AudioFormat format;
CrossfadeController crossfade;
PlaybackMetrics.Reason endReason = PlaybackMetrics.Reason.END_PLAY;
private Codec codec;
private TrackOrEpisode metadata;
private volatile boolean closed = false;
Expand Down Expand Up @@ -150,6 +152,20 @@ int getTime() throws Codec.CannotGetTimeException {
return codec == null ? -1 : codec.time();
}

/**
* Returns the current position.
*
* @return The current position of the player or {@code -1} if not available.
* @see PlayerQueueEntry#getTime()
*/
int getTimeNoThrow() {
try {
return getTime();
} catch (Codec.CannotGetTimeException e) {
return -1;
}
}

/**
* Seeks to the specified position.
*
Expand Down Expand Up @@ -229,7 +245,7 @@ public void run() {
} catch (IOException | ContentRestrictedException | CdnManager.CdnException | MercuryClient.MercuryException | Codec.CodecException ex) {
close();
listener.loadingError(this, ex, retried);
LOGGER.trace(String.format("%s terminated at loading.", this));
LOGGER.trace(String.format("%s terminated at loading.", this), ex);
return;
}

Expand Down Expand Up @@ -275,15 +291,24 @@ public void run() {
}

try {
if (codec.writeSomeTo(output.stream()) == -1)
if (codec.writeSomeTo(output.stream()) == -1) {
try {
int time = codec.time();
LOGGER.debug(String.format("Player time offset is %d. {id: %s}", metadata.duration() - time, playbackId));
} catch (Codec.CannotGetTimeException ignored) {
}

close();
break;
}
} catch (IOException | Codec.CodecException ex) {
if (!closed) {
close();
listener.playbackError(this, ex);
return;
}

return;
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class PlayerSession implements Closeable, PlayerQueueEntry.Listener {
private final PlayerQueue queue;
private int lastPlayPos = 0;
private Reason lastPlayReason = null;
private volatile boolean closed = false;

public PlayerSession(@NotNull Session session, @NotNull AudioSink sink, @NotNull String sessionId, @NotNull Listener listener) {
this.session = session;
Expand Down Expand Up @@ -85,7 +86,7 @@ private boolean advanceTo(@NotNull PlayableId id) {
* Gets the next content and tries to advance, notifying if successful.
*/
private void advance(@NotNull Reason reason) {
// TODO: Call #trackPlayed somewhere!!
if (closed) return;

PlayableId next = listener.nextPlayable();
if (next == null)
Expand Down Expand Up @@ -114,7 +115,10 @@ public void instantReached(@NotNull PlayerQueueEntry entry, int callbackId, int

@Override
public void playbackEnded(@NotNull PlayerQueueEntry entry) {
if (entry == queue.head()) advance(Reason.TRACK_DONE);
listener.trackPlayed(entry.playbackId, entry.endReason, entry.metrics(), entry.getTimeNoThrow());

if (entry == queue.head())
advance(Reason.TRACK_DONE);
}

@Override
Expand Down Expand Up @@ -207,6 +211,7 @@ public void playbackResumed(@NotNull PlayerQueueEntry entry, int chunk, int diff
throw new IllegalStateException();

if (head.prev != null) {
head.prev.endReason = reason;
CrossfadeController.FadeInterval fadeOut;
if (head.prev.crossfade == null || (fadeOut = head.prev.crossfade.selectFadeOut(reason)) == null) {
head.prev.close();
Expand Down Expand Up @@ -300,11 +305,18 @@ public long currentTime() throws Codec.CannotGetTimeException {
else return queue.head().getTime();
}

@Nullable
public String currentPlaybackId() {
if (queue.head() == null) return null;
else return queue.head().playbackId;
}

/**
* Close the session by clearing the queue which will close all entries.
*/
@Override
public void close() {
closed = true;
queue.close();
}

Expand Down Expand Up @@ -381,10 +393,12 @@ public interface Listener {
/**
* The current entry has finished playing.
*
* @param playbackId The playback ID of this entry
* @param endReason The reason why this track ended
* @param playerMetrics The {@link PlayerMetrics} for this entry
* @param endedAt The time this entry ended
*/
void trackPlayed(@NotNull Reason endReason, @NotNull PlayerMetrics playerMetrics, int endedAt);
void trackPlayed(@NotNull String playbackId, @NotNull Reason endReason, @NotNull PlayerMetrics playerMetrics, int endedAt);
}

private static class EntryWithPos {
Expand Down