From bb787d662da6bdc8db42bda3c5cecee538dc55d1 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 15 Jul 2020 18:10:37 +0100 Subject: [PATCH] Ensure onAdPlaybackStarted is only called after the session is created We currently try to call onAdPlaybackStarted even if the ad session is not created yet and if not, we never call the callback afterwards. Make sure to update and create the current session before trying to send onAdPlaybackStarted. As a result, we can merge updateSessions into the existing handleTimelineChanged and handleDiscontinuity calls as they always need to be called together. PiperOrigin-RevId: 321383860 --- .../analytics/AnalyticsListener.java | 37 ++ .../DefaultPlaybackSessionManager.java | 48 ++- .../analytics/PlaybackSessionManager.java | 22 +- .../analytics/PlaybackStatsListener.java | 9 +- .../DefaultPlaybackSessionManagerTest.java | 348 ++++++++++-------- 5 files changed, 297 insertions(+), 167 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java index 1125e60690..7115ebaf5b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.common.base.Objects; import java.io.IOException; /** @@ -155,6 +156,42 @@ public interface AnalyticsListener { this.currentPlaybackPositionMs = currentPlaybackPositionMs; this.totalBufferedDurationMs = totalBufferedDurationMs; } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EventTime eventTime = (EventTime) o; + return realtimeMs == eventTime.realtimeMs + && windowIndex == eventTime.windowIndex + && eventPlaybackPositionMs == eventTime.eventPlaybackPositionMs + && currentWindowIndex == eventTime.currentWindowIndex + && currentPlaybackPositionMs == eventTime.currentPlaybackPositionMs + && totalBufferedDurationMs == eventTime.totalBufferedDurationMs + && Objects.equal(timeline, eventTime.timeline) + && Objects.equal(mediaPeriodId, eventTime.mediaPeriodId) + && Objects.equal(currentTimeline, eventTime.currentTimeline) + && Objects.equal(currentMediaPeriodId, eventTime.currentMediaPeriodId); + } + + @Override + public int hashCode() { + return Objects.hashCode( + realtimeMs, + timeline, + windowIndex, + mediaPeriodId, + eventPlaybackPositionMs, + currentTimeline, + currentWindowIndex, + currentMediaPeriodId, + currentPlaybackPositionMs, + totalBufferedDurationMs); + } } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java index 04536bb6c1..b1ef52839b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java @@ -15,6 +15,9 @@ */ package com.google.android.exoplayer2.analytics; +import static com.google.android.exoplayer2.C.usToMs; +import static java.lang.Math.max; + import android.util.Base64; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; @@ -120,6 +123,38 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag if (currentSessionId == null) { currentSessionId = eventSession.sessionId; } + if (eventTime.mediaPeriodId != null && eventTime.mediaPeriodId.isAd()) { + // Ensure that the content session for an ad session is created first. + MediaPeriodId contentMediaPeriodId = + new MediaPeriodId( + eventTime.mediaPeriodId.periodUid, + eventTime.mediaPeriodId.windowSequenceNumber, + eventTime.mediaPeriodId.adGroupIndex); + SessionDescriptor contentSession = + getOrAddSession(eventTime.windowIndex, contentMediaPeriodId); + if (!contentSession.isCreated) { + contentSession.isCreated = true; + eventTime.timeline.getPeriodByUid(eventTime.mediaPeriodId.periodUid, period); + long adGroupPositionMs = + usToMs(period.getAdGroupTimeUs(eventTime.mediaPeriodId.adGroupIndex)) + + period.getPositionInWindowMs(); + // getAdGroupTimeUs may return 0 for prerolls despite period offset. + adGroupPositionMs = max(0, adGroupPositionMs); + EventTime eventTimeForContent = + new EventTime( + eventTime.realtimeMs, + eventTime.timeline, + eventTime.windowIndex, + contentMediaPeriodId, + /* eventPlaybackPositionMs= */ adGroupPositionMs, + eventTime.currentTimeline, + eventTime.currentWindowIndex, + eventTime.currentMediaPeriodId, + eventTime.currentPlaybackPositionMs, + eventTime.totalBufferedDurationMs); + listener.onSessionCreated(eventTimeForContent, contentSession.sessionId); + } + } if (!eventSession.isCreated) { eventSession.isCreated = true; listener.onSessionCreated(eventTime, eventSession.sessionId); @@ -131,7 +166,7 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag } @Override - public synchronized void handleTimelineUpdate(EventTime eventTime) { + public synchronized void updateSessionsWithTimelineChange(EventTime eventTime) { Assertions.checkNotNull(listener); Timeline previousTimeline = currentTimeline; currentTimeline = eventTime.timeline; @@ -149,11 +184,11 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag } } } - handlePositionDiscontinuity(eventTime, Player.DISCONTINUITY_REASON_INTERNAL); + updateSessionsWithDiscontinuity(eventTime, Player.DISCONTINUITY_REASON_INTERNAL); } @Override - public synchronized void handlePositionDiscontinuity( + public synchronized void updateSessionsWithDiscontinuity( EventTime eventTime, @DiscontinuityReason int reason) { Assertions.checkNotNull(listener); boolean hasAutomaticTransition = @@ -179,6 +214,7 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag SessionDescriptor currentSessionDescriptor = getOrAddSession(eventTime.windowIndex, eventTime.mediaPeriodId); currentSessionId = currentSessionDescriptor.sessionId; + updateSessions(eventTime); if (eventTime.mediaPeriodId != null && eventTime.mediaPeriodId.isAd() && (previousSessionDescriptor == null @@ -195,10 +231,8 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag eventTime.mediaPeriodId.periodUid, eventTime.mediaPeriodId.windowSequenceNumber); SessionDescriptor contentSession = getOrAddSession(eventTime.windowIndex, contentMediaPeriodId); - if (contentSession.isCreated && currentSessionDescriptor.isCreated) { - listener.onAdPlaybackStarted( - eventTime, contentSession.sessionId, currentSessionDescriptor.sessionId); - } + listener.onAdPlaybackStarted( + eventTime, contentSession.sessionId, currentSessionDescriptor.sessionId); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackSessionManager.java index 7045779125..1038f3b6e1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackSessionManager.java @@ -99,24 +99,34 @@ public interface PlaybackSessionManager { /** * Updates or creates sessions based on a player {@link EventTime}. * + *

Call {@link #updateSessionsWithTimelineChange(EventTime)} or {@link + * #updateSessionsWithDiscontinuity(EventTime, int)} if the event is a {@link Timeline} change or + * a position discontinuity respectively. + * * @param eventTime The {@link EventTime}. */ void updateSessions(EventTime eventTime); /** - * Updates the session associations to a new timeline. + * Updates or creates sessions based on a {@link Timeline} change at {@link EventTime}. * - * @param eventTime The event time with the timeline change. + *

Should be called instead of {@link #updateSessions(EventTime)} if a {@link Timeline} change + * occurred. + * + * @param eventTime The {@link EventTime} with the timeline change. */ - void handleTimelineUpdate(EventTime eventTime); + void updateSessionsWithTimelineChange(EventTime eventTime); /** - * Handles a position discontinuity. + * Updates or creates sessions based on a position discontinuity at {@link EventTime}. * - * @param eventTime The event time of the position discontinuity. + *

Should be called instead of {@link #updateSessions(EventTime)} if a position discontinuity + * occurred. + * + * @param eventTime The {@link EventTime} of the position discontinuity. * @param reason The {@link DiscontinuityReason}. */ - void handlePositionDiscontinuity(EventTime eventTime, @DiscontinuityReason int reason); + void updateSessionsWithDiscontinuity(EventTime eventTime, @DiscontinuityReason int reason); /** * Finishes all existing sessions and calls their respective {@link diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java index 662cb0b8a1..d6f8d17d31 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/PlaybackStatsListener.java @@ -284,8 +284,7 @@ public final class PlaybackStatsListener @Override public void onTimelineChanged(EventTime eventTime, @Player.TimelineChangeReason int reason) { - sessionManager.handleTimelineUpdate(eventTime); - maybeAddSession(eventTime); + sessionManager.updateSessionsWithTimelineChange(eventTime); for (String session : playbackStatsTrackers.keySet()) { if (sessionManager.belongsToSession(eventTime, session)) { playbackStatsTrackers.get(session).onPositionDiscontinuity(eventTime, /* isSeek= */ false); @@ -295,8 +294,10 @@ public final class PlaybackStatsListener @Override public void onPositionDiscontinuity(EventTime eventTime, @Player.DiscontinuityReason int reason) { - sessionManager.handlePositionDiscontinuity(eventTime, reason); - maybeAddSession(eventTime); + boolean isCompletelyIdle = eventTime.timeline.isEmpty() && playbackState == Player.STATE_IDLE; + if (!isCompletelyIdle) { + sessionManager.updateSessionsWithDiscontinuity(eventTime, reason); + } if (reason == Player.DISCONTINUITY_REASON_SEEK) { onSeekStartedCalled = false; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java index a5a021c80a..08b784dee3 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManagerTest.java @@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -39,6 +40,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -160,26 +162,44 @@ public final class DefaultPlaybackSessionManagerTest { @Test public void updateSessions_ofSameWindow_withoutMediaPeriodId_afterAd_doesNotCreateNewSession() { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - MediaPeriodId mediaPeriodId = + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ new Object(), + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 10_000_000, + FakeTimeline.createAdPlaybackState( + /* adsPerGroup= */ 1, /* adGroupTimesUs... */ 0))); + MediaPeriodId adMediaPeriodId = new MediaPeriodId( timeline.getUidOfPeriod(/* periodIndex= */ 0), /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, /* windowSequenceNumber= */ 0); - EventTime eventTime1 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId); - EventTime eventTime2 = + MediaPeriodId contentMediaPeriodIdDuringAd = + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), + /* windowSequenceNumber= */ 0, + /* nextAdGroupIndex= */ 0); + EventTime adEventTime = createEventTime(timeline, /* windowIndex= */ 0, adMediaPeriodId); + EventTime contentEventTimeDuringAd = + createEventTime( + timeline, /* windowIndex= */ 0, contentMediaPeriodIdDuringAd, adMediaPeriodId); + EventTime contentEventTimeWithoutMediaPeriodId = createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null); - sessionManager.updateSessions(eventTime1); - sessionManager.updateSessions(eventTime2); + sessionManager.updateSessions(adEventTime); + sessionManager.updateSessions(contentEventTimeWithoutMediaPeriodId); - ArgumentCaptor sessionId = ArgumentCaptor.forClass(String.class); - verify(mockListener).onSessionCreated(eq(eventTime1), sessionId.capture()); - verify(mockListener).onSessionActive(eventTime1, sessionId.getValue()); + verify(mockListener).onSessionCreated(eq(contentEventTimeDuringAd), anyString()); + ArgumentCaptor adSessionId = ArgumentCaptor.forClass(String.class); + verify(mockListener).onSessionCreated(eq(adEventTime), adSessionId.capture()); + verify(mockListener).onSessionActive(adEventTime, adSessionId.getValue()); verifyNoMoreInteractions(mockListener); - assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId)) - .isEqualTo(sessionId.getValue()); + assertThat(sessionManager.getSessionForMediaPeriodId(timeline, adMediaPeriodId)) + .isEqualTo(adSessionId.getValue()); } @Test @@ -350,18 +370,6 @@ public final class DefaultPlaybackSessionManagerTest { verifyNoMoreInteractions(mockListener); } - @Test - public void getSessionForMediaPeriodId_returnsValue_butDoesNotCreateSession() { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - MediaPeriodId mediaPeriodId = - new MediaPeriodId( - timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0); - String session = sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId); - - assertThat(session).isNotEmpty(); - verifyNoMoreInteractions(mockListener); - } - @Test public void updateSessions_afterSessionForMediaPeriodId_withSameMediaPeriodId_returnsSameValue() { Timeline timeline = new FakeTimeline(/* windowCount= */ 1); @@ -399,6 +407,81 @@ public final class DefaultPlaybackSessionManagerTest { assertThat(sessionId.getValue()).isEqualTo(expectedSessionId); } + @Test + public void + updateSessions_withNewAd_afterDiscontinuitiesFromContentToAdAndBack_doesNotActivateNewAd() { + Timeline adTimeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs =*/ 10 * C.MICROS_PER_SECOND, + new AdPlaybackState( + /* adGroupTimesUs=... */ 2 * C.MICROS_PER_SECOND, 5 * C.MICROS_PER_SECOND) + .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) + .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1))); + EventTime adEventTime1 = + createEventTime( + adTimeline, + /* windowIndex= */ 0, + new MediaPeriodId( + adTimeline.getUidOfPeriod(/* periodIndex= */ 0), + /* adGroupIndex= */ 0, + /* adIndexInAdGroup= */ 0, + /* windowSequenceNumber= */ 0)); + EventTime adEventTime2 = + createEventTime( + adTimeline, + /* windowIndex= */ 0, + new MediaPeriodId( + adTimeline.getUidOfPeriod(/* periodIndex= */ 0), + /* adGroupIndex= */ 1, + /* adIndexInAdGroup= */ 0, + /* windowSequenceNumber= */ 0)); + EventTime contentEventTime1 = + createEventTime( + adTimeline, + /* windowIndex= */ 0, + new MediaPeriodId( + adTimeline.getUidOfPeriod(/* periodIndex= */ 0), + /* windowSequenceNumber= */ 0, + /* nextAdGroupIndex= */ 0)); + EventTime contentEventTime2 = + createEventTime( + adTimeline, + /* windowIndex= */ 0, + new MediaPeriodId( + adTimeline.getUidOfPeriod(/* periodIndex= */ 0), + /* windowSequenceNumber= */ 0, + /* nextAdGroupIndex= */ 1)); + sessionManager.updateSessionsWithTimelineChange(contentEventTime1); + sessionManager.updateSessions(adEventTime1); + sessionManager.updateSessionsWithDiscontinuity( + adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION); + sessionManager.updateSessionsWithDiscontinuity( + contentEventTime2, Player.DISCONTINUITY_REASON_AD_INSERTION); + String adSessionId2 = + sessionManager.getSessionForMediaPeriodId(adTimeline, adEventTime2.mediaPeriodId); + + sessionManager.updateSessions(adEventTime2); + + verify(mockListener, never()).onSessionActive(any(), eq(adSessionId2)); + } + + @Test + public void getSessionForMediaPeriodId_returnsValue_butDoesNotCreateSession() { + Timeline timeline = new FakeTimeline(/* windowCount= */ 1); + MediaPeriodId mediaPeriodId = + new MediaPeriodId( + timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0); + String session = sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId); + + assertThat(session).isNotEmpty(); + verifyNoMoreInteractions(mockListener); + } + @Test public void belongsToSession_withSameWindowIndex_returnsTrue() { EventTime eventTime = @@ -465,28 +548,38 @@ public final class DefaultPlaybackSessionManagerTest { @Test public void belongsToSession_withAd_returnsFalse() { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - MediaPeriodId mediaPeriodId1 = + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ new Object(), + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 10_000_000, + FakeTimeline.createAdPlaybackState( + /* adsPerGroup= */ 1, /* adGroupTimesUs... */ 0))); + MediaPeriodId contentMediaPeriodId = new MediaPeriodId( timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0); - MediaPeriodId mediaPeriodId2 = + MediaPeriodId adMediaPeriodId = new MediaPeriodId( timeline.getUidOfPeriod(/* periodIndex= */ 0), /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, /* windowSequenceNumber= */ 1); - EventTime eventTime1 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId1); - EventTime eventTime2 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId2); - sessionManager.updateSessions(eventTime1); - sessionManager.updateSessions(eventTime2); + EventTime contentEventTime = + createEventTime(timeline, /* windowIndex= */ 0, contentMediaPeriodId); + EventTime adEventTime = createEventTime(timeline, /* windowIndex= */ 0, adMediaPeriodId); + sessionManager.updateSessions(contentEventTime); + sessionManager.updateSessions(adEventTime); ArgumentCaptor sessionId1 = ArgumentCaptor.forClass(String.class); ArgumentCaptor sessionId2 = ArgumentCaptor.forClass(String.class); - verify(mockListener).onSessionCreated(eq(eventTime1), sessionId1.capture()); - verify(mockListener).onSessionCreated(eq(eventTime2), sessionId2.capture()); - assertThat(sessionManager.belongsToSession(eventTime2, sessionId1.getValue())).isFalse(); - assertThat(sessionManager.belongsToSession(eventTime1, sessionId2.getValue())).isFalse(); - assertThat(sessionManager.belongsToSession(eventTime2, sessionId2.getValue())).isTrue(); + verify(mockListener).onSessionCreated(eq(contentEventTime), sessionId1.capture()); + verify(mockListener).onSessionCreated(eq(adEventTime), sessionId2.capture()); + assertThat(sessionManager.belongsToSession(adEventTime, sessionId1.getValue())).isFalse(); + assertThat(sessionManager.belongsToSession(contentEventTime, sessionId2.getValue())).isFalse(); + assertThat(sessionManager.belongsToSession(adEventTime, sessionId2.getValue())).isTrue(); } @Test @@ -501,8 +594,7 @@ public final class DefaultPlaybackSessionManagerTest { EventTime newTimelineEventTime = createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null); - sessionManager.handleTimelineUpdate(newTimelineEventTime); - sessionManager.updateSessions(newTimelineEventTime); + sessionManager.updateSessionsWithTimelineChange(newTimelineEventTime); ArgumentCaptor sessionId1 = ArgumentCaptor.forClass(String.class); ArgumentCaptor sessionId2 = ArgumentCaptor.forClass(String.class); @@ -545,8 +637,7 @@ public final class DefaultPlaybackSessionManagerTest { new MediaPeriodId( initialTimeline.getUidOfPeriod(/* periodIndex= */ 3), /* windowSequenceNumber= */ 2)); - sessionManager.handleTimelineUpdate(eventForInitialTimelineId100); - sessionManager.updateSessions(eventForInitialTimelineId100); + sessionManager.updateSessionsWithTimelineChange(eventForInitialTimelineId100); sessionManager.updateSessions(eventForInitialTimelineId200); sessionManager.updateSessions(eventForInitialTimelineId300); String sessionId100 = @@ -578,7 +669,7 @@ public final class DefaultPlaybackSessionManagerTest { timelineUpdate.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 2)); - sessionManager.handleTimelineUpdate(eventForTimelineUpdateId100); + sessionManager.updateSessionsWithTimelineChange(eventForTimelineUpdateId100); String updatedSessionId100 = sessionManager.getSessionForMediaPeriodId( timelineUpdate, eventForTimelineUpdateId100.mediaPeriodId); @@ -632,7 +723,7 @@ public final class DefaultPlaybackSessionManagerTest { sessionManager.updateSessions(contentEventTime); sessionManager.updateSessions(adEventTime); - sessionManager.handleTimelineUpdate(contentEventTime); + sessionManager.updateSessionsWithTimelineChange(contentEventTime); verify(mockListener, never()).onSessionFinished(any(), anyString(), anyBoolean()); } @@ -653,13 +744,11 @@ public final class DefaultPlaybackSessionManagerTest { /* windowIndex= */ 0, new MediaPeriodId( timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 0)); - sessionManager.handleTimelineUpdate(eventTime1); - sessionManager.updateSessions(eventTime1); + sessionManager.updateSessionsWithTimelineChange(eventTime1); sessionManager.updateSessions(eventTime2); - sessionManager.handlePositionDiscontinuity( + sessionManager.updateSessionsWithDiscontinuity( eventTime2, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); - sessionManager.updateSessions(eventTime2); verify(mockListener).onSessionCreated(eq(eventTime1), anyString()); verify(mockListener).onSessionActive(eq(eventTime1), anyString()); @@ -681,17 +770,15 @@ public final class DefaultPlaybackSessionManagerTest { /* windowIndex= */ 1, new MediaPeriodId( timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1)); - sessionManager.handleTimelineUpdate(eventTime1); - sessionManager.updateSessions(eventTime1); + sessionManager.updateSessionsWithTimelineChange(eventTime1); sessionManager.updateSessions(eventTime2); String sessionId1 = sessionManager.getSessionForMediaPeriodId(timeline, eventTime1.mediaPeriodId); String sessionId2 = sessionManager.getSessionForMediaPeriodId(timeline, eventTime2.mediaPeriodId); - sessionManager.handlePositionDiscontinuity( + sessionManager.updateSessionsWithDiscontinuity( eventTime2, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION); - sessionManager.updateSessions(eventTime2); verify(mockListener).onSessionCreated(eventTime1, sessionId1); verify(mockListener).onSessionActive(eventTime1, sessionId1); @@ -717,16 +804,14 @@ public final class DefaultPlaybackSessionManagerTest { /* windowIndex= */ 1, new MediaPeriodId( timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1)); - sessionManager.handleTimelineUpdate(eventTime1); - sessionManager.updateSessions(eventTime1); + sessionManager.updateSessionsWithTimelineChange(eventTime1); sessionManager.updateSessions(eventTime2); String sessionId1 = sessionManager.getSessionForMediaPeriodId(timeline, eventTime1.mediaPeriodId); String sessionId2 = sessionManager.getSessionForMediaPeriodId(timeline, eventTime2.mediaPeriodId); - sessionManager.handlePositionDiscontinuity(eventTime2, Player.DISCONTINUITY_REASON_SEEK); - sessionManager.updateSessions(eventTime2); + sessionManager.updateSessionsWithDiscontinuity(eventTime2, Player.DISCONTINUITY_REASON_SEEK); verify(mockListener).onSessionCreated(eventTime1, sessionId1); verify(mockListener).onSessionActive(eventTime1, sessionId1); @@ -748,12 +833,10 @@ public final class DefaultPlaybackSessionManagerTest { timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); EventTime eventTime2 = createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null); - sessionManager.handleTimelineUpdate(eventTime1); - sessionManager.updateSessions(eventTime1); + sessionManager.updateSessionsWithTimelineChange(eventTime1); sessionManager.updateSessions(eventTime2); - sessionManager.handlePositionDiscontinuity(eventTime2, Player.DISCONTINUITY_REASON_SEEK); - sessionManager.updateSessions(eventTime2); + sessionManager.updateSessionsWithDiscontinuity(eventTime2, Player.DISCONTINUITY_REASON_SEEK); verify(mockListener, never()).onSessionFinished(any(), anyString(), anyBoolean()); } @@ -785,7 +868,7 @@ public final class DefaultPlaybackSessionManagerTest { /* windowIndex= */ 3, new MediaPeriodId( timeline.getUidOfPeriod(/* periodIndex= */ 3), /* windowSequenceNumber= */ 3)); - sessionManager.handleTimelineUpdate(eventTime1); + sessionManager.updateSessionsWithTimelineChange(eventTime1); sessionManager.updateSessions(eventTime1); sessionManager.updateSessions(eventTime2); sessionManager.updateSessions(eventTime3); @@ -795,8 +878,7 @@ public final class DefaultPlaybackSessionManagerTest { String sessionId2 = sessionManager.getSessionForMediaPeriodId(timeline, eventTime2.mediaPeriodId); - sessionManager.handlePositionDiscontinuity(eventTime3, Player.DISCONTINUITY_REASON_SEEK); - sessionManager.updateSessions(eventTime3); + sessionManager.updateSessionsWithDiscontinuity(eventTime3, Player.DISCONTINUITY_REASON_SEEK); verify(mockListener).onSessionCreated(eventTime1, sessionId1); verify(mockListener).onSessionActive(eventTime1, sessionId1); @@ -842,7 +924,20 @@ public final class DefaultPlaybackSessionManagerTest { /* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, /* windowSequenceNumber= */ 0)); - EventTime contentEventTime = + EventTime contentEventTimeDuringPreroll = + createEventTime( + adTimeline, + /* windowIndex= */ 0, + /* eventMediaPeriodId= */ new MediaPeriodId( + adTimeline.getUidOfPeriod(/* periodIndex= */ 0), + /* windowSequenceNumber= */ 0, + /* nextAdGroupIndex= */ 0), + /* currentMediaPeriodId= */ new MediaPeriodId( + adTimeline.getUidOfPeriod(/* periodIndex= */ 0), + /* adGroupIndex= */ 0, + /* adIndexInAdGroup= */ 0, + /* windowSequenceNumber= */ 0)); + EventTime contentEventTimeBetweenAds = createEventTime( adTimeline, /* windowIndex= */ 0, @@ -850,25 +945,31 @@ public final class DefaultPlaybackSessionManagerTest { adTimeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0, /* nextAdGroupIndex= */ 1)); - sessionManager.handleTimelineUpdate(adEventTime1); - sessionManager.updateSessions(adEventTime1); + sessionManager.updateSessionsWithTimelineChange(adEventTime1); sessionManager.updateSessions(adEventTime2); String adSessionId1 = sessionManager.getSessionForMediaPeriodId(adTimeline, adEventTime1.mediaPeriodId); + String contentSessionId = + sessionManager.getSessionForMediaPeriodId( + adTimeline, contentEventTimeDuringPreroll.mediaPeriodId); - sessionManager.handlePositionDiscontinuity( - contentEventTime, Player.DISCONTINUITY_REASON_AD_INSERTION); - sessionManager.updateSessions(contentEventTime); + sessionManager.updateSessionsWithDiscontinuity( + contentEventTimeBetweenAds, Player.DISCONTINUITY_REASON_AD_INSERTION); - verify(mockListener).onSessionCreated(adEventTime1, adSessionId1); - verify(mockListener).onSessionActive(adEventTime1, adSessionId1); - verify(mockListener).onSessionCreated(eq(adEventTime2), anyString()); - verify(mockListener) + InOrder inOrder = inOrder(mockListener); + inOrder.verify(mockListener).onSessionCreated(contentEventTimeDuringPreroll, contentSessionId); + inOrder.verify(mockListener).onSessionCreated(adEventTime1, adSessionId1); + inOrder.verify(mockListener).onSessionActive(adEventTime1, adSessionId1); + inOrder.verify(mockListener).onAdPlaybackStarted(adEventTime1, contentSessionId, adSessionId1); + inOrder.verify(mockListener).onSessionCreated(eq(adEventTime2), anyString()); + inOrder + .verify(mockListener) .onSessionFinished( - contentEventTime, adSessionId1, /* automaticTransitionToNextPlayback= */ true); - verify(mockListener).onSessionCreated(eq(contentEventTime), anyString()); - verify(mockListener).onSessionActive(eq(contentEventTime), anyString()); - verifyNoMoreInteractions(mockListener); + contentEventTimeBetweenAds, + adSessionId1, + /* automaticTransitionToNextPlayback= */ true); + inOrder.verify(mockListener).onSessionActive(eq(contentEventTimeBetweenAds), anyString()); + inOrder.verifyNoMoreInteractions(); } @Test @@ -911,14 +1012,12 @@ public final class DefaultPlaybackSessionManagerTest { adTimeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0, /* nextAdGroupIndex= */ 0)); - sessionManager.handleTimelineUpdate(contentEventTime); - sessionManager.updateSessions(contentEventTime); + sessionManager.updateSessionsWithTimelineChange(contentEventTime); sessionManager.updateSessions(adEventTime1); sessionManager.updateSessions(adEventTime2); - sessionManager.handlePositionDiscontinuity( + sessionManager.updateSessionsWithDiscontinuity( adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION); - sessionManager.updateSessions(adEventTime1); verify(mockListener, never()).onSessionFinished(any(), anyString(), anyBoolean()); } @@ -962,8 +1061,7 @@ public final class DefaultPlaybackSessionManagerTest { adTimeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0, /* nextAdGroupIndex= */ 1)); - sessionManager.handleTimelineUpdate(contentEventTime); - sessionManager.updateSessions(contentEventTime); + sessionManager.updateSessionsWithTimelineChange(contentEventTime); sessionManager.updateSessions(adEventTime1); sessionManager.updateSessions(adEventTime2); String contentSessionId = @@ -973,11 +1071,9 @@ public final class DefaultPlaybackSessionManagerTest { String adSessionId2 = sessionManager.getSessionForMediaPeriodId(adTimeline, adEventTime2.mediaPeriodId); - sessionManager.handlePositionDiscontinuity( + sessionManager.updateSessionsWithDiscontinuity( adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION); - sessionManager.updateSessions(adEventTime1); - sessionManager.handlePositionDiscontinuity(adEventTime2, Player.DISCONTINUITY_REASON_SEEK); - sessionManager.updateSessions(adEventTime2); + sessionManager.updateSessionsWithDiscontinuity(adEventTime2, Player.DISCONTINUITY_REASON_SEEK); verify(mockListener).onSessionCreated(eq(contentEventTime), anyString()); verify(mockListener).onSessionActive(eq(contentEventTime), anyString()); @@ -993,72 +1089,6 @@ public final class DefaultPlaybackSessionManagerTest { verifyNoMoreInteractions(mockListener); } - @Test - public void - updateSessions_withNewAd_afterDiscontinuitiesFromContentToAdAndBack_doesNotActivateNewAd() { - Timeline adTimeline = - new FakeTimeline( - new TimelineWindowDefinition( - /* periodCount= */ 1, - /* id= */ 0, - /* isSeekable= */ true, - /* isDynamic= */ false, - /* durationUs =*/ 10 * C.MICROS_PER_SECOND, - new AdPlaybackState( - /* adGroupTimesUs=... */ 2 * C.MICROS_PER_SECOND, 5 * C.MICROS_PER_SECOND) - .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1) - .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1))); - EventTime adEventTime1 = - createEventTime( - adTimeline, - /* windowIndex= */ 0, - new MediaPeriodId( - adTimeline.getUidOfPeriod(/* periodIndex= */ 0), - /* adGroupIndex= */ 0, - /* adIndexInAdGroup= */ 0, - /* windowSequenceNumber= */ 0)); - EventTime adEventTime2 = - createEventTime( - adTimeline, - /* windowIndex= */ 0, - new MediaPeriodId( - adTimeline.getUidOfPeriod(/* periodIndex= */ 0), - /* adGroupIndex= */ 1, - /* adIndexInAdGroup= */ 0, - /* windowSequenceNumber= */ 0)); - EventTime contentEventTime1 = - createEventTime( - adTimeline, - /* windowIndex= */ 0, - new MediaPeriodId( - adTimeline.getUidOfPeriod(/* periodIndex= */ 0), - /* windowSequenceNumber= */ 0, - /* nextAdGroupIndex= */ 0)); - EventTime contentEventTime2 = - createEventTime( - adTimeline, - /* windowIndex= */ 0, - new MediaPeriodId( - adTimeline.getUidOfPeriod(/* periodIndex= */ 0), - /* windowSequenceNumber= */ 0, - /* nextAdGroupIndex= */ 1)); - sessionManager.handleTimelineUpdate(contentEventTime1); - sessionManager.updateSessions(contentEventTime1); - 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); - - sessionManager.updateSessions(adEventTime2); - - verify(mockListener, never()).onSessionActive(any(), eq(adSessionId2)); - } - @Test public void finishAllSessions_callsOnSessionFinishedForAllCreatedSessions() { Timeline timeline = new FakeTimeline(/* windowCount= */ 4); @@ -1098,4 +1128,22 @@ public final class DefaultPlaybackSessionManagerTest { /* currentPlaybackPositionMs= */ 0, /* totalBufferedDurationMs= */ 0); } + + private static EventTime createEventTime( + Timeline timeline, + int windowIndex, + @Nullable MediaPeriodId eventMediaPeriodId, + @Nullable MediaPeriodId currentMediaPeriodId) { + return new EventTime( + /* realtimeMs = */ 0, + timeline, + windowIndex, + eventMediaPeriodId, + /* eventPlaybackPositionMs= */ 0, + timeline, + windowIndex, + currentMediaPeriodId, + /* currentPlaybackPositionMs= */ 0, + /* totalBufferedDurationMs= */ 0); + } }