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); + } }