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:
parent
f0723a27b7
commit
6a2d04e3ce
@ -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 */ );
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user