mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Make use of position discontinuity changes.
The extended onPositionDiscontinuity callback can be used to improve some listener classes: - Listening to onTimelineChanged to detect discontinuities is no longer needed. - Listening to onSeekStarted is no longer needed as the start position is part of the onPositionDiscontinuty callback. - The exact old position is also useful for media time history logging. As a side effect, removing onSeekStarted handling from PlaybackStatsListener fixes Issue: #8675 that was caused by the special EventTime handling for onSeekStarted. PiperOrigin-RevId: 365558959
This commit is contained in:
parent
47af9a6889
commit
394ab7bcfd
@ -33,6 +33,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default {@link PlaybackSessionManager} which instantiates a new session for each window in the
|
* Default {@link PlaybackSessionManager} which instantiates a new session for each window in the
|
||||||
@ -184,7 +185,7 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateSessionsWithDiscontinuity(eventTime, Player.DISCONTINUITY_REASON_INTERNAL);
|
updateCurrentSession(eventTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -208,6 +209,31 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updateCurrentSession(eventTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public synchronized String getActiveSessionId() {
|
||||||
|
return currentSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void finishAllSessions(EventTime eventTime) {
|
||||||
|
currentSessionId = null;
|
||||||
|
Iterator<SessionDescriptor> iterator = sessions.values().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
SessionDescriptor session = iterator.next();
|
||||||
|
iterator.remove();
|
||||||
|
if (session.isCreated && listener != null) {
|
||||||
|
listener.onSessionFinished(
|
||||||
|
eventTime, session.sessionId, /* automaticTransitionToNextPlayback= */ false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull("listener")
|
||||||
|
private void updateCurrentSession(EventTime eventTime) {
|
||||||
@Nullable SessionDescriptor previousSessionDescriptor = sessions.get(currentSessionId);
|
@Nullable SessionDescriptor previousSessionDescriptor = sessions.get(currentSessionId);
|
||||||
SessionDescriptor currentSessionDescriptor =
|
SessionDescriptor currentSessionDescriptor =
|
||||||
getOrAddSession(eventTime.windowIndex, eventTime.mediaPeriodId);
|
getOrAddSession(eventTime.windowIndex, eventTime.mediaPeriodId);
|
||||||
@ -234,20 +260,6 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finishAllSessions(EventTime eventTime) {
|
|
||||||
currentSessionId = null;
|
|
||||||
Iterator<SessionDescriptor> iterator = sessions.values().iterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
SessionDescriptor session = iterator.next();
|
|
||||||
iterator.remove();
|
|
||||||
if (session.isCreated && listener != null) {
|
|
||||||
listener.onSessionFinished(
|
|
||||||
eventTime, session.sessionId, /* automaticTransitionToNextPlayback= */ false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SessionDescriptor getOrAddSession(
|
private SessionDescriptor getOrAddSession(
|
||||||
int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
|
int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
|
||||||
// There should only be one matching session if mediaPeriodId is non-null. If mediaPeriodId is
|
// There should only be one matching session if mediaPeriodId is non-null. If mediaPeriodId is
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.analytics;
|
package com.google.android.exoplayer2.analytics;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime;
|
import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime;
|
||||||
@ -128,6 +129,13 @@ public interface PlaybackSessionManager {
|
|||||||
*/
|
*/
|
||||||
void updateSessionsWithDiscontinuity(EventTime eventTime, @DiscontinuityReason int reason);
|
void updateSessionsWithDiscontinuity(EventTime eventTime, @DiscontinuityReason int reason);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the session identifier of the session that is currently actively playing, or {@code
|
||||||
|
* null} if there no such session.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
String getActiveSessionId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finishes all existing sessions and calls their respective {@link
|
* Finishes all existing sessions and calls their respective {@link
|
||||||
* Listener#onSessionFinished(EventTime, String, boolean)} callback.
|
* Listener#onSessionFinished(EventTime, String, boolean)} callback.
|
||||||
|
@ -79,19 +79,18 @@ public final class PlaybackStatsListener
|
|||||||
private final Period period;
|
private final Period period;
|
||||||
|
|
||||||
private PlaybackStats finishedPlaybackStats;
|
private PlaybackStats finishedPlaybackStats;
|
||||||
@Nullable private String activeContentPlayback;
|
|
||||||
@Nullable private String activeAdPlayback;
|
|
||||||
|
|
||||||
@Nullable private EventTime onSeekStartedEventTime;
|
@Nullable private String discontinuityFromSession;
|
||||||
@Player.DiscontinuityReason int discontinuityReason;
|
private long discontinuityFromPositionMs;
|
||||||
int droppedFrames;
|
@Player.DiscontinuityReason private int discontinuityReason;
|
||||||
@Nullable Exception nonFatalException;
|
private int droppedFrames;
|
||||||
long bandwidthTimeMs;
|
@Nullable private Exception nonFatalException;
|
||||||
long bandwidthBytes;
|
private long bandwidthTimeMs;
|
||||||
@Nullable Format videoFormat;
|
private long bandwidthBytes;
|
||||||
@Nullable Format audioFormat;
|
@Nullable private Format videoFormat;
|
||||||
int videoHeight;
|
@Nullable private Format audioFormat;
|
||||||
int videoWidth;
|
private int videoHeight;
|
||||||
|
private int videoWidth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates listener for playback stats.
|
* Creates listener for playback stats.
|
||||||
@ -137,13 +136,10 @@ public final class PlaybackStatsListener
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public PlaybackStats getPlaybackStats() {
|
public PlaybackStats getPlaybackStats() {
|
||||||
|
@Nullable String activeSessionId = sessionManager.getActiveSessionId();
|
||||||
@Nullable
|
@Nullable
|
||||||
PlaybackStatsTracker activeStatsTracker =
|
PlaybackStatsTracker activeStatsTracker =
|
||||||
activeAdPlayback != null
|
activeSessionId == null ? null : playbackStatsTrackers.get(activeSessionId);
|
||||||
? playbackStatsTrackers.get(activeAdPlayback)
|
|
||||||
: activeContentPlayback != null
|
|
||||||
? playbackStatsTrackers.get(activeContentPlayback)
|
|
||||||
: null;
|
|
||||||
return activeStatsTracker == null ? null : activeStatsTracker.build(/* isFinal= */ false);
|
return activeStatsTracker == null ? null : activeStatsTracker.build(/* isFinal= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,11 +155,6 @@ public final class PlaybackStatsListener
|
|||||||
@Override
|
@Override
|
||||||
public void onSessionActive(EventTime eventTime, String session) {
|
public void onSessionActive(EventTime eventTime, String session) {
|
||||||
checkNotNull(playbackStatsTrackers.get(session)).onForeground();
|
checkNotNull(playbackStatsTrackers.get(session)).onForeground();
|
||||||
if (eventTime.mediaPeriodId != null && eventTime.mediaPeriodId.isAd()) {
|
|
||||||
activeAdPlayback = session;
|
|
||||||
} else {
|
|
||||||
activeContentPlayback = session;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -173,14 +164,11 @@ public final class PlaybackStatsListener
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSessionFinished(EventTime eventTime, String session, boolean automaticTransition) {
|
public void onSessionFinished(EventTime eventTime, String session, boolean automaticTransition) {
|
||||||
if (session.equals(activeAdPlayback)) {
|
|
||||||
activeAdPlayback = null;
|
|
||||||
} else if (session.equals(activeContentPlayback)) {
|
|
||||||
activeContentPlayback = null;
|
|
||||||
}
|
|
||||||
PlaybackStatsTracker tracker = checkNotNull(playbackStatsTrackers.remove(session));
|
PlaybackStatsTracker tracker = checkNotNull(playbackStatsTrackers.remove(session));
|
||||||
EventTime startEventTime = checkNotNull(sessionStartEventTimes.remove(session));
|
EventTime startEventTime = checkNotNull(sessionStartEventTimes.remove(session));
|
||||||
tracker.onFinished(eventTime, automaticTransition);
|
long discontinuityFromPositionMs =
|
||||||
|
session.equals(discontinuityFromSession) ? this.discontinuityFromPositionMs : C.TIME_UNSET;
|
||||||
|
tracker.onFinished(eventTime, automaticTransition, discontinuityFromPositionMs);
|
||||||
PlaybackStats playbackStats = tracker.build(/* isFinal= */ true);
|
PlaybackStats playbackStats = tracker.build(/* isFinal= */ true);
|
||||||
finishedPlaybackStats = PlaybackStats.merge(finishedPlaybackStats, playbackStats);
|
finishedPlaybackStats = PlaybackStats.merge(finishedPlaybackStats, playbackStats);
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
@ -191,13 +179,16 @@ public final class PlaybackStatsListener
|
|||||||
// AnalyticsListener implementation.
|
// AnalyticsListener implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPositionDiscontinuity(EventTime eventTime, @Player.DiscontinuityReason int reason) {
|
public void onPositionDiscontinuity(
|
||||||
discontinuityReason = reason;
|
EventTime eventTime,
|
||||||
|
Player.PositionInfo oldPositionInfo,
|
||||||
|
Player.PositionInfo newPositionInfo,
|
||||||
|
@Player.DiscontinuityReason int reason) {
|
||||||
|
if (discontinuityFromSession == null) {
|
||||||
|
discontinuityFromSession = sessionManager.getActiveSessionId();
|
||||||
|
discontinuityFromPositionMs = oldPositionInfo.positionMs;
|
||||||
}
|
}
|
||||||
|
discontinuityReason = reason;
|
||||||
@Override
|
|
||||||
public void onSeekStarted(EventTime eventTime) {
|
|
||||||
onSeekStartedEventTime = eventTime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -253,9 +244,7 @@ public final class PlaybackStatsListener
|
|||||||
for (String session : playbackStatsTrackers.keySet()) {
|
for (String session : playbackStatsTrackers.keySet()) {
|
||||||
Pair<EventTime, Boolean> eventTimeAndBelongsToPlayback = findBestEventTime(events, session);
|
Pair<EventTime, Boolean> eventTimeAndBelongsToPlayback = findBestEventTime(events, session);
|
||||||
PlaybackStatsTracker tracker = playbackStatsTrackers.get(session);
|
PlaybackStatsTracker tracker = playbackStatsTrackers.get(session);
|
||||||
boolean hasPositionDiscontinuity =
|
boolean hasDiscontinuityToPlayback = hasEvent(events, session, EVENT_POSITION_DISCONTINUITY);
|
||||||
hasEvent(events, session, EVENT_POSITION_DISCONTINUITY)
|
|
||||||
|| hasEvent(events, session, EVENT_TIMELINE_CHANGED);
|
|
||||||
boolean hasDroppedFrames = hasEvent(events, session, EVENT_DROPPED_VIDEO_FRAMES);
|
boolean hasDroppedFrames = hasEvent(events, session, EVENT_DROPPED_VIDEO_FRAMES);
|
||||||
boolean hasAudioUnderrun = hasEvent(events, session, EVENT_AUDIO_UNDERRUN);
|
boolean hasAudioUnderrun = hasEvent(events, session, EVENT_AUDIO_UNDERRUN);
|
||||||
boolean startedLoading = hasEvent(events, session, EVENT_LOAD_STARTED);
|
boolean startedLoading = hasEvent(events, session, EVENT_LOAD_STARTED);
|
||||||
@ -270,8 +259,8 @@ public final class PlaybackStatsListener
|
|||||||
player,
|
player,
|
||||||
/* eventTime= */ eventTimeAndBelongsToPlayback.first,
|
/* eventTime= */ eventTimeAndBelongsToPlayback.first,
|
||||||
/* belongsToPlayback= */ eventTimeAndBelongsToPlayback.second,
|
/* belongsToPlayback= */ eventTimeAndBelongsToPlayback.second,
|
||||||
/* seeked= */ onSeekStartedEventTime != null,
|
session.equals(discontinuityFromSession) ? discontinuityFromPositionMs : C.TIME_UNSET,
|
||||||
hasPositionDiscontinuity,
|
hasDiscontinuityToPlayback,
|
||||||
hasDroppedFrames ? droppedFrames : 0,
|
hasDroppedFrames ? droppedFrames : 0,
|
||||||
hasAudioUnderrun,
|
hasAudioUnderrun,
|
||||||
startedLoading,
|
startedLoading,
|
||||||
@ -284,9 +273,9 @@ public final class PlaybackStatsListener
|
|||||||
hasVideoSize ? videoHeight : Format.NO_VALUE,
|
hasVideoSize ? videoHeight : Format.NO_VALUE,
|
||||||
hasVideoSize ? videoWidth : Format.NO_VALUE);
|
hasVideoSize ? videoWidth : Format.NO_VALUE);
|
||||||
}
|
}
|
||||||
onSeekStartedEventTime = null;
|
|
||||||
videoFormat = null;
|
videoFormat = null;
|
||||||
audioFormat = null;
|
audioFormat = null;
|
||||||
|
discontinuityFromSession = null;
|
||||||
if (events.contains(AnalyticsListener.EVENT_PLAYER_RELEASED)) {
|
if (events.contains(AnalyticsListener.EVENT_PLAYER_RELEASED)) {
|
||||||
sessionManager.finishAllSessions(events.getEventTime(EVENT_PLAYER_RELEASED));
|
sessionManager.finishAllSessions(events.getEventTime(EVENT_PLAYER_RELEASED));
|
||||||
}
|
}
|
||||||
@ -311,11 +300,8 @@ public final class PlaybackStatsListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Pair<EventTime, Boolean> findBestEventTime(Events events, String session) {
|
private Pair<EventTime, Boolean> findBestEventTime(Events events, String session) {
|
||||||
// Check all event times of the events as well as the event time when a seek started.
|
@Nullable EventTime eventTime = null;
|
||||||
@Nullable EventTime eventTime = onSeekStartedEventTime;
|
boolean belongsToPlayback = false;
|
||||||
boolean belongsToPlayback =
|
|
||||||
onSeekStartedEventTime != null
|
|
||||||
&& sessionManager.belongsToSession(onSeekStartedEventTime, session);
|
|
||||||
for (int i = 0; i < events.size(); i++) {
|
for (int i = 0; i < events.size(); i++) {
|
||||||
@EventFlags int event = events.get(i);
|
@EventFlags int event = events.get(i);
|
||||||
EventTime newEventTime = events.getEventTime(event);
|
EventTime newEventTime = events.getEventTime(event);
|
||||||
@ -463,15 +449,18 @@ public final class PlaybackStatsListener
|
|||||||
* @param eventTime The {@link EventTime}. Does not belong to this playback.
|
* @param eventTime The {@link EventTime}. Does not belong to this playback.
|
||||||
* @param automaticTransition Whether the playback finished because of an automatic transition
|
* @param automaticTransition Whether the playback finished because of an automatic transition
|
||||||
* to the next playback item.
|
* to the next playback item.
|
||||||
|
* @param discontinuityFromPositionMs The position before the discontinuity from this playback,
|
||||||
|
* {@link C#TIME_UNSET} if no discontinuity started from this playback.
|
||||||
*/
|
*/
|
||||||
public void onFinished(EventTime eventTime, boolean automaticTransition) {
|
public void onFinished(
|
||||||
|
EventTime eventTime, boolean automaticTransition, long discontinuityFromPositionMs) {
|
||||||
// Simulate state change to ENDED to record natural ending of playback.
|
// Simulate state change to ENDED to record natural ending of playback.
|
||||||
@PlaybackState
|
@PlaybackState
|
||||||
int finalPlaybackState =
|
int finalPlaybackState =
|
||||||
currentPlaybackState == PlaybackStats.PLAYBACK_STATE_ENDED || automaticTransition
|
currentPlaybackState == PlaybackStats.PLAYBACK_STATE_ENDED || automaticTransition
|
||||||
? PlaybackStats.PLAYBACK_STATE_ENDED
|
? PlaybackStats.PLAYBACK_STATE_ENDED
|
||||||
: PlaybackStats.PLAYBACK_STATE_ABANDONED;
|
: PlaybackStats.PLAYBACK_STATE_ABANDONED;
|
||||||
maybeUpdateMediaTimeHistory(eventTime.realtimeMs, /* mediaTimeMs= */ C.TIME_UNSET);
|
maybeUpdateMediaTimeHistory(eventTime.realtimeMs, discontinuityFromPositionMs);
|
||||||
maybeRecordVideoFormatTime(eventTime.realtimeMs);
|
maybeRecordVideoFormatTime(eventTime.realtimeMs);
|
||||||
maybeRecordAudioFormatTime(eventTime.realtimeMs);
|
maybeRecordAudioFormatTime(eventTime.realtimeMs);
|
||||||
updatePlaybackState(finalPlaybackState, eventTime);
|
updatePlaybackState(finalPlaybackState, eventTime);
|
||||||
@ -483,8 +472,9 @@ public final class PlaybackStatsListener
|
|||||||
* @param player The {@link Player}.
|
* @param player The {@link Player}.
|
||||||
* @param eventTime The {@link EventTime} of the events.
|
* @param eventTime The {@link EventTime} of the events.
|
||||||
* @param belongsToPlayback Whether the {@code eventTime} belongs to this playback.
|
* @param belongsToPlayback Whether the {@code eventTime} belongs to this playback.
|
||||||
* @param seeked Whether a seek occurred.
|
* @param discontinuityFromPositionMs The position before the discontinuity from this playback,
|
||||||
* @param positionDiscontinuity Whether a position discontinuity occurred for this playback.
|
* or {@link C#TIME_UNSET} if no discontinuity started from this playback.
|
||||||
|
* @param hasDiscontinuity Whether a discontinuity to this playback occurred.
|
||||||
* @param droppedFrameCount The number of newly dropped frames for this playback.
|
* @param droppedFrameCount The number of newly dropped frames for this playback.
|
||||||
* @param hasAudioUnderun Whether a new audio underrun occurred for this playback.
|
* @param hasAudioUnderun Whether a new audio underrun occurred for this playback.
|
||||||
* @param startedLoading Whether this playback started loading.
|
* @param startedLoading Whether this playback started loading.
|
||||||
@ -501,8 +491,8 @@ public final class PlaybackStatsListener
|
|||||||
Player player,
|
Player player,
|
||||||
EventTime eventTime,
|
EventTime eventTime,
|
||||||
boolean belongsToPlayback,
|
boolean belongsToPlayback,
|
||||||
boolean seeked,
|
long discontinuityFromPositionMs,
|
||||||
boolean positionDiscontinuity,
|
boolean hasDiscontinuity,
|
||||||
int droppedFrameCount,
|
int droppedFrameCount,
|
||||||
boolean hasAudioUnderun,
|
boolean hasAudioUnderun,
|
||||||
boolean startedLoading,
|
boolean startedLoading,
|
||||||
@ -514,7 +504,8 @@ public final class PlaybackStatsListener
|
|||||||
@Nullable Format audioFormat,
|
@Nullable Format audioFormat,
|
||||||
int videoHeight,
|
int videoHeight,
|
||||||
int videoWidth) {
|
int videoWidth) {
|
||||||
if (seeked) {
|
if (discontinuityFromPositionMs != C.TIME_UNSET) {
|
||||||
|
maybeUpdateMediaTimeHistory(eventTime.realtimeMs, discontinuityFromPositionMs);
|
||||||
isSeeking = true;
|
isSeeking = true;
|
||||||
}
|
}
|
||||||
if (player.getPlaybackState() != Player.STATE_BUFFERING) {
|
if (player.getPlaybackState() != Player.STATE_BUFFERING) {
|
||||||
@ -523,7 +514,7 @@ public final class PlaybackStatsListener
|
|||||||
int playerPlaybackState = player.getPlaybackState();
|
int playerPlaybackState = player.getPlaybackState();
|
||||||
if (playerPlaybackState == Player.STATE_IDLE
|
if (playerPlaybackState == Player.STATE_IDLE
|
||||||
|| playerPlaybackState == Player.STATE_ENDED
|
|| playerPlaybackState == Player.STATE_ENDED
|
||||||
|| positionDiscontinuity) {
|
|| hasDiscontinuity) {
|
||||||
isInterruptedByAd = false;
|
isInterruptedByAd = false;
|
||||||
}
|
}
|
||||||
if (fatalError != null) {
|
if (fatalError != null) {
|
||||||
|
@ -187,11 +187,6 @@ public class EventLogger implements AnalyticsListener {
|
|||||||
logd(eventTime, "positionDiscontinuity", builder.toString());
|
logd(eventTime, "positionDiscontinuity", builder.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSeekStarted(EventTime eventTime) {
|
|
||||||
logd(eventTime, "seekStarted");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackParametersChanged(
|
public void onPlaybackParametersChanged(
|
||||||
EventTime eventTime, PlaybackParameters playbackParameters) {
|
EventTime eventTime, PlaybackParameters playbackParameters) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user