mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add remaining PlaybackStatsListener metrics.
This adds all the non-playback-state metrics, like format, error, bandwidth and renderer performance metrics. PiperOrigin-RevId: 250668854
This commit is contained in:
parent
77595da159
commit
fd1179aaa1
4 changed files with 842 additions and 17 deletions
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
### dev-v2 (not yet released) ###
|
### dev-v2 (not yet released) ###
|
||||||
|
|
||||||
|
* Add `PlaybackStatsListener` to collect `PlaybackStats` for playbacks analysis
|
||||||
|
and analytics reporting (TODO: link to developer guide page/blog post).
|
||||||
* Add basic DRM support to the Cast demo app.
|
* Add basic DRM support to the Cast demo app.
|
||||||
* Add `ResolvingDataSource` for just-in-time resolution of `DataSpec`s
|
* Add `ResolvingDataSource` for just-in-time resolution of `DataSpec`s
|
||||||
([#5779](https://github.com/google/ExoPlayer/issues/5779)).
|
([#5779](https://github.com/google/ExoPlayer/issues/5779)).
|
||||||
|
|
|
||||||
|
|
@ -1373,6 +1373,38 @@ public final class Format implements Parcelable {
|
||||||
accessibilityChannel);
|
accessibilityChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Format copyWithVideoSize(int width, int height) {
|
||||||
|
return new Format(
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
selectionFlags,
|
||||||
|
roleFlags,
|
||||||
|
bitrate,
|
||||||
|
codecs,
|
||||||
|
metadata,
|
||||||
|
containerMimeType,
|
||||||
|
sampleMimeType,
|
||||||
|
maxInputSize,
|
||||||
|
initializationData,
|
||||||
|
drmInitData,
|
||||||
|
subsampleOffsetUs,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
frameRate,
|
||||||
|
rotationDegrees,
|
||||||
|
pixelWidthHeightRatio,
|
||||||
|
projectionData,
|
||||||
|
stereoMode,
|
||||||
|
colorInfo,
|
||||||
|
channelCount,
|
||||||
|
sampleRate,
|
||||||
|
pcmEncoding,
|
||||||
|
encoderDelay,
|
||||||
|
encoderPadding,
|
||||||
|
language,
|
||||||
|
accessibilityChannel);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of pixels if this is a video format whose {@link #width} and {@link #height}
|
* Returns the number of pixels if this is a video format whose {@link #width} and {@link #height}
|
||||||
* are known, or {@link #NO_VALUE} otherwise
|
* are known, or {@link #NO_VALUE} otherwise
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import android.os.SystemClock;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime;
|
import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
|
|
@ -27,6 +28,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
|
|
||||||
/** Statistics about playbacks. */
|
/** Statistics about playbacks. */
|
||||||
public final class PlaybackStats {
|
public final class PlaybackStats {
|
||||||
|
|
@ -109,12 +111,31 @@ public final class PlaybackStats {
|
||||||
int backgroundJoiningCount = 0;
|
int backgroundJoiningCount = 0;
|
||||||
long totalValidJoinTimeMs = C.TIME_UNSET;
|
long totalValidJoinTimeMs = C.TIME_UNSET;
|
||||||
int validJoinTimeCount = 0;
|
int validJoinTimeCount = 0;
|
||||||
int pauseCount = 0;
|
int totalPauseCount = 0;
|
||||||
int pauseBufferCount = 0;
|
int totalPauseBufferCount = 0;
|
||||||
int seekCount = 0;
|
int totalSeekCount = 0;
|
||||||
int rebufferCount = 0;
|
int totalRebufferCount = 0;
|
||||||
long maxRebufferTimeMs = C.TIME_UNSET;
|
long maxRebufferTimeMs = C.TIME_UNSET;
|
||||||
int adCount = 0;
|
int adPlaybackCount = 0;
|
||||||
|
long totalVideoFormatHeightTimeMs = 0;
|
||||||
|
long totalVideoFormatHeightTimeProduct = 0;
|
||||||
|
long totalVideoFormatBitrateTimeMs = 0;
|
||||||
|
long totalVideoFormatBitrateTimeProduct = 0;
|
||||||
|
long totalAudioFormatTimeMs = 0;
|
||||||
|
long totalAudioFormatBitrateTimeProduct = 0;
|
||||||
|
int initialVideoFormatHeightCount = 0;
|
||||||
|
int initialVideoFormatBitrateCount = 0;
|
||||||
|
int totalInitialVideoFormatHeight = C.LENGTH_UNSET;
|
||||||
|
long totalInitialVideoFormatBitrate = C.LENGTH_UNSET;
|
||||||
|
int initialAudioFormatBitrateCount = 0;
|
||||||
|
long totalInitialAudioFormatBitrate = C.LENGTH_UNSET;
|
||||||
|
long totalBandwidthTimeMs = 0;
|
||||||
|
long totalBandwidthBytes = 0;
|
||||||
|
long totalDroppedFrames = 0;
|
||||||
|
long totalAudioUnderruns = 0;
|
||||||
|
int fatalErrorPlaybackCount = 0;
|
||||||
|
int fatalErrorCount = 0;
|
||||||
|
int nonFatalErrorCount = 0;
|
||||||
for (PlaybackStats stats : playbackStats) {
|
for (PlaybackStats stats : playbackStats) {
|
||||||
playbackCount += stats.playbackCount;
|
playbackCount += stats.playbackCount;
|
||||||
for (int i = 0; i < PLAYBACK_STATE_COUNT; i++) {
|
for (int i = 0; i < PLAYBACK_STATE_COUNT; i++) {
|
||||||
|
|
@ -135,21 +156,53 @@ public final class PlaybackStats {
|
||||||
totalValidJoinTimeMs += stats.totalValidJoinTimeMs;
|
totalValidJoinTimeMs += stats.totalValidJoinTimeMs;
|
||||||
}
|
}
|
||||||
validJoinTimeCount += stats.validJoinTimeCount;
|
validJoinTimeCount += stats.validJoinTimeCount;
|
||||||
pauseCount += stats.totalPauseCount;
|
totalPauseCount += stats.totalPauseCount;
|
||||||
pauseBufferCount += stats.totalPauseBufferCount;
|
totalPauseBufferCount += stats.totalPauseBufferCount;
|
||||||
seekCount += stats.totalSeekCount;
|
totalSeekCount += stats.totalSeekCount;
|
||||||
rebufferCount += stats.totalRebufferCount;
|
totalRebufferCount += stats.totalRebufferCount;
|
||||||
if (maxRebufferTimeMs == C.TIME_UNSET) {
|
if (maxRebufferTimeMs == C.TIME_UNSET) {
|
||||||
maxRebufferTimeMs = stats.maxRebufferTimeMs;
|
maxRebufferTimeMs = stats.maxRebufferTimeMs;
|
||||||
} else if (stats.maxRebufferTimeMs != C.TIME_UNSET) {
|
} else if (stats.maxRebufferTimeMs != C.TIME_UNSET) {
|
||||||
maxRebufferTimeMs = Math.max(maxRebufferTimeMs, stats.maxRebufferTimeMs);
|
maxRebufferTimeMs = Math.max(maxRebufferTimeMs, stats.maxRebufferTimeMs);
|
||||||
}
|
}
|
||||||
adCount += stats.adPlaybackCount;
|
adPlaybackCount += stats.adPlaybackCount;
|
||||||
|
totalVideoFormatHeightTimeMs += stats.totalVideoFormatHeightTimeMs;
|
||||||
|
totalVideoFormatHeightTimeProduct += stats.totalVideoFormatHeightTimeProduct;
|
||||||
|
totalVideoFormatBitrateTimeMs += stats.totalVideoFormatBitrateTimeMs;
|
||||||
|
totalVideoFormatBitrateTimeProduct += stats.totalVideoFormatBitrateTimeProduct;
|
||||||
|
totalAudioFormatTimeMs += stats.totalAudioFormatTimeMs;
|
||||||
|
totalAudioFormatBitrateTimeProduct += stats.totalAudioFormatBitrateTimeProduct;
|
||||||
|
initialVideoFormatHeightCount += stats.initialVideoFormatHeightCount;
|
||||||
|
initialVideoFormatBitrateCount += stats.initialVideoFormatBitrateCount;
|
||||||
|
if (totalInitialVideoFormatHeight == C.LENGTH_UNSET) {
|
||||||
|
totalInitialVideoFormatHeight = stats.totalInitialVideoFormatHeight;
|
||||||
|
} else if (stats.totalInitialVideoFormatHeight != C.LENGTH_UNSET) {
|
||||||
|
totalInitialVideoFormatHeight += stats.totalInitialVideoFormatHeight;
|
||||||
|
}
|
||||||
|
if (totalInitialVideoFormatBitrate == C.LENGTH_UNSET) {
|
||||||
|
totalInitialVideoFormatBitrate = stats.totalInitialVideoFormatBitrate;
|
||||||
|
} else if (stats.totalInitialVideoFormatBitrate != C.LENGTH_UNSET) {
|
||||||
|
totalInitialVideoFormatBitrate += stats.totalInitialVideoFormatBitrate;
|
||||||
|
}
|
||||||
|
initialAudioFormatBitrateCount += stats.initialAudioFormatBitrateCount;
|
||||||
|
if (totalInitialAudioFormatBitrate == C.LENGTH_UNSET) {
|
||||||
|
totalInitialAudioFormatBitrate = stats.totalInitialAudioFormatBitrate;
|
||||||
|
} else if (stats.totalInitialAudioFormatBitrate != C.LENGTH_UNSET) {
|
||||||
|
totalInitialAudioFormatBitrate += stats.totalInitialAudioFormatBitrate;
|
||||||
|
}
|
||||||
|
totalBandwidthTimeMs += stats.totalBandwidthTimeMs;
|
||||||
|
totalBandwidthBytes += stats.totalBandwidthBytes;
|
||||||
|
totalDroppedFrames += stats.totalDroppedFrames;
|
||||||
|
totalAudioUnderruns += stats.totalAudioUnderruns;
|
||||||
|
fatalErrorPlaybackCount += stats.fatalErrorPlaybackCount;
|
||||||
|
fatalErrorCount += stats.fatalErrorCount;
|
||||||
|
nonFatalErrorCount += stats.nonFatalErrorCount;
|
||||||
}
|
}
|
||||||
return new PlaybackStats(
|
return new PlaybackStats(
|
||||||
playbackCount,
|
playbackCount,
|
||||||
playbackStateDurationsMs,
|
playbackStateDurationsMs,
|
||||||
/* playbackStateHistory */ Collections.emptyList(),
|
/* playbackStateHistory */ Collections.emptyList(),
|
||||||
|
/* mediaTimeHistory= */ Collections.emptyList(),
|
||||||
firstReportedTimeMs,
|
firstReportedTimeMs,
|
||||||
foregroundPlaybackCount,
|
foregroundPlaybackCount,
|
||||||
abandonedBeforeReadyCount,
|
abandonedBeforeReadyCount,
|
||||||
|
|
@ -157,12 +210,35 @@ public final class PlaybackStats {
|
||||||
backgroundJoiningCount,
|
backgroundJoiningCount,
|
||||||
totalValidJoinTimeMs,
|
totalValidJoinTimeMs,
|
||||||
validJoinTimeCount,
|
validJoinTimeCount,
|
||||||
pauseCount,
|
totalPauseCount,
|
||||||
pauseBufferCount,
|
totalPauseBufferCount,
|
||||||
seekCount,
|
totalSeekCount,
|
||||||
rebufferCount,
|
totalRebufferCount,
|
||||||
maxRebufferTimeMs,
|
maxRebufferTimeMs,
|
||||||
adCount);
|
adPlaybackCount,
|
||||||
|
/* videoFormatHistory= */ Collections.emptyList(),
|
||||||
|
/* audioFormatHistory= */ Collections.emptyList(),
|
||||||
|
totalVideoFormatHeightTimeMs,
|
||||||
|
totalVideoFormatHeightTimeProduct,
|
||||||
|
totalVideoFormatBitrateTimeMs,
|
||||||
|
totalVideoFormatBitrateTimeProduct,
|
||||||
|
totalAudioFormatTimeMs,
|
||||||
|
totalAudioFormatBitrateTimeProduct,
|
||||||
|
initialVideoFormatHeightCount,
|
||||||
|
initialVideoFormatBitrateCount,
|
||||||
|
totalInitialVideoFormatHeight,
|
||||||
|
totalInitialVideoFormatBitrate,
|
||||||
|
initialAudioFormatBitrateCount,
|
||||||
|
totalInitialAudioFormatBitrate,
|
||||||
|
totalBandwidthTimeMs,
|
||||||
|
totalBandwidthBytes,
|
||||||
|
totalDroppedFrames,
|
||||||
|
totalAudioUnderruns,
|
||||||
|
fatalErrorPlaybackCount,
|
||||||
|
fatalErrorCount,
|
||||||
|
nonFatalErrorCount,
|
||||||
|
/* fatalErrorHistory= */ Collections.emptyList(),
|
||||||
|
/* nonFatalErrorHistory= */ Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The number of individual playbacks for which these stats were collected. */
|
/** The number of individual playbacks for which these stats were collected. */
|
||||||
|
|
@ -175,6 +251,12 @@ public final class PlaybackStats {
|
||||||
* active and the {@link PlaybackState}.
|
* active and the {@link PlaybackState}.
|
||||||
*/
|
*/
|
||||||
public final List<Pair<EventTime, @PlaybackState Integer>> playbackStateHistory;
|
public final List<Pair<EventTime, @PlaybackState Integer>> 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
|
||||||
|
* realtime, in milliseconds.
|
||||||
|
*/
|
||||||
|
public final List<long[]> mediaTimeHistory;
|
||||||
/**
|
/**
|
||||||
* The elapsed real-time as returned by {@code SystemClock.elapsedRealtime()} of the first
|
* The elapsed real-time as returned by {@code SystemClock.elapsedRealtime()} of the first
|
||||||
* reported playback event, or {@link C#TIME_UNSET} if no event has been reported.
|
* reported playback event, or {@link C#TIME_UNSET} if no event has been reported.
|
||||||
|
|
@ -223,12 +305,108 @@ public final class PlaybackStats {
|
||||||
/** The number of ad playbacks. */
|
/** The number of ad playbacks. */
|
||||||
public final int adPlaybackCount;
|
public final int adPlaybackCount;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
*/
|
||||||
|
public final List<Pair<EventTime, @NullableType Format>> 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.
|
||||||
|
*/
|
||||||
|
public final List<Pair<EventTime, @NullableType Format>> audioFormatHistory;
|
||||||
|
/** The total media time for which video format height data is available, in milliseconds. */
|
||||||
|
public final long totalVideoFormatHeightTimeMs;
|
||||||
|
/**
|
||||||
|
* The accumulated sum of all video format heights, in pixels, times the time the format was used
|
||||||
|
* for playback, in milliseconds.
|
||||||
|
*/
|
||||||
|
public final long totalVideoFormatHeightTimeProduct;
|
||||||
|
/** The total media time for which video format bitrate data is available, in milliseconds. */
|
||||||
|
public final long totalVideoFormatBitrateTimeMs;
|
||||||
|
/**
|
||||||
|
* The accumulated sum of all video format bitrates, in bits per second, times the time the format
|
||||||
|
* was used for playback, in milliseconds.
|
||||||
|
*/
|
||||||
|
public final long totalVideoFormatBitrateTimeProduct;
|
||||||
|
/** The total media time for which audio format data is available, in milliseconds. */
|
||||||
|
public final long totalAudioFormatTimeMs;
|
||||||
|
/**
|
||||||
|
* The accumulated sum of all audio format bitrates, in bits per second, times the time the format
|
||||||
|
* was used for playback, in milliseconds.
|
||||||
|
*/
|
||||||
|
public final long totalAudioFormatBitrateTimeProduct;
|
||||||
|
/** The number of playbacks with initial video format height data. */
|
||||||
|
public final int initialVideoFormatHeightCount;
|
||||||
|
/** The number of playbacks with initial video format bitrate data. */
|
||||||
|
public final int initialVideoFormatBitrateCount;
|
||||||
|
/**
|
||||||
|
* The total initial video format height for all playbacks, in pixels, or {@link C#LENGTH_UNSET}
|
||||||
|
* if no initial video format data is available.
|
||||||
|
*/
|
||||||
|
public final int totalInitialVideoFormatHeight;
|
||||||
|
/**
|
||||||
|
* The total initial video format bitrate for all playbacks, in bits per second, or {@link
|
||||||
|
* C#LENGTH_UNSET} if no initial video format data is available.
|
||||||
|
*/
|
||||||
|
public final long totalInitialVideoFormatBitrate;
|
||||||
|
/** The number of playbacks with initial audio format bitrate data. */
|
||||||
|
public final int initialAudioFormatBitrateCount;
|
||||||
|
/**
|
||||||
|
* The total initial audio format bitrate for all playbacks, in bits per second, or {@link
|
||||||
|
* C#LENGTH_UNSET} if no initial audio format data is available.
|
||||||
|
*/
|
||||||
|
public final long totalInitialAudioFormatBitrate;
|
||||||
|
|
||||||
|
// Bandwidth stats.
|
||||||
|
|
||||||
|
/** The total time for which bandwidth measurement data is available, in milliseconds. */
|
||||||
|
public final long totalBandwidthTimeMs;
|
||||||
|
/** The total bytes transferred during {@link #totalBandwidthTimeMs}. */
|
||||||
|
public final long totalBandwidthBytes;
|
||||||
|
|
||||||
|
// Renderer quality stats.
|
||||||
|
|
||||||
|
/** The total number of dropped video frames. */
|
||||||
|
public final long totalDroppedFrames;
|
||||||
|
/** The total number of audio underruns. */
|
||||||
|
public final long totalAudioUnderruns;
|
||||||
|
|
||||||
|
// Error stats.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of playback with at least one fatal error. Errors are fatal if playback
|
||||||
|
* stopped due to this error.
|
||||||
|
*/
|
||||||
|
public final int fatalErrorPlaybackCount;
|
||||||
|
/** The total number of fatal errors. Errors are fatal if playback stopped due to this error. */
|
||||||
|
public final int fatalErrorCount;
|
||||||
|
/**
|
||||||
|
* The total number of non-fatal errors. Error are non-fatal if playback can recover from the
|
||||||
|
* error without stopping.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
public final List<Pair<EventTime, Exception>> 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.
|
||||||
|
*/
|
||||||
|
public final List<Pair<EventTime, Exception>> nonFatalErrorHistory;
|
||||||
|
|
||||||
private final long[] playbackStateDurationsMs;
|
private final long[] playbackStateDurationsMs;
|
||||||
|
|
||||||
/* package */ PlaybackStats(
|
/* package */ PlaybackStats(
|
||||||
int playbackCount,
|
int playbackCount,
|
||||||
long[] playbackStateDurationsMs,
|
long[] playbackStateDurationsMs,
|
||||||
List<Pair<EventTime, @PlaybackState Integer>> playbackStateHistory,
|
List<Pair<EventTime, @PlaybackState Integer>> playbackStateHistory,
|
||||||
|
List<long[]> mediaTimeHistory,
|
||||||
long firstReportedTimeMs,
|
long firstReportedTimeMs,
|
||||||
int foregroundPlaybackCount,
|
int foregroundPlaybackCount,
|
||||||
int abandonedBeforeReadyCount,
|
int abandonedBeforeReadyCount,
|
||||||
|
|
@ -241,10 +419,34 @@ public final class PlaybackStats {
|
||||||
int totalSeekCount,
|
int totalSeekCount,
|
||||||
int totalRebufferCount,
|
int totalRebufferCount,
|
||||||
long maxRebufferTimeMs,
|
long maxRebufferTimeMs,
|
||||||
int adPlaybackCount) {
|
int adPlaybackCount,
|
||||||
|
List<Pair<EventTime, @NullableType Format>> videoFormatHistory,
|
||||||
|
List<Pair<EventTime, @NullableType Format>> audioFormatHistory,
|
||||||
|
long totalVideoFormatHeightTimeMs,
|
||||||
|
long totalVideoFormatHeightTimeProduct,
|
||||||
|
long totalVideoFormatBitrateTimeMs,
|
||||||
|
long totalVideoFormatBitrateTimeProduct,
|
||||||
|
long totalAudioFormatTimeMs,
|
||||||
|
long totalAudioFormatBitrateTimeProduct,
|
||||||
|
int initialVideoFormatHeightCount,
|
||||||
|
int initialVideoFormatBitrateCount,
|
||||||
|
int totalInitialVideoFormatHeight,
|
||||||
|
long totalInitialVideoFormatBitrate,
|
||||||
|
int initialAudioFormatBitrateCount,
|
||||||
|
long totalInitialAudioFormatBitrate,
|
||||||
|
long totalBandwidthTimeMs,
|
||||||
|
long totalBandwidthBytes,
|
||||||
|
long totalDroppedFrames,
|
||||||
|
long totalAudioUnderruns,
|
||||||
|
int fatalErrorPlaybackCount,
|
||||||
|
int fatalErrorCount,
|
||||||
|
int nonFatalErrorCount,
|
||||||
|
List<Pair<EventTime, Exception>> fatalErrorHistory,
|
||||||
|
List<Pair<EventTime, Exception>> nonFatalErrorHistory) {
|
||||||
this.playbackCount = playbackCount;
|
this.playbackCount = playbackCount;
|
||||||
this.playbackStateDurationsMs = playbackStateDurationsMs;
|
this.playbackStateDurationsMs = playbackStateDurationsMs;
|
||||||
this.playbackStateHistory = Collections.unmodifiableList(playbackStateHistory);
|
this.playbackStateHistory = Collections.unmodifiableList(playbackStateHistory);
|
||||||
|
this.mediaTimeHistory = Collections.unmodifiableList(mediaTimeHistory);
|
||||||
this.firstReportedTimeMs = firstReportedTimeMs;
|
this.firstReportedTimeMs = firstReportedTimeMs;
|
||||||
this.foregroundPlaybackCount = foregroundPlaybackCount;
|
this.foregroundPlaybackCount = foregroundPlaybackCount;
|
||||||
this.abandonedBeforeReadyCount = abandonedBeforeReadyCount;
|
this.abandonedBeforeReadyCount = abandonedBeforeReadyCount;
|
||||||
|
|
@ -258,6 +460,29 @@ public final class PlaybackStats {
|
||||||
this.totalRebufferCount = totalRebufferCount;
|
this.totalRebufferCount = totalRebufferCount;
|
||||||
this.maxRebufferTimeMs = maxRebufferTimeMs;
|
this.maxRebufferTimeMs = maxRebufferTimeMs;
|
||||||
this.adPlaybackCount = adPlaybackCount;
|
this.adPlaybackCount = adPlaybackCount;
|
||||||
|
this.videoFormatHistory = Collections.unmodifiableList(videoFormatHistory);
|
||||||
|
this.audioFormatHistory = Collections.unmodifiableList(audioFormatHistory);
|
||||||
|
this.totalVideoFormatHeightTimeMs = totalVideoFormatHeightTimeMs;
|
||||||
|
this.totalVideoFormatHeightTimeProduct = totalVideoFormatHeightTimeProduct;
|
||||||
|
this.totalVideoFormatBitrateTimeMs = totalVideoFormatBitrateTimeMs;
|
||||||
|
this.totalVideoFormatBitrateTimeProduct = totalVideoFormatBitrateTimeProduct;
|
||||||
|
this.totalAudioFormatTimeMs = totalAudioFormatTimeMs;
|
||||||
|
this.totalAudioFormatBitrateTimeProduct = totalAudioFormatBitrateTimeProduct;
|
||||||
|
this.initialVideoFormatHeightCount = initialVideoFormatHeightCount;
|
||||||
|
this.initialVideoFormatBitrateCount = initialVideoFormatBitrateCount;
|
||||||
|
this.totalInitialVideoFormatHeight = totalInitialVideoFormatHeight;
|
||||||
|
this.totalInitialVideoFormatBitrate = totalInitialVideoFormatBitrate;
|
||||||
|
this.initialAudioFormatBitrateCount = initialAudioFormatBitrateCount;
|
||||||
|
this.totalInitialAudioFormatBitrate = totalInitialAudioFormatBitrate;
|
||||||
|
this.totalBandwidthTimeMs = totalBandwidthTimeMs;
|
||||||
|
this.totalBandwidthBytes = totalBandwidthBytes;
|
||||||
|
this.totalDroppedFrames = totalDroppedFrames;
|
||||||
|
this.totalAudioUnderruns = totalAudioUnderruns;
|
||||||
|
this.fatalErrorPlaybackCount = fatalErrorPlaybackCount;
|
||||||
|
this.fatalErrorCount = fatalErrorCount;
|
||||||
|
this.nonFatalErrorCount = nonFatalErrorCount;
|
||||||
|
this.fatalErrorHistory = Collections.unmodifiableList(fatalErrorHistory);
|
||||||
|
this.nonFatalErrorHistory = Collections.unmodifiableList(nonFatalErrorHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -289,6 +514,41 @@ public final class PlaybackStats {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the estimated media time at the given realtime, in milliseconds, or {@link
|
||||||
|
* C#TIME_UNSET} if the media time history is unknown.
|
||||||
|
*
|
||||||
|
* @param realtimeMs The realtime as returned by {@link SystemClock#elapsedRealtime()}.
|
||||||
|
* @return The estimated media time in milliseconds at this realtime, {@link C#TIME_UNSET} if no
|
||||||
|
* estimate can be given.
|
||||||
|
*/
|
||||||
|
public long getMediaTimeMsAtRealtimeMs(long realtimeMs) {
|
||||||
|
if (mediaTimeHistory.isEmpty()) {
|
||||||
|
return C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
int nextIndex = 0;
|
||||||
|
while (nextIndex < mediaTimeHistory.size()
|
||||||
|
&& mediaTimeHistory.get(nextIndex)[0] <= realtimeMs) {
|
||||||
|
nextIndex++;
|
||||||
|
}
|
||||||
|
if (nextIndex == 0) {
|
||||||
|
return mediaTimeHistory.get(0)[1];
|
||||||
|
}
|
||||||
|
if (nextIndex == mediaTimeHistory.size()) {
|
||||||
|
return mediaTimeHistory.get(mediaTimeHistory.size() - 1)[1];
|
||||||
|
}
|
||||||
|
long prevRealtimeMs = mediaTimeHistory.get(nextIndex - 1)[0];
|
||||||
|
long prevMediaTimeMs = mediaTimeHistory.get(nextIndex - 1)[1];
|
||||||
|
long nextRealtimeMs = mediaTimeHistory.get(nextIndex)[0];
|
||||||
|
long nextMediaTimeMs = mediaTimeHistory.get(nextIndex)[1];
|
||||||
|
long realtimeDurationMs = nextRealtimeMs - prevRealtimeMs;
|
||||||
|
if (realtimeDurationMs == 0) {
|
||||||
|
return prevMediaTimeMs;
|
||||||
|
}
|
||||||
|
float fraction = (float) (realtimeMs - prevRealtimeMs) / realtimeDurationMs;
|
||||||
|
return prevMediaTimeMs + (long) ((nextMediaTimeMs - prevMediaTimeMs) * fraction);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the mean time spent joining the playback, in milliseconds, or {@link C#TIME_UNSET} if
|
* Returns the mean time spent joining the playback, in milliseconds, or {@link C#TIME_UNSET} if
|
||||||
* no valid join time is available. Only includes playbacks with valid join times as documented in
|
* no valid join time is available. Only includes playbacks with valid join times as documented in
|
||||||
|
|
@ -564,4 +824,147 @@ public final class PlaybackStats {
|
||||||
public float getMeanTimeBetweenRebuffers() {
|
public float getMeanTimeBetweenRebuffers() {
|
||||||
return 1f / getRebufferRate();
|
return 1f / getRebufferRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean initial video format height, in pixels, or {@link C#LENGTH_UNSET} if no video
|
||||||
|
* format data is available.
|
||||||
|
*/
|
||||||
|
public int getMeanInitialVideoFormatHeight() {
|
||||||
|
return initialVideoFormatHeightCount == 0
|
||||||
|
? C.LENGTH_UNSET
|
||||||
|
: totalInitialVideoFormatHeight / initialVideoFormatHeightCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean initial video format bitrate, in bits per second, or {@link C#LENGTH_UNSET} if
|
||||||
|
* no video format data is available.
|
||||||
|
*/
|
||||||
|
public int getMeanInitialVideoFormatBitrate() {
|
||||||
|
return initialVideoFormatBitrateCount == 0
|
||||||
|
? C.LENGTH_UNSET
|
||||||
|
: (int) (totalInitialVideoFormatBitrate / initialVideoFormatBitrateCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean initial audio format bitrate, in bits per second, or {@link C#LENGTH_UNSET} if
|
||||||
|
* no audio format data is available.
|
||||||
|
*/
|
||||||
|
public int getMeanInitialAudioFormatBitrate() {
|
||||||
|
return initialAudioFormatBitrateCount == 0
|
||||||
|
? C.LENGTH_UNSET
|
||||||
|
: (int) (totalInitialAudioFormatBitrate / initialAudioFormatBitrateCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean video format height, in pixels, or {@link C#LENGTH_UNSET} if no video format
|
||||||
|
* data is available. This is a weighted average taking the time the format was used for playback
|
||||||
|
* into account.
|
||||||
|
*/
|
||||||
|
public int getMeanVideoFormatHeight() {
|
||||||
|
return totalVideoFormatHeightTimeMs == 0
|
||||||
|
? C.LENGTH_UNSET
|
||||||
|
: (int) (totalVideoFormatHeightTimeProduct / totalVideoFormatHeightTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean video format bitrate, in bits per second, or {@link C#LENGTH_UNSET} if no
|
||||||
|
* video format data is available. This is a weighted average taking the time the format was used
|
||||||
|
* for playback into account.
|
||||||
|
*/
|
||||||
|
public int getMeanVideoFormatBitrate() {
|
||||||
|
return totalVideoFormatBitrateTimeMs == 0
|
||||||
|
? C.LENGTH_UNSET
|
||||||
|
: (int) (totalVideoFormatBitrateTimeProduct / totalVideoFormatBitrateTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean audio format bitrate, in bits per second, or {@link C#LENGTH_UNSET} if no
|
||||||
|
* audio format data is available. This is a weighted average taking the time the format was used
|
||||||
|
* for playback into account.
|
||||||
|
*/
|
||||||
|
public int getMeanAudioFormatBitrate() {
|
||||||
|
return totalAudioFormatTimeMs == 0
|
||||||
|
? C.LENGTH_UNSET
|
||||||
|
: (int) (totalAudioFormatBitrateTimeProduct / totalAudioFormatTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean network bandwidth based on transfer measurements, in bits per second, or
|
||||||
|
* {@link C#LENGTH_UNSET} if no transfer data is available.
|
||||||
|
*/
|
||||||
|
public int getMeanBandwidth() {
|
||||||
|
return totalBandwidthTimeMs == 0
|
||||||
|
? C.LENGTH_UNSET
|
||||||
|
: (int) (totalBandwidthBytes * 8000 / totalBandwidthTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean rate at which video frames are dropped, in dropped frames per play time
|
||||||
|
* second, or {@code 0.0} if no time was spent playing.
|
||||||
|
*/
|
||||||
|
public float getDroppedFramesRate() {
|
||||||
|
long playTimeMs = getTotalPlayTimeMs();
|
||||||
|
return playTimeMs == 0 ? 0f : 1000f * totalDroppedFrames / playTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean rate at which audio underruns occurred, in underruns per play time second, or
|
||||||
|
* {@code 0.0} if no time was spent playing.
|
||||||
|
*/
|
||||||
|
public float getAudioUnderrunRate() {
|
||||||
|
long playTimeMs = getTotalPlayTimeMs();
|
||||||
|
return playTimeMs == 0 ? 0f : 1000f * totalAudioUnderruns / playTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ratio of foreground playbacks which experienced fatal errors, or {@code 0.0} if no
|
||||||
|
* playback has been in foreground.
|
||||||
|
*/
|
||||||
|
public float getFatalErrorRatio() {
|
||||||
|
return foregroundPlaybackCount == 0
|
||||||
|
? 0f
|
||||||
|
: (float) fatalErrorPlaybackCount / foregroundPlaybackCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the rate of fatal errors, in errors per play time second, or {@code 0.0} if no time was
|
||||||
|
* spend playing. This is equivalent to 1.0 / {@link #getMeanTimeBetweenFatalErrors()}.
|
||||||
|
*/
|
||||||
|
public float getFatalErrorRate() {
|
||||||
|
long playTimeMs = getTotalPlayTimeMs();
|
||||||
|
return playTimeMs == 0 ? 0f : 1000f * fatalErrorCount / playTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean play time between fatal errors, in seconds. This is equivalent to 1.0 / {@link
|
||||||
|
* #getFatalErrorRate()}. Note that this may return {@link Float#POSITIVE_INFINITY}.
|
||||||
|
*/
|
||||||
|
public float getMeanTimeBetweenFatalErrors() {
|
||||||
|
return 1f / getFatalErrorRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean number of non-fatal errors per foreground playback, or {@code 0.0} if no
|
||||||
|
* playback has been in foreground.
|
||||||
|
*/
|
||||||
|
public float getMeanNonFatalErrorCount() {
|
||||||
|
return foregroundPlaybackCount == 0 ? 0f : (float) nonFatalErrorCount / foregroundPlaybackCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the rate of non-fatal errors, in errors per play time second, or {@code 0.0} if no time
|
||||||
|
* was spend playing. This is equivalent to 1.0 / {@link #getMeanTimeBetweenNonFatalErrors()}.
|
||||||
|
*/
|
||||||
|
public float getNonFatalErrorRate() {
|
||||||
|
long playTimeMs = getTotalPlayTimeMs();
|
||||||
|
return playTimeMs == 0 ? 0f : 1000f * nonFatalErrorCount / playTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mean play time between non-fatal errors, in seconds. This is equivalent to 1.0 /
|
||||||
|
* {@link #getNonFatalErrorRate()}. Note that this may return {@link Float#POSITIVE_INFINITY}.
|
||||||
|
*/
|
||||||
|
public float getMeanTimeBetweenNonFatalErrors() {
|
||||||
|
return 1f / getNonFatalErrorRate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import androidx.annotation.Nullable;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.Timeline.Period;
|
import com.google.android.exoplayer2.Timeline.Period;
|
||||||
|
|
@ -27,13 +29,20 @@ import com.google.android.exoplayer2.analytics.PlaybackStats.PlaybackState;
|
||||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.LoadEventInfo;
|
import com.google.android.exoplayer2.source.MediaSourceEventListener.LoadEventInfo;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.MediaLoadData;
|
import com.google.android.exoplayer2.source.MediaSourceEventListener.MediaLoadData;
|
||||||
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link AnalyticsListener} to gather {@link PlaybackStats} from the player.
|
* {@link AnalyticsListener} to gather {@link PlaybackStats} from the player.
|
||||||
|
|
@ -72,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 float playbackSpeed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates listener for playback stats.
|
* Creates listener for playback stats.
|
||||||
|
|
@ -89,6 +99,7 @@ public final class PlaybackStatsListener
|
||||||
finishedPlaybackStats = PlaybackStats.EMPTY;
|
finishedPlaybackStats = PlaybackStats.EMPTY;
|
||||||
playWhenReady = false;
|
playWhenReady = false;
|
||||||
playbackState = Player.STATE_IDLE;
|
playbackState = Player.STATE_IDLE;
|
||||||
|
playbackSpeed = 1f;
|
||||||
period = new Period();
|
period = new Period();
|
||||||
sessionManager.setListener(this);
|
sessionManager.setListener(this);
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +169,7 @@ public final class PlaybackStatsListener
|
||||||
PlaybackStatsTracker tracker = new PlaybackStatsTracker(keepHistory, eventTime);
|
PlaybackStatsTracker tracker = new PlaybackStatsTracker(keepHistory, eventTime);
|
||||||
tracker.onPlayerStateChanged(
|
tracker.onPlayerStateChanged(
|
||||||
eventTime, playWhenReady, playbackState, /* belongsToPlayback= */ true);
|
eventTime, playWhenReady, playbackState, /* belongsToPlayback= */ true);
|
||||||
|
tracker.onPlaybackSpeedChanged(eventTime, playbackSpeed);
|
||||||
playbackStatsTrackers.put(session, tracker);
|
playbackStatsTrackers.put(session, tracker);
|
||||||
sessionStartEventTimes.put(session, eventTime);
|
sessionStartEventTimes.put(session, eventTime);
|
||||||
}
|
}
|
||||||
|
|
@ -286,6 +298,27 @@ public final class PlaybackStatsListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackParametersChanged(
|
||||||
|
EventTime eventTime, PlaybackParameters playbackParameters) {
|
||||||
|
playbackSpeed = playbackParameters.speed;
|
||||||
|
sessionManager.updateSessions(eventTime);
|
||||||
|
for (PlaybackStatsTracker tracker : playbackStatsTrackers.values()) {
|
||||||
|
tracker.onPlaybackSpeedChanged(eventTime, playbackSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTracksChanged(
|
||||||
|
EventTime eventTime, TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||||
|
sessionManager.updateSessions(eventTime);
|
||||||
|
for (String session : playbackStatsTrackers.keySet()) {
|
||||||
|
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||||
|
playbackStatsTrackers.get(session).onTracksChanged(eventTime, trackSelections);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadStarted(
|
public void onLoadStarted(
|
||||||
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
|
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
|
||||||
|
|
@ -297,6 +330,88 @@ public final class PlaybackStatsListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDownstreamFormatChanged(EventTime eventTime, MediaLoadData mediaLoadData) {
|
||||||
|
sessionManager.updateSessions(eventTime);
|
||||||
|
for (String session : playbackStatsTrackers.keySet()) {
|
||||||
|
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||||
|
playbackStatsTrackers.get(session).onDownstreamFormatChanged(eventTime, mediaLoadData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoSizeChanged(
|
||||||
|
EventTime eventTime,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int unappliedRotationDegrees,
|
||||||
|
float pixelWidthHeightRatio) {
|
||||||
|
sessionManager.updateSessions(eventTime);
|
||||||
|
for (String session : playbackStatsTrackers.keySet()) {
|
||||||
|
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||||
|
playbackStatsTrackers.get(session).onVideoSizeChanged(eventTime, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBandwidthEstimate(
|
||||||
|
EventTime eventTime, int totalLoadTimeMs, long totalBytesLoaded, long bitrateEstimate) {
|
||||||
|
sessionManager.updateSessions(eventTime);
|
||||||
|
for (String session : playbackStatsTrackers.keySet()) {
|
||||||
|
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||||
|
playbackStatsTrackers.get(session).onBandwidthData(totalLoadTimeMs, totalBytesLoaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioUnderrun(
|
||||||
|
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||||
|
sessionManager.updateSessions(eventTime);
|
||||||
|
for (String session : playbackStatsTrackers.keySet()) {
|
||||||
|
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||||
|
playbackStatsTrackers.get(session).onAudioUnderrun();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {
|
||||||
|
sessionManager.updateSessions(eventTime);
|
||||||
|
for (String session : playbackStatsTrackers.keySet()) {
|
||||||
|
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||||
|
playbackStatsTrackers.get(session).onDroppedVideoFrames(droppedFrames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadError(
|
||||||
|
EventTime eventTime,
|
||||||
|
LoadEventInfo loadEventInfo,
|
||||||
|
MediaLoadData mediaLoadData,
|
||||||
|
IOException error,
|
||||||
|
boolean wasCanceled) {
|
||||||
|
sessionManager.updateSessions(eventTime);
|
||||||
|
for (String session : playbackStatsTrackers.keySet()) {
|
||||||
|
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||||
|
playbackStatsTrackers.get(session).onNonFatalError(eventTime, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrmSessionManagerError(EventTime eventTime, Exception error) {
|
||||||
|
sessionManager.updateSessions(eventTime);
|
||||||
|
for (String session : playbackStatsTrackers.keySet()) {
|
||||||
|
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||||
|
playbackStatsTrackers.get(session).onNonFatalError(eventTime, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Tracker for playback stats of a single playback. */
|
/** Tracker for playback stats of a single playback. */
|
||||||
private static final class PlaybackStatsTracker {
|
private static final class PlaybackStatsTracker {
|
||||||
|
|
||||||
|
|
@ -304,6 +419,11 @@ public final class PlaybackStatsListener
|
||||||
private final boolean keepHistory;
|
private final boolean keepHistory;
|
||||||
private final long[] playbackStateDurationsMs;
|
private final long[] playbackStateDurationsMs;
|
||||||
private final List<Pair<EventTime, @PlaybackState Integer>> playbackStateHistory;
|
private final List<Pair<EventTime, @PlaybackState Integer>> playbackStateHistory;
|
||||||
|
private final List<long[]> mediaTimeHistory;
|
||||||
|
private final List<Pair<EventTime, @NullableType Format>> videoFormatHistory;
|
||||||
|
private final List<Pair<EventTime, @NullableType Format>> audioFormatHistory;
|
||||||
|
private final List<Pair<EventTime, Exception>> fatalErrorHistory;
|
||||||
|
private final List<Pair<EventTime, Exception>> nonFatalErrorHistory;
|
||||||
private final boolean isAd;
|
private final boolean isAd;
|
||||||
|
|
||||||
private long firstReportedTimeMs;
|
private long firstReportedTimeMs;
|
||||||
|
|
@ -315,6 +435,21 @@ public final class PlaybackStatsListener
|
||||||
private int seekCount;
|
private int seekCount;
|
||||||
private int rebufferCount;
|
private int rebufferCount;
|
||||||
private long maxRebufferTimeMs;
|
private long maxRebufferTimeMs;
|
||||||
|
private int initialVideoFormatHeight;
|
||||||
|
private long initialVideoFormatBitrate;
|
||||||
|
private long initialAudioFormatBitrate;
|
||||||
|
private long videoFormatHeightTimeMs;
|
||||||
|
private long videoFormatHeightTimeProduct;
|
||||||
|
private long videoFormatBitrateTimeMs;
|
||||||
|
private long videoFormatBitrateTimeProduct;
|
||||||
|
private long audioFormatTimeMs;
|
||||||
|
private long audioFormatBitrateTimeProduct;
|
||||||
|
private long bandwidthTimeMs;
|
||||||
|
private long bandwidthBytes;
|
||||||
|
private long droppedFrames;
|
||||||
|
private long audioUnderruns;
|
||||||
|
private int fatalErrorCount;
|
||||||
|
private int nonFatalErrorCount;
|
||||||
|
|
||||||
// Current player state tracking.
|
// Current player state tracking.
|
||||||
@PlaybackState private int currentPlaybackState;
|
@PlaybackState private int currentPlaybackState;
|
||||||
|
|
@ -327,6 +462,11 @@ public final class PlaybackStatsListener
|
||||||
private boolean hasFatalError;
|
private boolean hasFatalError;
|
||||||
private boolean startedLoading;
|
private boolean startedLoading;
|
||||||
private long lastRebufferStartTimeMs;
|
private long lastRebufferStartTimeMs;
|
||||||
|
@Nullable private Format currentVideoFormat;
|
||||||
|
@Nullable private Format currentAudioFormat;
|
||||||
|
private long lastVideoFormatStartTimeMs;
|
||||||
|
private long lastAudioFormatStartTimeMs;
|
||||||
|
private float currentPlaybackSpeed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a tracker for playback stats.
|
* Creates a tracker for playback stats.
|
||||||
|
|
@ -338,12 +478,21 @@ public final class PlaybackStatsListener
|
||||||
this.keepHistory = keepHistory;
|
this.keepHistory = keepHistory;
|
||||||
playbackStateDurationsMs = new long[PlaybackStats.PLAYBACK_STATE_COUNT];
|
playbackStateDurationsMs = new long[PlaybackStats.PLAYBACK_STATE_COUNT];
|
||||||
playbackStateHistory = keepHistory ? new ArrayList<>() : Collections.emptyList();
|
playbackStateHistory = keepHistory ? new ArrayList<>() : Collections.emptyList();
|
||||||
|
mediaTimeHistory = keepHistory ? new ArrayList<>() : Collections.emptyList();
|
||||||
|
videoFormatHistory = keepHistory ? new ArrayList<>() : Collections.emptyList();
|
||||||
|
audioFormatHistory = keepHistory ? new ArrayList<>() : Collections.emptyList();
|
||||||
|
fatalErrorHistory = keepHistory ? new ArrayList<>() : Collections.emptyList();
|
||||||
|
nonFatalErrorHistory = keepHistory ? new ArrayList<>() : Collections.emptyList();
|
||||||
currentPlaybackState = PlaybackStats.PLAYBACK_STATE_NOT_STARTED;
|
currentPlaybackState = PlaybackStats.PLAYBACK_STATE_NOT_STARTED;
|
||||||
currentPlaybackStateStartTimeMs = startTime.realtimeMs;
|
currentPlaybackStateStartTimeMs = startTime.realtimeMs;
|
||||||
playerPlaybackState = Player.STATE_IDLE;
|
playerPlaybackState = Player.STATE_IDLE;
|
||||||
firstReportedTimeMs = C.TIME_UNSET;
|
firstReportedTimeMs = C.TIME_UNSET;
|
||||||
maxRebufferTimeMs = C.TIME_UNSET;
|
maxRebufferTimeMs = C.TIME_UNSET;
|
||||||
isAd = startTime.mediaPeriodId != null && startTime.mediaPeriodId.isAd();
|
isAd = startTime.mediaPeriodId != null && startTime.mediaPeriodId.isAd();
|
||||||
|
initialAudioFormatBitrate = C.LENGTH_UNSET;
|
||||||
|
initialVideoFormatBitrate = C.LENGTH_UNSET;
|
||||||
|
initialVideoFormatHeight = C.LENGTH_UNSET;
|
||||||
|
currentPlaybackSpeed = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -407,6 +556,10 @@ public final class PlaybackStatsListener
|
||||||
* @param eventTime The {@link EventTime}.
|
* @param eventTime The {@link EventTime}.
|
||||||
*/
|
*/
|
||||||
public void onFatalError(EventTime eventTime, Exception error) {
|
public void onFatalError(EventTime eventTime, Exception error) {
|
||||||
|
fatalErrorCount++;
|
||||||
|
if (keepHistory) {
|
||||||
|
fatalErrorHistory.add(Pair.create(eventTime, error));
|
||||||
|
}
|
||||||
hasFatalError = true;
|
hasFatalError = true;
|
||||||
isSuspended = false;
|
isSuspended = false;
|
||||||
isSeeking = false;
|
isSeeking = false;
|
||||||
|
|
@ -446,6 +599,115 @@ public final class PlaybackStatsListener
|
||||||
maybeUpdatePlaybackState(eventTime, belongsToPlayback);
|
maybeUpdatePlaybackState(eventTime, belongsToPlayback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the tracker that the track selection for the current playback changed.
|
||||||
|
*
|
||||||
|
* @param eventTime The {@link EventTime}.
|
||||||
|
* @param trackSelections The new {@link TrackSelectionArray}.
|
||||||
|
*/
|
||||||
|
public void onTracksChanged(EventTime eventTime, TrackSelectionArray trackSelections) {
|
||||||
|
boolean videoEnabled = false;
|
||||||
|
boolean audioEnabled = false;
|
||||||
|
for (TrackSelection trackSelection : trackSelections.getAll()) {
|
||||||
|
if (trackSelection != null && trackSelection.length() > 0) {
|
||||||
|
int trackType = MimeTypes.getTrackType(trackSelection.getFormat(0).sampleMimeType);
|
||||||
|
if (trackType == C.TRACK_TYPE_VIDEO) {
|
||||||
|
videoEnabled = true;
|
||||||
|
} else if (trackType == C.TRACK_TYPE_AUDIO) {
|
||||||
|
audioEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!videoEnabled) {
|
||||||
|
maybeUpdateVideoFormat(eventTime, /* newFormat= */ null);
|
||||||
|
}
|
||||||
|
if (!audioEnabled) {
|
||||||
|
maybeUpdateAudioFormat(eventTime, /* newFormat= */ null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the tracker that a format being read by the renderers for the current playback
|
||||||
|
* changed.
|
||||||
|
*
|
||||||
|
* @param eventTime The {@link EventTime}.
|
||||||
|
* @param mediaLoadData The {@link MediaLoadData} describing the format change.
|
||||||
|
*/
|
||||||
|
public void onDownstreamFormatChanged(EventTime eventTime, MediaLoadData mediaLoadData) {
|
||||||
|
if (mediaLoadData.trackType == C.TRACK_TYPE_VIDEO
|
||||||
|
|| mediaLoadData.trackType == C.TRACK_TYPE_DEFAULT) {
|
||||||
|
maybeUpdateVideoFormat(eventTime, mediaLoadData.trackFormat);
|
||||||
|
} else if (mediaLoadData.trackType == C.TRACK_TYPE_AUDIO) {
|
||||||
|
maybeUpdateAudioFormat(eventTime, mediaLoadData.trackFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the tracker that the video size for the current playback changed.
|
||||||
|
*
|
||||||
|
* @param eventTime The {@link EventTime}.
|
||||||
|
* @param width The video width in pixels.
|
||||||
|
* @param height The video height in pixels.
|
||||||
|
*/
|
||||||
|
public void onVideoSizeChanged(EventTime eventTime, int width, int height) {
|
||||||
|
if (currentVideoFormat != null && currentVideoFormat.height == Format.NO_VALUE) {
|
||||||
|
Format formatWithHeight = currentVideoFormat.copyWithVideoSize(width, height);
|
||||||
|
maybeUpdateVideoFormat(eventTime, formatWithHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the tracker of a playback speed change, including all playback speed changes while
|
||||||
|
* the playback is not in the foreground.
|
||||||
|
*
|
||||||
|
* @param eventTime The {@link EventTime}.
|
||||||
|
* @param playbackSpeed The new playback speed.
|
||||||
|
*/
|
||||||
|
public void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {
|
||||||
|
maybeUpdateMediaTimeHistory(eventTime.realtimeMs, eventTime.eventPlaybackPositionMs);
|
||||||
|
maybeRecordVideoFormatTime(eventTime.realtimeMs);
|
||||||
|
maybeRecordAudioFormatTime(eventTime.realtimeMs);
|
||||||
|
currentPlaybackSpeed = playbackSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Notifies the builder of an audio underrun for the current playback. */
|
||||||
|
public void onAudioUnderrun() {
|
||||||
|
audioUnderruns++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the tracker of dropped video frames for the current playback.
|
||||||
|
*
|
||||||
|
* @param droppedFrames The number of dropped video frames.
|
||||||
|
*/
|
||||||
|
public void onDroppedVideoFrames(int droppedFrames) {
|
||||||
|
this.droppedFrames += droppedFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the tracker of bandwidth measurement data for the current playback.
|
||||||
|
*
|
||||||
|
* @param timeMs The time for which bandwidth measurement data is available, in milliseconds.
|
||||||
|
* @param bytes The bytes transferred during {@code timeMs}.
|
||||||
|
*/
|
||||||
|
public void onBandwidthData(long timeMs, long bytes) {
|
||||||
|
bandwidthTimeMs += timeMs;
|
||||||
|
bandwidthBytes += bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the tracker of a non-fatal error in the current playback.
|
||||||
|
*
|
||||||
|
* @param eventTime The {@link EventTime}.
|
||||||
|
* @param error The error.
|
||||||
|
*/
|
||||||
|
public void onNonFatalError(EventTime eventTime, Exception error) {
|
||||||
|
nonFatalErrorCount++;
|
||||||
|
if (keepHistory) {
|
||||||
|
nonFatalErrorHistory.add(Pair.create(eventTime, error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the playback stats.
|
* Builds the playback stats.
|
||||||
*
|
*
|
||||||
|
|
@ -453,6 +715,7 @@ public final class PlaybackStatsListener
|
||||||
*/
|
*/
|
||||||
public PlaybackStats build(boolean isFinal) {
|
public PlaybackStats build(boolean isFinal) {
|
||||||
long[] playbackStateDurationsMs = this.playbackStateDurationsMs;
|
long[] playbackStateDurationsMs = this.playbackStateDurationsMs;
|
||||||
|
List<long[]> mediaTimeHistory = this.mediaTimeHistory;
|
||||||
if (!isFinal) {
|
if (!isFinal) {
|
||||||
long buildTimeMs = SystemClock.elapsedRealtime();
|
long buildTimeMs = SystemClock.elapsedRealtime();
|
||||||
playbackStateDurationsMs =
|
playbackStateDurationsMs =
|
||||||
|
|
@ -460,6 +723,12 @@ public final class PlaybackStatsListener
|
||||||
long lastStateDurationMs = Math.max(0, buildTimeMs - currentPlaybackStateStartTimeMs);
|
long lastStateDurationMs = Math.max(0, buildTimeMs - currentPlaybackStateStartTimeMs);
|
||||||
playbackStateDurationsMs[currentPlaybackState] += lastStateDurationMs;
|
playbackStateDurationsMs[currentPlaybackState] += lastStateDurationMs;
|
||||||
maybeUpdateMaxRebufferTimeMs(buildTimeMs);
|
maybeUpdateMaxRebufferTimeMs(buildTimeMs);
|
||||||
|
maybeRecordVideoFormatTime(buildTimeMs);
|
||||||
|
maybeRecordAudioFormatTime(buildTimeMs);
|
||||||
|
mediaTimeHistory = new ArrayList<>(this.mediaTimeHistory);
|
||||||
|
if (keepHistory && currentPlaybackState == PlaybackStats.PLAYBACK_STATE_PLAYING) {
|
||||||
|
mediaTimeHistory.add(guessMediaTimeBasedOnElapsedRealtime(buildTimeMs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
boolean isJoinTimeInvalid = this.isJoinTimeInvalid || !hasBeenReady;
|
boolean isJoinTimeInvalid = this.isJoinTimeInvalid || !hasBeenReady;
|
||||||
long validJoinTimeMs =
|
long validJoinTimeMs =
|
||||||
|
|
@ -472,6 +741,7 @@ public final class PlaybackStatsListener
|
||||||
/* playbackCount= */ 1,
|
/* playbackCount= */ 1,
|
||||||
playbackStateDurationsMs,
|
playbackStateDurationsMs,
|
||||||
isFinal ? playbackStateHistory : new ArrayList<>(playbackStateHistory),
|
isFinal ? playbackStateHistory : new ArrayList<>(playbackStateHistory),
|
||||||
|
mediaTimeHistory,
|
||||||
firstReportedTimeMs,
|
firstReportedTimeMs,
|
||||||
/* foregroundPlaybackCount= */ isForeground ? 1 : 0,
|
/* foregroundPlaybackCount= */ isForeground ? 1 : 0,
|
||||||
/* abandonedBeforeReadyCount= */ hasBeenReady ? 0 : 1,
|
/* abandonedBeforeReadyCount= */ hasBeenReady ? 0 : 1,
|
||||||
|
|
@ -484,7 +754,30 @@ public final class PlaybackStatsListener
|
||||||
seekCount,
|
seekCount,
|
||||||
rebufferCount,
|
rebufferCount,
|
||||||
maxRebufferTimeMs,
|
maxRebufferTimeMs,
|
||||||
/* adPlaybackCount= */ isAd ? 1 : 0);
|
/* adPlaybackCount= */ isAd ? 1 : 0,
|
||||||
|
isFinal ? videoFormatHistory : new ArrayList<>(videoFormatHistory),
|
||||||
|
isFinal ? audioFormatHistory : new ArrayList<>(audioFormatHistory),
|
||||||
|
videoFormatHeightTimeMs,
|
||||||
|
videoFormatHeightTimeProduct,
|
||||||
|
videoFormatBitrateTimeMs,
|
||||||
|
videoFormatBitrateTimeProduct,
|
||||||
|
audioFormatTimeMs,
|
||||||
|
audioFormatBitrateTimeProduct,
|
||||||
|
/* initialVideoFormatHeightCount= */ initialVideoFormatHeight == C.LENGTH_UNSET ? 0 : 1,
|
||||||
|
/* initialVideoFormatBitrateCount= */ initialVideoFormatBitrate == C.LENGTH_UNSET ? 0 : 1,
|
||||||
|
initialVideoFormatHeight,
|
||||||
|
initialVideoFormatBitrate,
|
||||||
|
/* initialAudioFormatBitrateCount= */ initialAudioFormatBitrate == C.LENGTH_UNSET ? 0 : 1,
|
||||||
|
initialAudioFormatBitrate,
|
||||||
|
bandwidthTimeMs,
|
||||||
|
bandwidthBytes,
|
||||||
|
droppedFrames,
|
||||||
|
audioUnderruns,
|
||||||
|
/* fatalErrorPlaybackCount= */ fatalErrorCount > 0 ? 1 : 0,
|
||||||
|
fatalErrorCount,
|
||||||
|
nonFatalErrorCount,
|
||||||
|
fatalErrorHistory,
|
||||||
|
nonFatalErrorHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeUpdatePlaybackState(EventTime eventTime, boolean belongsToPlayback) {
|
private void maybeUpdatePlaybackState(EventTime eventTime, boolean belongsToPlayback) {
|
||||||
|
|
@ -517,7 +810,12 @@ public final class PlaybackStatsListener
|
||||||
pauseBufferCount++;
|
pauseBufferCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maybeUpdateMediaTimeHistory(
|
||||||
|
eventTime.realtimeMs,
|
||||||
|
/* mediaTimeMs= */ belongsToPlayback ? eventTime.eventPlaybackPositionMs : C.TIME_UNSET);
|
||||||
maybeUpdateMaxRebufferTimeMs(eventTime.realtimeMs);
|
maybeUpdateMaxRebufferTimeMs(eventTime.realtimeMs);
|
||||||
|
maybeRecordVideoFormatTime(eventTime.realtimeMs);
|
||||||
|
maybeRecordAudioFormatTime(eventTime.realtimeMs);
|
||||||
|
|
||||||
currentPlaybackState = newPlaybackState;
|
currentPlaybackState = newPlaybackState;
|
||||||
currentPlaybackStateStartTimeMs = eventTime.realtimeMs;
|
currentPlaybackStateStartTimeMs = eventTime.realtimeMs;
|
||||||
|
|
@ -581,6 +879,96 @@ public final class PlaybackStatsListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void maybeUpdateMediaTimeHistory(long realtimeMs, long mediaTimeMs) {
|
||||||
|
if (currentPlaybackState != PlaybackStats.PLAYBACK_STATE_PLAYING) {
|
||||||
|
if (mediaTimeMs == C.TIME_UNSET) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mediaTimeHistory.isEmpty()) {
|
||||||
|
long previousMediaTimeMs = mediaTimeHistory.get(mediaTimeHistory.size() - 1)[1];
|
||||||
|
if (previousMediaTimeMs != mediaTimeMs) {
|
||||||
|
mediaTimeHistory.add(new long[] {realtimeMs, previousMediaTimeMs});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mediaTimeHistory.add(
|
||||||
|
mediaTimeMs == C.TIME_UNSET
|
||||||
|
? guessMediaTimeBasedOnElapsedRealtime(realtimeMs)
|
||||||
|
: new long[] {realtimeMs, mediaTimeMs});
|
||||||
|
}
|
||||||
|
|
||||||
|
private long[] guessMediaTimeBasedOnElapsedRealtime(long realtimeMs) {
|
||||||
|
long[] previousKnownMediaTimeHistory = mediaTimeHistory.get(mediaTimeHistory.size() - 1);
|
||||||
|
long previousRealtimeMs = previousKnownMediaTimeHistory[0];
|
||||||
|
long previousMediaTimeMs = previousKnownMediaTimeHistory[1];
|
||||||
|
long elapsedMediaTimeEstimateMs =
|
||||||
|
(long) ((realtimeMs - previousRealtimeMs) * currentPlaybackSpeed);
|
||||||
|
long mediaTimeEstimateMs = previousMediaTimeMs + elapsedMediaTimeEstimateMs;
|
||||||
|
return new long[] {realtimeMs, mediaTimeEstimateMs};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeUpdateVideoFormat(EventTime eventTime, @Nullable Format newFormat) {
|
||||||
|
if (Util.areEqual(currentVideoFormat, newFormat)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
maybeRecordVideoFormatTime(eventTime.realtimeMs);
|
||||||
|
if (newFormat != null) {
|
||||||
|
if (initialVideoFormatHeight == C.LENGTH_UNSET && newFormat.height != Format.NO_VALUE) {
|
||||||
|
initialVideoFormatHeight = newFormat.height;
|
||||||
|
}
|
||||||
|
if (initialVideoFormatBitrate == C.LENGTH_UNSET && newFormat.bitrate != Format.NO_VALUE) {
|
||||||
|
initialVideoFormatBitrate = newFormat.bitrate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentVideoFormat = newFormat;
|
||||||
|
if (keepHistory) {
|
||||||
|
videoFormatHistory.add(Pair.create(eventTime, currentVideoFormat));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeUpdateAudioFormat(EventTime eventTime, @Nullable Format newFormat) {
|
||||||
|
if (Util.areEqual(currentAudioFormat, newFormat)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
maybeRecordAudioFormatTime(eventTime.realtimeMs);
|
||||||
|
if (newFormat != null
|
||||||
|
&& initialAudioFormatBitrate == C.LENGTH_UNSET
|
||||||
|
&& newFormat.bitrate != Format.NO_VALUE) {
|
||||||
|
initialAudioFormatBitrate = newFormat.bitrate;
|
||||||
|
}
|
||||||
|
currentAudioFormat = newFormat;
|
||||||
|
if (keepHistory) {
|
||||||
|
audioFormatHistory.add(Pair.create(eventTime, currentAudioFormat));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeRecordVideoFormatTime(long nowMs) {
|
||||||
|
if (currentPlaybackState == PlaybackStats.PLAYBACK_STATE_PLAYING
|
||||||
|
&& currentVideoFormat != null) {
|
||||||
|
long mediaDurationMs = (long) ((nowMs - lastVideoFormatStartTimeMs) * currentPlaybackSpeed);
|
||||||
|
if (currentVideoFormat.height != Format.NO_VALUE) {
|
||||||
|
videoFormatHeightTimeMs += mediaDurationMs;
|
||||||
|
videoFormatHeightTimeProduct += mediaDurationMs * currentVideoFormat.height;
|
||||||
|
}
|
||||||
|
if (currentVideoFormat.bitrate != Format.NO_VALUE) {
|
||||||
|
videoFormatBitrateTimeMs += mediaDurationMs;
|
||||||
|
videoFormatBitrateTimeProduct += mediaDurationMs * currentVideoFormat.bitrate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastVideoFormatStartTimeMs = nowMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeRecordAudioFormatTime(long nowMs) {
|
||||||
|
if (currentPlaybackState == PlaybackStats.PLAYBACK_STATE_PLAYING
|
||||||
|
&& currentAudioFormat != null
|
||||||
|
&& currentAudioFormat.bitrate != Format.NO_VALUE) {
|
||||||
|
long mediaDurationMs = (long) ((nowMs - lastAudioFormatStartTimeMs) * currentPlaybackSpeed);
|
||||||
|
audioFormatTimeMs += mediaDurationMs;
|
||||||
|
audioFormatBitrateTimeProduct += mediaDurationMs * currentAudioFormat.bitrate;
|
||||||
|
}
|
||||||
|
lastAudioFormatStartTimeMs = nowMs;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue