mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Merge pull request #7455 from google/dev-v2-r2.11.5-7193
Merge fix for #7193 into dev-v2-r2.11.5
This commit is contained in:
commit
9830c773c6
5 changed files with 247 additions and 67 deletions
|
|
@ -29,7 +29,6 @@ import java.util.HashMap;
|
|||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
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
|
||||
|
|
@ -48,8 +47,7 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag
|
|||
|
||||
private @MonotonicNonNull Listener listener;
|
||||
private Timeline currentTimeline;
|
||||
@Nullable private MediaPeriodId currentMediaPeriodId;
|
||||
@Nullable private String activeSessionId;
|
||||
@Nullable private String currentSessionId;
|
||||
|
||||
/** Creates session manager. */
|
||||
public DefaultPlaybackSessionManager() {
|
||||
|
|
@ -83,22 +81,34 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag
|
|||
|
||||
@Override
|
||||
public synchronized void updateSessions(EventTime eventTime) {
|
||||
boolean isObviouslyFinished =
|
||||
eventTime.mediaPeriodId != null
|
||||
&& currentMediaPeriodId != null
|
||||
&& eventTime.mediaPeriodId.windowSequenceNumber
|
||||
< currentMediaPeriodId.windowSequenceNumber;
|
||||
if (!isObviouslyFinished) {
|
||||
SessionDescriptor descriptor =
|
||||
getOrAddSession(eventTime.windowIndex, eventTime.mediaPeriodId);
|
||||
if (!descriptor.isCreated) {
|
||||
descriptor.isCreated = true;
|
||||
Assertions.checkNotNull(listener).onSessionCreated(eventTime, descriptor.sessionId);
|
||||
if (activeSessionId == null) {
|
||||
updateActiveSession(eventTime, descriptor);
|
||||
}
|
||||
Assertions.checkNotNull(listener);
|
||||
@Nullable SessionDescriptor currentSession = sessions.get(currentSessionId);
|
||||
if (eventTime.mediaPeriodId != null && currentSession != null) {
|
||||
// If we receive an event associated with a media period, then it needs to be either part of
|
||||
// the current window if it's the first created media period, or a window that will be played
|
||||
// in the future. Otherwise, we know that it belongs to a session that was already finished
|
||||
// and we can ignore the event.
|
||||
boolean isAlreadyFinished =
|
||||
currentSession.windowSequenceNumber == C.INDEX_UNSET
|
||||
? currentSession.windowIndex != eventTime.windowIndex
|
||||
: eventTime.mediaPeriodId.windowSequenceNumber < currentSession.windowSequenceNumber;
|
||||
if (isAlreadyFinished) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
SessionDescriptor eventSession =
|
||||
getOrAddSession(eventTime.windowIndex, eventTime.mediaPeriodId);
|
||||
if (currentSessionId == null) {
|
||||
currentSessionId = eventSession.sessionId;
|
||||
}
|
||||
if (!eventSession.isCreated) {
|
||||
eventSession.isCreated = true;
|
||||
listener.onSessionCreated(eventTime, eventSession.sessionId);
|
||||
}
|
||||
if (eventSession.sessionId.equals(currentSessionId) && !eventSession.isActive) {
|
||||
eventSession.isActive = true;
|
||||
listener.onSessionActive(eventTime, eventSession.sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -112,8 +122,8 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag
|
|||
if (!session.tryResolvingToNewTimeline(previousTimeline, currentTimeline)) {
|
||||
iterator.remove();
|
||||
if (session.isCreated) {
|
||||
if (session.sessionId.equals(activeSessionId)) {
|
||||
activeSessionId = null;
|
||||
if (session.sessionId.equals(currentSessionId)) {
|
||||
currentSessionId = null;
|
||||
}
|
||||
listener.onSessionFinished(
|
||||
eventTime, session.sessionId, /* automaticTransitionToNextPlayback= */ false);
|
||||
|
|
@ -136,36 +146,55 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag
|
|||
if (session.isFinishedAtEventTime(eventTime)) {
|
||||
iterator.remove();
|
||||
if (session.isCreated) {
|
||||
boolean isRemovingActiveSession = session.sessionId.equals(activeSessionId);
|
||||
boolean isAutomaticTransition = hasAutomaticTransition && isRemovingActiveSession;
|
||||
if (isRemovingActiveSession) {
|
||||
activeSessionId = null;
|
||||
boolean isRemovingCurrentSession = session.sessionId.equals(currentSessionId);
|
||||
boolean isAutomaticTransition =
|
||||
hasAutomaticTransition && isRemovingCurrentSession && session.isActive;
|
||||
if (isRemovingCurrentSession) {
|
||||
currentSessionId = null;
|
||||
}
|
||||
listener.onSessionFinished(eventTime, session.sessionId, isAutomaticTransition);
|
||||
}
|
||||
}
|
||||
}
|
||||
SessionDescriptor activeSessionDescriptor =
|
||||
@Nullable SessionDescriptor previousSessionDescriptor = sessions.get(currentSessionId);
|
||||
SessionDescriptor currentSessionDescriptor =
|
||||
getOrAddSession(eventTime.windowIndex, eventTime.mediaPeriodId);
|
||||
currentSessionId = currentSessionDescriptor.sessionId;
|
||||
if (eventTime.mediaPeriodId != null
|
||||
&& eventTime.mediaPeriodId.isAd()
|
||||
&& (currentMediaPeriodId == null
|
||||
|| currentMediaPeriodId.windowSequenceNumber
|
||||
&& (previousSessionDescriptor == null
|
||||
|| previousSessionDescriptor.windowSequenceNumber
|
||||
!= eventTime.mediaPeriodId.windowSequenceNumber
|
||||
|| currentMediaPeriodId.adGroupIndex != eventTime.mediaPeriodId.adGroupIndex
|
||||
|| currentMediaPeriodId.adIndexInAdGroup != eventTime.mediaPeriodId.adIndexInAdGroup)) {
|
||||
|| previousSessionDescriptor.adMediaPeriodId == null
|
||||
|| previousSessionDescriptor.adMediaPeriodId.adGroupIndex
|
||||
!= eventTime.mediaPeriodId.adGroupIndex
|
||||
|| previousSessionDescriptor.adMediaPeriodId.adIndexInAdGroup
|
||||
!= eventTime.mediaPeriodId.adIndexInAdGroup)) {
|
||||
// New ad playback started. Find corresponding content session and notify ad playback started.
|
||||
MediaPeriodId contentMediaPeriodId =
|
||||
new MediaPeriodId(
|
||||
eventTime.mediaPeriodId.periodUid, eventTime.mediaPeriodId.windowSequenceNumber);
|
||||
SessionDescriptor contentSession =
|
||||
getOrAddSession(eventTime.windowIndex, contentMediaPeriodId);
|
||||
if (contentSession.isCreated && activeSessionDescriptor.isCreated) {
|
||||
if (contentSession.isCreated && currentSessionDescriptor.isCreated) {
|
||||
listener.onAdPlaybackStarted(
|
||||
eventTime, contentSession.sessionId, activeSessionDescriptor.sessionId);
|
||||
eventTime, contentSession.sessionId, currentSessionDescriptor.sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
updateActiveSession(eventTime, activeSessionDescriptor);
|
||||
}
|
||||
|
||||
private SessionDescriptor getOrAddSession(
|
||||
|
|
@ -199,18 +228,6 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag
|
|||
return bestMatch;
|
||||
}
|
||||
|
||||
@RequiresNonNull("listener")
|
||||
private void updateActiveSession(EventTime eventTime, SessionDescriptor sessionDescriptor) {
|
||||
currentMediaPeriodId = eventTime.mediaPeriodId;
|
||||
if (sessionDescriptor.isCreated) {
|
||||
activeSessionId = sessionDescriptor.sessionId;
|
||||
if (!sessionDescriptor.isActive) {
|
||||
sessionDescriptor.isActive = true;
|
||||
listener.onSessionActive(eventTime, sessionDescriptor.sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateSessionId() {
|
||||
byte[] randomBytes = new byte[SESSION_ID_LENGTH];
|
||||
RANDOM.nextBytes(randomBytes);
|
||||
|
|
@ -284,8 +301,7 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag
|
|||
int eventWindowIndex, @Nullable MediaPeriodId eventMediaPeriodId) {
|
||||
if (windowSequenceNumber == C.INDEX_UNSET
|
||||
&& eventWindowIndex == windowIndex
|
||||
&& eventMediaPeriodId != null
|
||||
&& !eventMediaPeriodId.isAd()) {
|
||||
&& eventMediaPeriodId != null) {
|
||||
// Set window sequence number for this session as soon as we have one.
|
||||
windowSequenceNumber = eventMediaPeriodId.windowSequenceNumber;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,4 +117,12 @@ public interface PlaybackSessionManager {
|
|||
* @param reason The {@link DiscontinuityReason}.
|
||||
*/
|
||||
void handlePositionDiscontinuity(EventTime eventTime, @DiscontinuityReason int reason);
|
||||
|
||||
/**
|
||||
* Finishes all existing sessions and calls their respective {@link
|
||||
* Listener#onSessionFinished(EventTime, String, boolean)} callback.
|
||||
*
|
||||
* @param eventTime The event time at which sessions are finished.
|
||||
*/
|
||||
void finishAllSessions(EventTime eventTime);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,7 +148,6 @@ public final class PlaybackStatsListener
|
|||
// TODO: Add AnalyticsListener.onAttachedToPlayer and onDetachedFromPlayer to auto-release with
|
||||
// an actual EventTime. Should also simplify other cases where the listener needs to be released
|
||||
// separately from the player.
|
||||
HashMap<String, PlaybackStatsTracker> trackerCopy = new HashMap<>(playbackStatsTrackers);
|
||||
EventTime dummyEventTime =
|
||||
new EventTime(
|
||||
SystemClock.elapsedRealtime(),
|
||||
|
|
@ -158,9 +157,7 @@ public final class PlaybackStatsListener
|
|||
/* eventPlaybackPositionMs= */ 0,
|
||||
/* currentPlaybackPositionMs= */ 0,
|
||||
/* totalBufferedDurationMs= */ 0);
|
||||
for (String session : trackerCopy.keySet()) {
|
||||
onSessionFinished(dummyEventTime, session, /* automaticTransition= */ false);
|
||||
}
|
||||
sessionManager.finishAllSessions(dummyEventTime);
|
||||
}
|
||||
|
||||
// PlaybackSessionManager.Listener implementation.
|
||||
|
|
@ -243,7 +240,7 @@ public final class PlaybackStatsListener
|
|||
EventTime eventTime, boolean playWhenReady, @Player.State int playbackState) {
|
||||
this.playWhenReady = playWhenReady;
|
||||
this.playbackState = playbackState;
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
boolean belongsToPlayback = sessionManager.belongsToSession(eventTime, session);
|
||||
playbackStatsTrackers
|
||||
|
|
@ -256,7 +253,7 @@ public final class PlaybackStatsListener
|
|||
public void onPlaybackSuppressionReasonChanged(
|
||||
EventTime eventTime, int playbackSuppressionReason) {
|
||||
isSuppressed = playbackSuppressionReason != Player.PLAYBACK_SUPPRESSION_REASON_NONE;
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
boolean belongsToPlayback = sessionManager.belongsToSession(eventTime, session);
|
||||
playbackStatsTrackers
|
||||
|
|
@ -268,7 +265,7 @@ public final class PlaybackStatsListener
|
|||
@Override
|
||||
public void onTimelineChanged(EventTime eventTime, int reason) {
|
||||
sessionManager.handleTimelineUpdate(eventTime);
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onPositionDiscontinuity(eventTime);
|
||||
|
|
@ -279,7 +276,7 @@ public final class PlaybackStatsListener
|
|||
@Override
|
||||
public void onPositionDiscontinuity(EventTime eventTime, int reason) {
|
||||
sessionManager.handlePositionDiscontinuity(eventTime, reason);
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onPositionDiscontinuity(eventTime);
|
||||
|
|
@ -289,7 +286,7 @@ public final class PlaybackStatsListener
|
|||
|
||||
@Override
|
||||
public void onSeekStarted(EventTime eventTime) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onSeekStarted(eventTime);
|
||||
|
|
@ -299,7 +296,7 @@ public final class PlaybackStatsListener
|
|||
|
||||
@Override
|
||||
public void onSeekProcessed(EventTime eventTime) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onSeekProcessed(eventTime);
|
||||
|
|
@ -309,7 +306,7 @@ public final class PlaybackStatsListener
|
|||
|
||||
@Override
|
||||
public void onPlayerError(EventTime eventTime, ExoPlaybackException error) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onFatalError(eventTime, error);
|
||||
|
|
@ -321,7 +318,7 @@ public final class PlaybackStatsListener
|
|||
public void onPlaybackParametersChanged(
|
||||
EventTime eventTime, PlaybackParameters playbackParameters) {
|
||||
playbackSpeed = playbackParameters.speed;
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (PlaybackStatsTracker tracker : playbackStatsTrackers.values()) {
|
||||
tracker.onPlaybackSpeedChanged(eventTime, playbackSpeed);
|
||||
}
|
||||
|
|
@ -330,7 +327,7 @@ public final class PlaybackStatsListener
|
|||
@Override
|
||||
public void onTracksChanged(
|
||||
EventTime eventTime, TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onTracksChanged(eventTime, trackSelections);
|
||||
|
|
@ -341,7 +338,7 @@ public final class PlaybackStatsListener
|
|||
@Override
|
||||
public void onLoadStarted(
|
||||
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onLoadStarted(eventTime);
|
||||
|
|
@ -351,7 +348,7 @@ public final class PlaybackStatsListener
|
|||
|
||||
@Override
|
||||
public void onDownstreamFormatChanged(EventTime eventTime, MediaLoadData mediaLoadData) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onDownstreamFormatChanged(eventTime, mediaLoadData);
|
||||
|
|
@ -366,7 +363,7 @@ public final class PlaybackStatsListener
|
|||
int height,
|
||||
int unappliedRotationDegrees,
|
||||
float pixelWidthHeightRatio) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onVideoSizeChanged(eventTime, width, height);
|
||||
|
|
@ -377,7 +374,7 @@ public final class PlaybackStatsListener
|
|||
@Override
|
||||
public void onBandwidthEstimate(
|
||||
EventTime eventTime, int totalLoadTimeMs, long totalBytesLoaded, long bitrateEstimate) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onBandwidthData(totalLoadTimeMs, totalBytesLoaded);
|
||||
|
|
@ -388,7 +385,7 @@ public final class PlaybackStatsListener
|
|||
@Override
|
||||
public void onAudioUnderrun(
|
||||
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onAudioUnderrun();
|
||||
|
|
@ -398,7 +395,7 @@ public final class PlaybackStatsListener
|
|||
|
||||
@Override
|
||||
public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onDroppedVideoFrames(droppedFrames);
|
||||
|
|
@ -413,7 +410,7 @@ public final class PlaybackStatsListener
|
|||
MediaLoadData mediaLoadData,
|
||||
IOException error,
|
||||
boolean wasCanceled) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onNonFatalError(eventTime, error);
|
||||
|
|
@ -423,7 +420,7 @@ public final class PlaybackStatsListener
|
|||
|
||||
@Override
|
||||
public void onDrmSessionManagerError(EventTime eventTime, Exception error) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
maybeAddSession(eventTime);
|
||||
for (String session : playbackStatsTrackers.keySet()) {
|
||||
if (sessionManager.belongsToSession(eventTime, session)) {
|
||||
playbackStatsTrackers.get(session).onNonFatalError(eventTime, error);
|
||||
|
|
@ -431,6 +428,13 @@ public final class PlaybackStatsListener
|
|||
}
|
||||
}
|
||||
|
||||
private void maybeAddSession(EventTime eventTime) {
|
||||
boolean isCompletelyIdle = eventTime.timeline.isEmpty() && playbackState == Player.STATE_IDLE;
|
||||
if (!isCompletelyIdle) {
|
||||
sessionManager.updateSessions(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
/** Tracker for playback stats of a single playback. */
|
||||
private static final class PlaybackStatsTracker {
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
|
|||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
|
|
@ -501,6 +502,7 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
|
||||
|
||||
sessionManager.handleTimelineUpdate(newTimelineEventTime);
|
||||
sessionManager.updateSessions(newTimelineEventTime);
|
||||
|
||||
ArgumentCaptor<String> sessionId1 = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<String> sessionId2 = ArgumentCaptor.forClass(String.class);
|
||||
|
|
@ -657,6 +659,7 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
|
||||
sessionManager.handlePositionDiscontinuity(
|
||||
eventTime2, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION);
|
||||
sessionManager.updateSessions(eventTime2);
|
||||
|
||||
verify(mockListener).onSessionCreated(eq(eventTime1), anyString());
|
||||
verify(mockListener).onSessionActive(eq(eventTime1), anyString());
|
||||
|
|
@ -688,6 +691,7 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
|
||||
sessionManager.handlePositionDiscontinuity(
|
||||
eventTime2, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION);
|
||||
sessionManager.updateSessions(eventTime2);
|
||||
|
||||
verify(mockListener).onSessionCreated(eventTime1, sessionId1);
|
||||
verify(mockListener).onSessionActive(eventTime1, sessionId1);
|
||||
|
|
@ -722,6 +726,7 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
sessionManager.getSessionForMediaPeriodId(timeline, eventTime2.mediaPeriodId);
|
||||
|
||||
sessionManager.handlePositionDiscontinuity(eventTime2, Player.DISCONTINUITY_REASON_SEEK);
|
||||
sessionManager.updateSessions(eventTime2);
|
||||
|
||||
verify(mockListener).onSessionCreated(eventTime1, sessionId1);
|
||||
verify(mockListener).onSessionActive(eventTime1, sessionId1);
|
||||
|
|
@ -748,6 +753,7 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
sessionManager.updateSessions(eventTime2);
|
||||
|
||||
sessionManager.handlePositionDiscontinuity(eventTime2, Player.DISCONTINUITY_REASON_SEEK);
|
||||
sessionManager.updateSessions(eventTime2);
|
||||
|
||||
verify(mockListener, never()).onSessionFinished(any(), anyString(), anyBoolean());
|
||||
}
|
||||
|
|
@ -790,6 +796,7 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
sessionManager.getSessionForMediaPeriodId(timeline, eventTime2.mediaPeriodId);
|
||||
|
||||
sessionManager.handlePositionDiscontinuity(eventTime3, Player.DISCONTINUITY_REASON_SEEK);
|
||||
sessionManager.updateSessions(eventTime3);
|
||||
|
||||
verify(mockListener).onSessionCreated(eventTime1, sessionId1);
|
||||
verify(mockListener).onSessionActive(eventTime1, sessionId1);
|
||||
|
|
@ -851,6 +858,7 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
|
||||
sessionManager.handlePositionDiscontinuity(
|
||||
contentEventTime, Player.DISCONTINUITY_REASON_AD_INSERTION);
|
||||
sessionManager.updateSessions(contentEventTime);
|
||||
|
||||
verify(mockListener).onSessionCreated(adEventTime1, adSessionId1);
|
||||
verify(mockListener).onSessionActive(adEventTime1, adSessionId1);
|
||||
|
|
@ -858,6 +866,8 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
verify(mockListener)
|
||||
.onSessionFinished(
|
||||
contentEventTime, adSessionId1, /* automaticTransitionToNextPlayback= */ true);
|
||||
verify(mockListener).onSessionCreated(eq(contentEventTime), anyString());
|
||||
verify(mockListener).onSessionActive(eq(contentEventTime), anyString());
|
||||
verifyNoMoreInteractions(mockListener);
|
||||
}
|
||||
|
||||
|
|
@ -908,6 +918,7 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
|
||||
sessionManager.handlePositionDiscontinuity(
|
||||
adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION);
|
||||
sessionManager.updateSessions(adEventTime1);
|
||||
|
||||
verify(mockListener, never()).onSessionFinished(any(), anyString(), anyBoolean());
|
||||
}
|
||||
|
|
@ -964,7 +975,9 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
|
||||
sessionManager.handlePositionDiscontinuity(
|
||||
adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION);
|
||||
sessionManager.updateSessions(adEventTime1);
|
||||
sessionManager.handlePositionDiscontinuity(adEventTime2, Player.DISCONTINUITY_REASON_SEEK);
|
||||
sessionManager.updateSessions(adEventTime2);
|
||||
|
||||
verify(mockListener).onSessionCreated(eq(contentEventTime), anyString());
|
||||
verify(mockListener).onSessionActive(eq(contentEventTime), anyString());
|
||||
|
|
@ -1034,8 +1047,10 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
sessionManager.updateSessions(adEventTime1);
|
||||
sessionManager.handlePositionDiscontinuity(
|
||||
adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION);
|
||||
sessionManager.updateSessions(adEventTime1);
|
||||
sessionManager.handlePositionDiscontinuity(
|
||||
contentEventTime2, Player.DISCONTINUITY_REASON_AD_INSERTION);
|
||||
sessionManager.updateSessions(contentEventTime2);
|
||||
String adSessionId2 =
|
||||
sessionManager.getSessionForMediaPeriodId(adTimeline, adEventTime2.mediaPeriodId);
|
||||
|
||||
|
|
@ -1044,6 +1059,31 @@ public final class DefaultPlaybackSessionManagerTest {
|
|||
verify(mockListener, never()).onSessionActive(any(), eq(adSessionId2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finishAllSessions_callsOnSessionFinishedForAllCreatedSessions() {
|
||||
Timeline timeline = new FakeTimeline(/* windowCount= */ 4);
|
||||
EventTime eventTimeWindow0 =
|
||||
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
|
||||
EventTime eventTimeWindow2 =
|
||||
createEventTime(timeline, /* windowIndex= */ 2, /* mediaPeriodId= */ null);
|
||||
// Actually create sessions for window 0 and 2.
|
||||
sessionManager.updateSessions(eventTimeWindow0);
|
||||
sessionManager.updateSessions(eventTimeWindow2);
|
||||
// Query information about session for window 1, but don't create it.
|
||||
sessionManager.getSessionForMediaPeriodId(
|
||||
timeline,
|
||||
new MediaPeriodId(
|
||||
timeline.getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true).uid,
|
||||
/* windowSequenceNumber= */ 123));
|
||||
verify(mockListener, times(2)).onSessionCreated(any(), anyString());
|
||||
|
||||
EventTime finishEventTime =
|
||||
createEventTime(Timeline.EMPTY, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
|
||||
sessionManager.finishAllSessions(finishEventTime);
|
||||
|
||||
verify(mockListener, times(2)).onSessionFinished(eq(finishEventTime), anyString(), eq(false));
|
||||
}
|
||||
|
||||
private static EventTime createEventTime(
|
||||
Timeline timeline, int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
|
||||
return new EventTime(
|
||||
|
|
|
|||
|
|
@ -16,11 +16,20 @@
|
|||
package com.google.android.exoplayer2.analytics;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
|
|
@ -28,7 +37,7 @@ import org.junit.runner.RunWith;
|
|||
@RunWith(AndroidJUnit4.class)
|
||||
public final class PlaybackStatsListenerTest {
|
||||
|
||||
private static final AnalyticsListener.EventTime TEST_EVENT_TIME =
|
||||
private static final AnalyticsListener.EventTime EMPTY_TIMELINE_EVENT_TIME =
|
||||
new AnalyticsListener.EventTime(
|
||||
/* realtimeMs= */ 500,
|
||||
Timeline.EMPTY,
|
||||
|
|
@ -37,6 +46,41 @@ public final class PlaybackStatsListenerTest {
|
|||
/* eventPlaybackPositionMs= */ 0,
|
||||
/* currentPlaybackPositionMs= */ 0,
|
||||
/* totalBufferedDurationMs= */ 0);
|
||||
private static final Timeline TEST_TIMELINE = new FakeTimeline(/* windowCount= */ 1);
|
||||
private static final AnalyticsListener.EventTime TEST_EVENT_TIME =
|
||||
new AnalyticsListener.EventTime(
|
||||
/* realtimeMs= */ 500,
|
||||
TEST_TIMELINE,
|
||||
/* windowIndex= */ 0,
|
||||
new MediaSource.MediaPeriodId(
|
||||
TEST_TIMELINE.getPeriod(
|
||||
/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true)
|
||||
.uid,
|
||||
/* windowSequenceNumber= */ 42),
|
||||
/* eventPlaybackPositionMs= */ 123,
|
||||
/* currentPlaybackPositionMs= */ 123,
|
||||
/* totalBufferedDurationMs= */ 456);
|
||||
|
||||
@Test
|
||||
public void stateChangeEvent_toNonIdle_createsInitialPlaybackStats() {
|
||||
PlaybackStatsListener playbackStatsListener =
|
||||
new PlaybackStatsListener(/* keepHistory= */ true, /* callback= */ null);
|
||||
|
||||
playbackStatsListener.onPlayerStateChanged(
|
||||
EMPTY_TIMELINE_EVENT_TIME, /* playWhenReady= */ false, Player.STATE_BUFFERING);
|
||||
|
||||
assertThat(playbackStatsListener.getPlaybackStats()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timelineChangeEvent_toNonEmpty_createsInitialPlaybackStats() {
|
||||
PlaybackStatsListener playbackStatsListener =
|
||||
new PlaybackStatsListener(/* keepHistory= */ true, /* callback= */ null);
|
||||
|
||||
playbackStatsListener.onTimelineChanged(TEST_EVENT_TIME, Player.TIMELINE_CHANGE_REASON_DYNAMIC);
|
||||
|
||||
assertThat(playbackStatsListener.getPlaybackStats()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playback_withKeepHistory_updatesStats() {
|
||||
|
|
@ -71,4 +115,72 @@ public final class PlaybackStatsListenerTest {
|
|||
assertThat(playbackStats).isNotNull();
|
||||
assertThat(playbackStats.endedCount).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finishedSession_callsCallback() {
|
||||
PlaybackStatsListener.Callback callback = mock(PlaybackStatsListener.Callback.class);
|
||||
PlaybackStatsListener playbackStatsListener =
|
||||
new PlaybackStatsListener(/* keepHistory= */ true, callback);
|
||||
|
||||
// Create session with an event and finish it by simulating removal from playlist.
|
||||
playbackStatsListener.onPlayerStateChanged(
|
||||
TEST_EVENT_TIME, /* playWhenReady= */ false, Player.STATE_BUFFERING);
|
||||
verify(callback, never()).onPlaybackStatsReady(any(), any());
|
||||
playbackStatsListener.onTimelineChanged(
|
||||
EMPTY_TIMELINE_EVENT_TIME, Player.TIMELINE_CHANGE_REASON_DYNAMIC);
|
||||
|
||||
verify(callback).onPlaybackStatsReady(eq(TEST_EVENT_TIME), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finishAllSessions_callsAllPendingCallbacks() {
|
||||
AnalyticsListener.EventTime eventTimeWindow0 =
|
||||
new AnalyticsListener.EventTime(
|
||||
/* realtimeMs= */ 0,
|
||||
Timeline.EMPTY,
|
||||
/* windowIndex= */ 0,
|
||||
/* mediaPeriodId= */ null,
|
||||
/* eventPlaybackPositionMs= */ 0,
|
||||
/* currentPlaybackPositionMs= */ 0,
|
||||
/* totalBufferedDurationMs= */ 0);
|
||||
AnalyticsListener.EventTime eventTimeWindow1 =
|
||||
new AnalyticsListener.EventTime(
|
||||
/* realtimeMs= */ 0,
|
||||
Timeline.EMPTY,
|
||||
/* windowIndex= */ 1,
|
||||
/* mediaPeriodId= */ null,
|
||||
/* eventPlaybackPositionMs= */ 0,
|
||||
/* currentPlaybackPositionMs= */ 0,
|
||||
/* totalBufferedDurationMs= */ 0);
|
||||
PlaybackStatsListener.Callback callback = mock(PlaybackStatsListener.Callback.class);
|
||||
PlaybackStatsListener playbackStatsListener =
|
||||
new PlaybackStatsListener(/* keepHistory= */ true, callback);
|
||||
playbackStatsListener.onPlayerStateChanged(
|
||||
eventTimeWindow0, /* playWhenReady= */ false, Player.STATE_BUFFERING);
|
||||
playbackStatsListener.onPlayerStateChanged(
|
||||
eventTimeWindow1, /* playWhenReady= */ false, Player.STATE_BUFFERING);
|
||||
|
||||
playbackStatsListener.finishAllSessions();
|
||||
|
||||
verify(callback, times(2)).onPlaybackStatsReady(any(), any());
|
||||
verify(callback).onPlaybackStatsReady(eq(eventTimeWindow0), any());
|
||||
verify(callback).onPlaybackStatsReady(eq(eventTimeWindow1), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finishAllSessions_doesNotCallCallbackAgainWhenSessionWouldBeAutomaticallyFinished() {
|
||||
PlaybackStatsListener.Callback callback = mock(PlaybackStatsListener.Callback.class);
|
||||
PlaybackStatsListener playbackStatsListener =
|
||||
new PlaybackStatsListener(/* keepHistory= */ true, callback);
|
||||
playbackStatsListener.onPlayerStateChanged(
|
||||
TEST_EVENT_TIME, /* playWhenReady= */ false, Player.STATE_BUFFERING);
|
||||
SystemClock.setCurrentTimeMillis(TEST_EVENT_TIME.realtimeMs + 100);
|
||||
|
||||
playbackStatsListener.finishAllSessions();
|
||||
// Simulate removing the playback item to ensure the session would finish if it hadn't already.
|
||||
playbackStatsListener.onTimelineChanged(
|
||||
EMPTY_TIMELINE_EVENT_TIME, Player.TIMELINE_CHANGE_REASON_DYNAMIC);
|
||||
|
||||
verify(callback).onPlaybackStatsReady(any(), any());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue