diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStats.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStats.java index b370c893de..893ecb07c2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStats.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStats.java @@ -16,8 +16,8 @@ package com.google.android.exoplayer2.analytics; import android.os.SystemClock; -import android.util.Pair; import androidx.annotation.IntDef; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime; @@ -28,11 +28,136 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Collections; import java.util.List; -import org.checkerframework.checker.nullness.compatqual.NullableType; /** Statistics about playbacks. */ public final class PlaybackStats { + /** Stores a playback state with the event time at which it became active. */ + public static final class EventTimeAndPlaybackState { + /** The event time at which the playback state became active. */ + public final EventTime eventTime; + /** The playback state that became active. */ + public final @PlaybackState int playbackState; + + /** + * Creates a new timed playback state event. + * + * @param eventTime The event time at which the playback state became active. + * @param playbackState The playback state that became active. + */ + public EventTimeAndPlaybackState(EventTime eventTime, @PlaybackState int playbackState) { + this.eventTime = eventTime; + this.playbackState = playbackState; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EventTimeAndPlaybackState that = (EventTimeAndPlaybackState) o; + if (playbackState != that.playbackState) { + return false; + } + return eventTime.equals(that.eventTime); + } + + @Override + public int hashCode() { + int result = eventTime.hashCode(); + result = 31 * result + playbackState; + return result; + } + } + + /** + * Stores a format with the event time at which it started being used, or {@code null} to indicate + * that no format was used. + */ + public static final class EventTimeAndFormat { + /** The event time associated with {@link #format}. */ + public final EventTime eventTime; + /** The format that started being used, or {@code null} if no format was used. */ + @Nullable public final Format format; + + /** + * Creates a new timed format event. + * + * @param eventTime The event time associated with {@code format}. + * @param format The format that started being used, or {@code null} if no format was used. + */ + public EventTimeAndFormat(EventTime eventTime, @Nullable Format format) { + this.eventTime = eventTime; + this.format = format; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EventTimeAndFormat that = (EventTimeAndFormat) o; + if (!eventTime.equals(that.eventTime)) { + return false; + } + return format != null ? format.equals(that.format) : that.format == null; + } + + @Override + public int hashCode() { + int result = eventTime.hashCode(); + result = 31 * result + (format != null ? format.hashCode() : 0); + return result; + } + } + + /** Stores an exception with the event time at which it occurred. */ + public static final class EventTimeAndException { + /** The event time at which the exception occurred. */ + public final EventTime eventTime; + /** The exception that was thrown. */ + public final Exception exception; + + /** + * Creates a new timed exception event. + * + * @param eventTime The event time at which the exception occurred. + * @param exception The exception that was thrown. + */ + public EventTimeAndException(EventTime eventTime, Exception exception) { + this.eventTime = eventTime; + this.exception = exception; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EventTimeAndException that = (EventTimeAndException) o; + if (!eventTime.equals(that.eventTime)) { + return false; + } + return exception.equals(that.exception); + } + + @Override + public int hashCode() { + int result = eventTime.hashCode(); + result = 31 * result + exception.hashCode(); + return result; + } + } + /** * State of a playback. One of {@link #PLAYBACK_STATE_NOT_STARTED}, {@link * #PLAYBACK_STATE_JOINING_FOREGROUND}, {@link #PLAYBACK_STATE_JOINING_BACKGROUND}, {@link @@ -258,10 +383,10 @@ public final class PlaybackStats { // Playback state stats. /** - * The playback state history as ordered pairs of the {@link EventTime} at which a state became - * active and the {@link PlaybackState}. + * The playback state history as {@link EventTimeAndPlaybackState EventTimeAndPlaybackStates} + * ordered by {@code EventTime.realTimeMs}. */ - public final List> playbackStateHistory; + public final List playbackStateHistory; /** * The media time history as an ordered list of long[2] arrays with [0] being the realtime as * returned by {@code SystemClock.elapsedRealtime()} and [1] being the media time at this @@ -319,15 +444,15 @@ public final class PlaybackStats { // Format stats. /** - * The video format history as ordered pairs of the {@link EventTime} at which a format started - * being used and the {@link Format}. The {@link Format} may be null if no video format was used. + * The video format history as {@link EventTimeAndFormat EventTimeAndFormats} ordered by {@code + * EventTime.realTimeMs}. The {@link Format} may be null if no video format was used. */ - public final List> videoFormatHistory; + public final List videoFormatHistory; /** - * The audio format history as ordered pairs of the {@link EventTime} at which a format started - * being used and the {@link Format}. The {@link Format} may be null if no audio format was used. + * The audio format history as {@link EventTimeAndFormat EventTimeAndFormats} ordered by {@code + * EventTime.realTimeMs}. The {@link Format} may be null if no audio format was used. */ - public final List> audioFormatHistory; + public final List audioFormatHistory; /** The total media time for which video format height data is available, in milliseconds. */ public final long totalVideoFormatHeightTimeMs; /** @@ -400,23 +525,23 @@ public final class PlaybackStats { */ public final int nonFatalErrorCount; /** - * The history of fatal errors as ordered pairs of the {@link EventTime} at which an error - * occurred and the error. Errors are fatal if playback stopped due to this error. + * The history of fatal errors as {@link EventTimeAndException EventTimeAndExceptions} ordered by + * {@code EventTime.realTimeMs}. Errors are fatal if playback stopped due to this error. */ - public final List> fatalErrorHistory; + public final List fatalErrorHistory; /** - * The history of non-fatal errors as ordered pairs of the {@link EventTime} at which an error - * occurred and the error. Error are non-fatal if playback can recover from the error without - * stopping. + * The history of non-fatal errors as {@link EventTimeAndException EventTimeAndExceptions} ordered + * by {@code EventTime.realTimeMs}. Errors are non-fatal if playback can recover from the error + * without stopping. */ - public final List> nonFatalErrorHistory; + public final List nonFatalErrorHistory; private final long[] playbackStateDurationsMs; /* package */ PlaybackStats( int playbackCount, long[] playbackStateDurationsMs, - List> playbackStateHistory, + List playbackStateHistory, List mediaTimeHistory, long firstReportedTimeMs, int foregroundPlaybackCount, @@ -431,8 +556,8 @@ public final class PlaybackStats { int totalRebufferCount, long maxRebufferTimeMs, int adPlaybackCount, - List> videoFormatHistory, - List> audioFormatHistory, + List videoFormatHistory, + List audioFormatHistory, long totalVideoFormatHeightTimeMs, long totalVideoFormatHeightTimeProduct, long totalVideoFormatBitrateTimeMs, @@ -452,8 +577,8 @@ public final class PlaybackStats { int fatalErrorPlaybackCount, int fatalErrorCount, int nonFatalErrorCount, - List> fatalErrorHistory, - List> nonFatalErrorHistory) { + List fatalErrorHistory, + List nonFatalErrorHistory) { this.playbackCount = playbackCount; this.playbackStateDurationsMs = playbackStateDurationsMs; this.playbackStateHistory = Collections.unmodifiableList(playbackStateHistory); @@ -515,11 +640,11 @@ public final class PlaybackStats { */ public @PlaybackState int getPlaybackStateAtTime(long realtimeMs) { @PlaybackState int state = PLAYBACK_STATE_NOT_STARTED; - for (Pair timeAndState : playbackStateHistory) { - if (timeAndState.first.realtimeMs > realtimeMs) { + for (EventTimeAndPlaybackState timeAndState : playbackStateHistory) { + if (timeAndState.eventTime.realtimeMs > realtimeMs) { break; } - state = timeAndState.second; + state = timeAndState.playbackState; } return state; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java index 2f8ca3a8cc..5927b9dd6e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.analytics; import android.os.SystemClock; -import android.util.Pair; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -25,6 +24,9 @@ import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; +import com.google.android.exoplayer2.analytics.PlaybackStats.EventTimeAndException; +import com.google.android.exoplayer2.analytics.PlaybackStats.EventTimeAndFormat; +import com.google.android.exoplayer2.analytics.PlaybackStats.EventTimeAndPlaybackState; import com.google.android.exoplayer2.analytics.PlaybackStats.PlaybackState; import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.MediaLoadData; @@ -42,7 +44,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.checkerframework.checker.nullness.compatqual.NullableType; /** * {@link AnalyticsListener} to gather {@link PlaybackStats} from the player. @@ -433,12 +434,12 @@ public final class PlaybackStatsListener // Final stats. private final boolean keepHistory; private final long[] playbackStateDurationsMs; - private final List> playbackStateHistory; + private final List playbackStateHistory; private final List mediaTimeHistory; - private final List> videoFormatHistory; - private final List> audioFormatHistory; - private final List> fatalErrorHistory; - private final List> nonFatalErrorHistory; + private final List videoFormatHistory; + private final List audioFormatHistory; + private final List fatalErrorHistory; + private final List nonFatalErrorHistory; private final boolean isAd; private long firstReportedTimeMs; @@ -589,7 +590,7 @@ public final class PlaybackStatsListener public void onFatalError(EventTime eventTime, Exception error) { fatalErrorCount++; if (keepHistory) { - fatalErrorHistory.add(Pair.create(eventTime, error)); + fatalErrorHistory.add(new EventTimeAndException(eventTime, error)); } hasFatalError = true; isInterruptedByAd = false; @@ -743,7 +744,7 @@ public final class PlaybackStatsListener public void onNonFatalError(EventTime eventTime, Exception error) { nonFatalErrorCount++; if (keepHistory) { - nonFatalErrorHistory.add(Pair.create(eventTime, error)); + nonFatalErrorHistory.add(new EventTimeAndException(eventTime, error)); } } @@ -776,9 +777,9 @@ public final class PlaybackStatsListener : playbackStateDurationsMs[PlaybackStats.PLAYBACK_STATE_JOINING_FOREGROUND]; boolean hasBackgroundJoin = playbackStateDurationsMs[PlaybackStats.PLAYBACK_STATE_JOINING_BACKGROUND] > 0; - List> videoHistory = + List videoHistory = isFinal ? videoFormatHistory : new ArrayList<>(videoFormatHistory); - List> audioHistory = + List audioHistory = isFinal ? audioFormatHistory : new ArrayList<>(audioFormatHistory); return new PlaybackStats( /* playbackCount= */ 1, @@ -864,7 +865,7 @@ public final class PlaybackStatsListener currentPlaybackState = newPlaybackState; currentPlaybackStateStartTimeMs = eventTime.realtimeMs; if (keepHistory) { - playbackStateHistory.add(Pair.create(eventTime, currentPlaybackState)); + playbackStateHistory.add(new EventTimeAndPlaybackState(eventTime, currentPlaybackState)); } } @@ -973,7 +974,7 @@ public final class PlaybackStatsListener } currentVideoFormat = newFormat; if (keepHistory) { - videoFormatHistory.add(Pair.create(eventTime, currentVideoFormat)); + videoFormatHistory.add(new EventTimeAndFormat(eventTime, currentVideoFormat)); } } @@ -989,7 +990,7 @@ public final class PlaybackStatsListener } currentAudioFormat = newFormat; if (keepHistory) { - audioFormatHistory.add(Pair.create(eventTime, currentAudioFormat)); + audioFormatHistory.add(new EventTimeAndFormat(eventTime, currentAudioFormat)); } }