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_PLAYING}, {@link #PLAYBACK_STATE_PAUSED}, {@link #PLAYBACK_STATE_SEEKING},
* {@link #PLAYBACK_STATE_BUFFERING}, {@link #PLAYBACK_STATE_PAUSED_BUFFERING}, {@link
* #PLAYBACK_STATE_SEEK_BUFFERING}, {@link #PLAYBACK_STATE_ENDED}, {@link
* #PLAYBACK_STATE_STOPPED}, {@link #PLAYBACK_STATE_FAILED} or {@link #PLAYBACK_STATE_SUSPENDED}.
* #PLAYBACK_STATE_SEEK_BUFFERING}, {@link #PLAYBACK_STATE_SUPPRESSED}, {@link
* #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
@Retention(RetentionPolicy.SOURCE)
@ -54,10 +56,13 @@ public final class PlaybackStats {
PLAYBACK_STATE_BUFFERING,
PLAYBACK_STATE_PAUSED_BUFFERING,
PLAYBACK_STATE_SEEK_BUFFERING,
PLAYBACK_STATE_SUPPRESSED,
PLAYBACK_STATE_SUPPRESSED_BUFFERING,
PLAYBACK_STATE_ENDED,
PLAYBACK_STATE_STOPPED,
PLAYBACK_STATE_FAILED,
PLAYBACK_STATE_SUSPENDED
PLAYBACK_STATE_INTERRUPTED_BY_AD,
PLAYBACK_STATE_ABANDONED
})
@interface PlaybackState {}
/** Playback has not started (initial state). */
@ -72,22 +77,28 @@ public final class PlaybackStats {
public static final int PLAYBACK_STATE_PAUSED = 4;
/** Playback is handling a seek. */
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;
/** Playback is buffering while paused. */
public static final int PLAYBACK_STATE_PAUSED_BUFFERING = 7;
/** Playback is buffering after a seek. */
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. */
public static final int PLAYBACK_STATE_ENDED = 9;
/** Playback is stopped and can be resumed. */
public static final int PLAYBACK_STATE_STOPPED = 10;
public static final int PLAYBACK_STATE_ENDED = 11;
/** Playback is stopped and can be restarted. */
public static final int PLAYBACK_STATE_STOPPED = 12;
/** Playback is stopped due a fatal error and can be retried. */
public static final int PLAYBACK_STATE_FAILED = 11;
/** Playback is suspended, e.g. because the user left or it is interrupted by another playback. */
public static final int PLAYBACK_STATE_SUSPENDED = 12;
public static final int PLAYBACK_STATE_FAILED = 13;
/** Playback is interrupted by an ad. */
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. */
/* package */ static final int PLAYBACK_STATE_COUNT = 13;
/* package */ static final int PLAYBACK_STATE_COUNT = 16;
/** Empty playback stats. */
public static final PlaybackStats EMPTY = merge(/* nothing */ );

View File

@ -81,6 +81,7 @@ public final class PlaybackStatsListener
@Nullable private String activeAdPlayback;
private boolean playWhenReady;
@Player.State private int playbackState;
private boolean isSuppressed;
private float playbackSpeed;
/**
@ -205,7 +206,7 @@ public final class PlaybackStatsListener
eventTime.currentPlaybackPositionMs,
eventTime.totalBufferedDurationMs);
Assertions.checkNotNull(playbackStatsTrackers.get(contentSession))
.onSuspended(contentEventTime, /* belongsToPlayback= */ true);
.onInterruptedByAd(contentEventTime);
}
@Override
@ -222,7 +223,7 @@ public final class PlaybackStatsListener
tracker.onPlayerStateChanged(
eventTime, /* playWhenReady= */ true, Player.STATE_ENDED, /* belongsToPlayback= */ false);
}
tracker.onSuspended(eventTime, /* belongsToPlayback= */ false);
tracker.onFinished(eventTime);
PlaybackStats playbackStats = tracker.build(/* isFinal= */ true);
finishedPlaybackStats = PlaybackStats.merge(finishedPlaybackStats, playbackStats);
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
public void onTimelineChanged(EventTime eventTime, int reason) {
sessionManager.handleTimelineUpdate(eventTime);
@ -456,9 +470,11 @@ public final class PlaybackStatsListener
private long currentPlaybackStateStartTimeMs;
private boolean isSeeking;
private boolean isForeground;
private boolean isSuspended;
private boolean isInterruptedByAd;
private boolean isFinished;
private boolean playWhenReady;
@Player.State private int playerPlaybackState;
private boolean isSuppressed;
private boolean hasFatalError;
private boolean startedLoading;
private long lastRebufferStartTimeMs;
@ -515,18 +531,32 @@ public final class PlaybackStatsListener
hasFatalError = false;
}
if (playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED) {
isSuspended = false;
isInterruptedByAd = false;
}
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.
*
* @param eventTime The {@link EventTime}.
*/
public void onPositionDiscontinuity(EventTime eventTime) {
isSuspended = false;
isInterruptedByAd = false;
maybeUpdatePlaybackState(eventTime, /* belongsToPlayback= */ true);
}
@ -561,7 +591,7 @@ public final class PlaybackStatsListener
fatalErrorHistory.add(Pair.create(eventTime, error));
}
hasFatalError = true;
isSuspended = false;
isInterruptedByAd = false;
isSeeking = false;
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
* permanently.
* Notifies the tracker that the current playback has been interrupted for ad playback.
*
* @param eventTime The {@link EventTime}.
* @param belongsToPlayback Whether the {@code eventTime} belongs to the current playback.
*/
public void onSuspended(EventTime eventTime, boolean belongsToPlayback) {
isSuspended = true;
public void onInterruptedByAd(EventTime eventTime) {
isInterruptedByAd = true;
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++;
lastRebufferStartTimeMs = eventTime.realtimeMs;
}
if (newPlaybackState == PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING
&& currentPlaybackState == PlaybackStats.PLAYBACK_STATE_BUFFERING) {
if (isRebufferingState(currentPlaybackState)
&& currentPlaybackState != PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING
&& newPlaybackState == PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING) {
pauseBufferCount++;
}
@ -829,11 +868,11 @@ public final class PlaybackStatsListener
}
private @PlaybackState int resolveNewPlaybackState() {
if (isSuspended) {
if (isFinished) {
// Keep VIDEO_STATE_ENDED if playback naturally ended (or progressed to next item).
return currentPlaybackState == PlaybackStats.PLAYBACK_STATE_ENDED
? PlaybackStats.PLAYBACK_STATE_ENDED
: PlaybackStats.PLAYBACK_STATE_SUSPENDED;
: PlaybackStats.PLAYBACK_STATE_ABANDONED;
} else if (isSeeking) {
// Seeking takes precedence over errors such that we report a seek while in error state.
return PlaybackStats.PLAYBACK_STATE_SEEKING;
@ -844,26 +883,34 @@ public final class PlaybackStatsListener
return startedLoading
? PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND
: PlaybackStats.PLAYBACK_STATE_NOT_STARTED;
} else if (isInterruptedByAd) {
return PlaybackStats.PLAYBACK_STATE_INTERRUPTED_BY_AD;
} else if (playerPlaybackState == Player.STATE_ENDED) {
return PlaybackStats.PLAYBACK_STATE_ENDED;
} else if (playerPlaybackState == Player.STATE_BUFFERING) {
if (currentPlaybackState == PlaybackStats.PLAYBACK_STATE_NOT_STARTED
|| currentPlaybackState == PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND
|| currentPlaybackState == PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND
|| currentPlaybackState == PlaybackStats.PLAYBACK_STATE_SUSPENDED) {
|| currentPlaybackState == PlaybackStats.PLAYBACK_STATE_INTERRUPTED_BY_AD) {
return PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND;
}
if (currentPlaybackState == PlaybackStats.PLAYBACK_STATE_SEEKING
|| currentPlaybackState == PlaybackStats.PLAYBACK_STATE_SEEK_BUFFERING) {
return PlaybackStats.PLAYBACK_STATE_SEEK_BUFFERING;
}
return playWhenReady
? PlaybackStats.PLAYBACK_STATE_BUFFERING
: PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING;
if (!playWhenReady) {
return PlaybackStats.PLAYBACK_STATE_PAUSED_BUFFERING;
}
return isSuppressed
? PlaybackStats.PLAYBACK_STATE_SUPPRESSED_BUFFERING
: PlaybackStats.PLAYBACK_STATE_BUFFERING;
} else if (playerPlaybackState == Player.STATE_READY) {
return playWhenReady
? PlaybackStats.PLAYBACK_STATE_PLAYING
: PlaybackStats.PLAYBACK_STATE_PAUSED;
if (!playWhenReady) {
return PlaybackStats.PLAYBACK_STATE_PAUSED;
}
return isSuppressed
? PlaybackStats.PLAYBACK_STATE_SUPPRESSED
: PlaybackStats.PLAYBACK_STATE_PLAYING;
} else if (playerPlaybackState == Player.STATE_IDLE
&& currentPlaybackState != PlaybackStats.PLAYBACK_STATE_NOT_STARTED) {
// 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) {
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) {
@ -984,21 +1032,23 @@ public final class PlaybackStatsListener
private static boolean isRebufferingState(@PlaybackState int state) {
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(
@PlaybackState int oldState, @PlaybackState int newState) {
if (oldState != PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND
&& oldState != PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND
&& oldState != PlaybackStats.PLAYBACK_STATE_SUSPENDED) {
&& oldState != PlaybackStats.PLAYBACK_STATE_INTERRUPTED_BY_AD) {
return false;
}
return newState != PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND
&& 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_PAUSED
&& newState != PlaybackStats.PLAYBACK_STATE_SUPPRESSED
&& newState != PlaybackStats.PLAYBACK_STATE_ENDED;
}
}