Reflect playback suppression in PlaybackStats states.

Also split existing SUSPENDED state into ABANDONED and INTERUPTED_BY_AD for
more clarity.

PiperOrigin-RevId: 271997824
This commit is contained in:
tonihei 2019-09-30 17:22:00 +01:00 committed by Oliver Woodman
parent f0723a27b7
commit 6a2d04e3ce
2 changed files with 99 additions and 38 deletions

View File

@ -38,8 +38,10 @@ public final class PlaybackStats {
* #PLAYBACK_STATE_JOINING_FOREGROUND}, {@link #PLAYBACK_STATE_JOINING_BACKGROUND}, {@link * #PLAYBACK_STATE_JOINING_FOREGROUND}, {@link #PLAYBACK_STATE_JOINING_BACKGROUND}, {@link
* #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED}, {@link #PLAYBACK_STATE_SEEKING}, * #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED}, {@link #PLAYBACK_STATE_SEEKING},
* {@link #PLAYBACK_STATE_BUFFERING}, {@link #PLAYBACK_STATE_PAUSED_BUFFERING}, {@link * {@link #PLAYBACK_STATE_BUFFERING}, {@link #PLAYBACK_STATE_PAUSED_BUFFERING}, {@link
* #PLAYBACK_STATE_SEEK_BUFFERING}, {@link #PLAYBACK_STATE_ENDED}, {@link * #PLAYBACK_STATE_SEEK_BUFFERING}, {@link #PLAYBACK_STATE_SUPPRESSED}, {@link
* #PLAYBACK_STATE_STOPPED}, {@link #PLAYBACK_STATE_FAILED} or {@link #PLAYBACK_STATE_SUSPENDED}. * #PLAYBACK_STATE_SUPPRESSED_BUFFERING}, {@link #PLAYBACK_STATE_ENDED}, {@link
* #PLAYBACK_STATE_STOPPED}, {@link #PLAYBACK_STATE_FAILED}, {@link
* #PLAYBACK_STATE_INTERRUPTED_BY_AD} or {@link #PLAYBACK_STATE_ABANDONED}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@ -54,10 +56,13 @@ public final class PlaybackStats {
PLAYBACK_STATE_BUFFERING, PLAYBACK_STATE_BUFFERING,
PLAYBACK_STATE_PAUSED_BUFFERING, PLAYBACK_STATE_PAUSED_BUFFERING,
PLAYBACK_STATE_SEEK_BUFFERING, PLAYBACK_STATE_SEEK_BUFFERING,
PLAYBACK_STATE_SUPPRESSED,
PLAYBACK_STATE_SUPPRESSED_BUFFERING,
PLAYBACK_STATE_ENDED, PLAYBACK_STATE_ENDED,
PLAYBACK_STATE_STOPPED, PLAYBACK_STATE_STOPPED,
PLAYBACK_STATE_FAILED, PLAYBACK_STATE_FAILED,
PLAYBACK_STATE_SUSPENDED PLAYBACK_STATE_INTERRUPTED_BY_AD,
PLAYBACK_STATE_ABANDONED
}) })
@interface PlaybackState {} @interface PlaybackState {}
/** Playback has not started (initial state). */ /** Playback has not started (initial state). */
@ -72,22 +77,28 @@ public final class PlaybackStats {
public static final int PLAYBACK_STATE_PAUSED = 4; public static final int PLAYBACK_STATE_PAUSED = 4;
/** Playback is handling a seek. */ /** Playback is handling a seek. */
public static final int PLAYBACK_STATE_SEEKING = 5; public static final int PLAYBACK_STATE_SEEKING = 5;
/** Playback is buffering to restart playback. */ /** Playback is buffering to resume active playback. */
public static final int PLAYBACK_STATE_BUFFERING = 6; public static final int PLAYBACK_STATE_BUFFERING = 6;
/** Playback is buffering while paused. */ /** Playback is buffering while paused. */
public static final int PLAYBACK_STATE_PAUSED_BUFFERING = 7; public static final int PLAYBACK_STATE_PAUSED_BUFFERING = 7;
/** Playback is buffering after a seek. */ /** Playback is buffering after a seek. */
public static final int PLAYBACK_STATE_SEEK_BUFFERING = 8; public static final int PLAYBACK_STATE_SEEK_BUFFERING = 8;
/** Playback is suppressed (e.g. due to audio focus loss). */
public static final int PLAYBACK_STATE_SUPPRESSED = 9;
/** Playback is suppressed (e.g. due to audio focus loss) while buffering to resume a playback. */
public static final int PLAYBACK_STATE_SUPPRESSED_BUFFERING = 10;
/** Playback has reached the end of the media. */ /** Playback has reached the end of the media. */
public static final int PLAYBACK_STATE_ENDED = 9; public static final int PLAYBACK_STATE_ENDED = 11;
/** Playback is stopped and can be resumed. */ /** Playback is stopped and can be restarted. */
public static final int PLAYBACK_STATE_STOPPED = 10; public static final int PLAYBACK_STATE_STOPPED = 12;
/** Playback is stopped due a fatal error and can be retried. */ /** Playback is stopped due a fatal error and can be retried. */
public static final int PLAYBACK_STATE_FAILED = 11; public static final int PLAYBACK_STATE_FAILED = 13;
/** Playback is suspended, e.g. because the user left or it is interrupted by another playback. */ /** Playback is interrupted by an ad. */
public static final int PLAYBACK_STATE_SUSPENDED = 12; public static final int PLAYBACK_STATE_INTERRUPTED_BY_AD = 14;
/** Playback is abandoned before reaching the end of the media. */
public static final int PLAYBACK_STATE_ABANDONED = 15;
/** Total number of playback states. */ /** Total number of playback states. */
/* package */ static final int PLAYBACK_STATE_COUNT = 13; /* package */ static final int PLAYBACK_STATE_COUNT = 16;
/** Empty playback stats. */ /** Empty playback stats. */
public static final PlaybackStats EMPTY = merge(/* nothing */ ); public static final PlaybackStats EMPTY = merge(/* nothing */ );

View File

@ -81,6 +81,7 @@ public final class PlaybackStatsListener
@Nullable private String activeAdPlayback; @Nullable private String activeAdPlayback;
private boolean playWhenReady; private boolean playWhenReady;
@Player.State private int playbackState; @Player.State private int playbackState;
private boolean isSuppressed;
private float playbackSpeed; private float playbackSpeed;
/** /**
@ -205,7 +206,7 @@ public final class PlaybackStatsListener
eventTime.currentPlaybackPositionMs, eventTime.currentPlaybackPositionMs,
eventTime.totalBufferedDurationMs); eventTime.totalBufferedDurationMs);
Assertions.checkNotNull(playbackStatsTrackers.get(contentSession)) Assertions.checkNotNull(playbackStatsTrackers.get(contentSession))
.onSuspended(contentEventTime, /* belongsToPlayback= */ true); .onInterruptedByAd(contentEventTime);
} }
@Override @Override
@ -222,7 +223,7 @@ public final class PlaybackStatsListener
tracker.onPlayerStateChanged( tracker.onPlayerStateChanged(
eventTime, /* playWhenReady= */ true, Player.STATE_ENDED, /* belongsToPlayback= */ false); eventTime, /* playWhenReady= */ true, Player.STATE_ENDED, /* belongsToPlayback= */ false);
} }
tracker.onSuspended(eventTime, /* belongsToPlayback= */ false); tracker.onFinished(eventTime);
PlaybackStats playbackStats = tracker.build(/* isFinal= */ true); PlaybackStats playbackStats = tracker.build(/* isFinal= */ true);
finishedPlaybackStats = PlaybackStats.merge(finishedPlaybackStats, playbackStats); finishedPlaybackStats = PlaybackStats.merge(finishedPlaybackStats, playbackStats);
if (callback != null) { if (callback != null) {
@ -246,6 +247,19 @@ public final class PlaybackStatsListener
} }
} }
@Override
public void onPlaybackSuppressionReasonChanged(
EventTime eventTime, int playbackSuppressionReason) {
isSuppressed = playbackSuppressionReason != Player.PLAYBACK_SUPPRESSION_REASON_NONE;
sessionManager.updateSessions(eventTime);
for (String session : playbackStatsTrackers.keySet()) {
boolean belongsToPlayback = sessionManager.belongsToSession(eventTime, session);
playbackStatsTrackers
.get(session)
.onIsSuppressedChanged(eventTime, isSuppressed, belongsToPlayback);
}
}
@Override @Override
public void onTimelineChanged(EventTime eventTime, int reason) { public void onTimelineChanged(EventTime eventTime, int reason) {
sessionManager.handleTimelineUpdate(eventTime); sessionManager.handleTimelineUpdate(eventTime);
@ -456,9 +470,11 @@ public final class PlaybackStatsListener
private long currentPlaybackStateStartTimeMs; private long currentPlaybackStateStartTimeMs;
private boolean isSeeking; private boolean isSeeking;
private boolean isForeground; private boolean isForeground;
private boolean isSuspended; private boolean isInterruptedByAd;
private boolean isFinished;
private boolean playWhenReady; private boolean playWhenReady;
@Player.State private int playerPlaybackState; @Player.State private int playerPlaybackState;
private boolean isSuppressed;
private boolean hasFatalError; private boolean hasFatalError;
private boolean startedLoading; private boolean startedLoading;
private long lastRebufferStartTimeMs; private long lastRebufferStartTimeMs;
@ -515,18 +531,32 @@ public final class PlaybackStatsListener
hasFatalError = false; hasFatalError = false;
} }
if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED) { if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED) {
isSuspended = false; isInterruptedByAd = false;
} }
maybeUpdatePlaybackState(eventTime, belongsToPlayback); maybeUpdatePlaybackState(eventTime, belongsToPlayback);
} }
/**
* Notifies the tracker of a change to the playback suppression (e.g. due to audio focus loss),
* including all updates while the playback is not in the foreground.
*
* @param eventTime The {@link EventTime}.
* @param isSuppressed Whether playback is suppressed.
* @param belongsToPlayback Whether the {@code eventTime} belongs to the current playback.
*/
public void onIsSuppressedChanged(
EventTime eventTime, boolean isSuppressed, boolean belongsToPlayback) {
this.isSuppressed = isSuppressed;
maybeUpdatePlaybackState(eventTime, belongsToPlayback);
}
/** /**
* Notifies the tracker of a position discontinuity or timeline update for the current playback. * Notifies the tracker of a position discontinuity or timeline update for the current playback.
* *
* @param eventTime The {@link EventTime}. * @param eventTime The {@link EventTime}.
*/ */
public void onPositionDiscontinuity(EventTime eventTime) { public void onPositionDiscontinuity(EventTime eventTime) {
isSuspended = false; isInterruptedByAd = false;
maybeUpdatePlaybackState(eventTime, /* belongsToPlayback= */ true); maybeUpdatePlaybackState(eventTime, /* belongsToPlayback= */ true);
} }
@ -561,7 +591,7 @@ public final class PlaybackStatsListener
fatalErrorHistory.add(Pair.create(eventTime, error)); fatalErrorHistory.add(Pair.create(eventTime, error));
} }
hasFatalError = true; hasFatalError = true;
isSuspended = false; isInterruptedByAd = false;
isSeeking = false; isSeeking = false;
maybeUpdatePlaybackState(eventTime, /* belongsToPlayback= */ true); maybeUpdatePlaybackState(eventTime, /* belongsToPlayback= */ true);
} }
@ -587,16 +617,24 @@ public final class PlaybackStatsListener
} }
/** /**
* Notifies the tracker that the current playback has been suspended, e.g. for ad playback or * Notifies the tracker that the current playback has been interrupted for ad playback.
* permanently.
* *
* @param eventTime The {@link EventTime}. * @param eventTime The {@link EventTime}.
* @param belongsToPlayback Whether the {@code eventTime} belongs to the current playback.
*/ */
public void onSuspended(EventTime eventTime, boolean belongsToPlayback) { public void onInterruptedByAd(EventTime eventTime) {
isSuspended = true; isInterruptedByAd = true;
isSeeking = false; isSeeking = false;
maybeUpdatePlaybackState(eventTime, belongsToPlayback); maybeUpdatePlaybackState(eventTime, /* belongsToPlayback= */ true);
}
/**
* Notifies the tracker that the current playback has finished.
*
* @param eventTime The {@link EventTime}. Not guaranteed to belong to the current playback.
*/
public void onFinished(EventTime eventTime) {
isFinished = true;
maybeUpdatePlaybackState(eventTime, /* belongsToPlayback= */ false);
} }
/** /**
@ -809,8 +847,9 @@ public final class PlaybackStatsListener
rebufferCount++; rebufferCount++;
lastRebufferStartTimeMs = eventTime.realtimeMs; lastRebufferStartTimeMs = eventTime.realtimeMs;
} }
if (newPlaybackState == PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING if (isRebufferingState(currentPlaybackState)
&& currentPlaybackState == PlaybackStats.PLAYBACK_STATE_BUFFERING) { && currentPlaybackState != PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING
&& newPlaybackState == PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING) {
pauseBufferCount++; pauseBufferCount++;
} }
@ -829,11 +868,11 @@ public final class PlaybackStatsListener
} }
private @PlaybackState int resolveNewPlaybackState() { private @PlaybackState int resolveNewPlaybackState() {
if (isSuspended) { if (isFinished) {
// Keep VIDEO_STATE_ENDED if playback naturally ended (or progressed to next item). // Keep VIDEO_STATE_ENDED if playback naturally ended (or progressed to next item).
return currentPlaybackState == PlaybackStats.PLAYBACK_STATE_ENDED return currentPlaybackState == PlaybackStats.PLAYBACK_STATE_ENDED
? PlaybackStats.PLAYBACK_STATE_ENDED ? PlaybackStats.PLAYBACK_STATE_ENDED
: PlaybackStats.PLAYBACK_STATE_SUSPENDED; : PlaybackStats.PLAYBACK_STATE_ABANDONED;
} else if (isSeeking) { } else if (isSeeking) {
// Seeking takes precedence over errors such that we report a seek while in error state. // Seeking takes precedence over errors such that we report a seek while in error state.
return PlaybackStats.PLAYBACK_STATE_SEEKING; return PlaybackStats.PLAYBACK_STATE_SEEKING;
@ -844,26 +883,34 @@ public final class PlaybackStatsListener
return startedLoading return startedLoading
? PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND ? PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND
: PlaybackStats.PLAYBACK_STATE_NOT_STARTED; : PlaybackStats.PLAYBACK_STATE_NOT_STARTED;
} else if (isInterruptedByAd) {
return PlaybackStats.PLAYBACK_STATE_INTERRUPTED_BY_AD;
} else if (playerPlaybackState == Player.STATE_ENDED) { } else if (playerPlaybackState == Player.STATE_ENDED) {
return PlaybackStats.PLAYBACK_STATE_ENDED; return PlaybackStats.PLAYBACK_STATE_ENDED;
} else if (playerPlaybackState == Player.STATE_BUFFERING) { } else if (playerPlaybackState == Player.STATE_BUFFERING) {
if (currentPlaybackState == PlaybackStats.PLAYBACK_STATE_NOT_STARTED if (currentPlaybackState == PlaybackStats.PLAYBACK_STATE_NOT_STARTED
|| currentPlaybackState == PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND || currentPlaybackState == PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND
|| currentPlaybackState == PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND || currentPlaybackState == PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND
|| currentPlaybackState == PlaybackStats.PLAYBACK_STATE_SUSPENDED) { || currentPlaybackState == PlaybackStats.PLAYBACK_STATE_INTERRUPTED_BY_AD) {
return PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND; return PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND;
} }
if (currentPlaybackState == PlaybackStats.PLAYBACK_STATE_SEEKING if (currentPlaybackState == PlaybackStats.PLAYBACK_STATE_SEEKING
|| currentPlaybackState == PlaybackStats.PLAYBACK_STATE_SEEK_BUFFERING) { || currentPlaybackState == PlaybackStats.PLAYBACK_STATE_SEEK_BUFFERING) {
return PlaybackStats.PLAYBACK_STATE_SEEK_BUFFERING; return PlaybackStats.PLAYBACK_STATE_SEEK_BUFFERING;
} }
return playWhenReady if (!playWhenReady) {
? PlaybackStats.PLAYBACK_STATE_BUFFERING return PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING;
: PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING; }
return isSuppressed
? PlaybackStats.PLAYBACK_STATE_SUPPRESSED_BUFFERING
: PlaybackStats.PLAYBACK_STATE_BUFFERING;
} else if (playerPlaybackState == Player.STATE_READY) { } else if (playerPlaybackState == Player.STATE_READY) {
return playWhenReady if (!playWhenReady) {
? PlaybackStats.PLAYBACK_STATE_PLAYING return PlaybackStats.PLAYBACK_STATE_PAUSED;
: PlaybackStats.PLAYBACK_STATE_PAUSED; }
return isSuppressed
? PlaybackStats.PLAYBACK_STATE_SUPPRESSED
: PlaybackStats.PLAYBACK_STATE_PLAYING;
} else if (playerPlaybackState == Player.STATE_IDLE } else if (playerPlaybackState == Player.STATE_IDLE
&& currentPlaybackState != PlaybackStats.PLAYBACK_STATE_NOT_STARTED) { && currentPlaybackState != PlaybackStats.PLAYBACK_STATE_NOT_STARTED) {
// This case only applies for calls to player.stop(). All other IDLE cases are handled by // This case only applies for calls to player.stop(). All other IDLE cases are handled by
@ -974,7 +1021,8 @@ public final class PlaybackStatsListener
private static boolean isReadyState(@PlaybackState int state) { private static boolean isReadyState(@PlaybackState int state) {
return state == PlaybackStats.PLAYBACK_STATE_PLAYING return state == PlaybackStats.PLAYBACK_STATE_PLAYING
|| state == PlaybackStats.PLAYBACK_STATE_PAUSED; || state == PlaybackStats.PLAYBACK_STATE_PAUSED
|| state == PlaybackStats.PLAYBACK_STATE_SUPPRESSED;
} }
private static boolean isPausedState(@PlaybackState int state) { private static boolean isPausedState(@PlaybackState int state) {
@ -984,21 +1032,23 @@ public final class PlaybackStatsListener
private static boolean isRebufferingState(@PlaybackState int state) { private static boolean isRebufferingState(@PlaybackState int state) {
return state == PlaybackStats.PLAYBACK_STATE_BUFFERING return state == PlaybackStats.PLAYBACK_STATE_BUFFERING
|| state == PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING; || state == PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING
|| state == PlaybackStats.PLAYBACK_STATE_SUPPRESSED_BUFFERING;
} }
private static boolean isInvalidJoinTransition( private static boolean isInvalidJoinTransition(
@PlaybackState int oldState, @PlaybackState int newState) { @PlaybackState int oldState, @PlaybackState int newState) {
if (oldState != PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND if (oldState != PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND
&& oldState != PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND && oldState != PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND
&& oldState != PlaybackStats.PLAYBACK_STATE_SUSPENDED) { && oldState != PlaybackStats.PLAYBACK_STATE_INTERRUPTED_BY_AD) {
return false; return false;
} }
return newState != PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND return newState != PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND
&& newState != PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND && newState != PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND
&& newState != PlaybackStats.PLAYBACK_STATE_SUSPENDED && newState != PlaybackStats.PLAYBACK_STATE_INTERRUPTED_BY_AD
&& newState != PlaybackStats.PLAYBACK_STATE_PLAYING && newState != PlaybackStats.PLAYBACK_STATE_PLAYING
&& newState != PlaybackStats.PLAYBACK_STATE_PAUSED && newState != PlaybackStats.PLAYBACK_STATE_PAUSED
&& newState != PlaybackStats.PLAYBACK_STATE_SUPPRESSED
&& newState != PlaybackStats.PLAYBACK_STATE_ENDED; && newState != PlaybackStats.PLAYBACK_STATE_ENDED;
} }
} }