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 2403f9bf77..b3393b67fd 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 @@ -177,7 +177,8 @@ public final class DefaultPlaybackSessionManager implements PlaybackSessionManag Iterator iterator = sessions.values().iterator(); while (iterator.hasNext()) { SessionDescriptor session = iterator.next(); - if (!session.tryResolvingToNewTimeline(previousTimeline, currentTimeline)) { + if (!session.tryResolvingToNewTimeline(previousTimeline, currentTimeline) + || session.isFinishedAtEventTime(eventTime)) { iterator.remove(); if (session.isCreated) { if (session.sessionId.equals(currentSessionId)) { 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 930a835cec..1df3d64205 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 @@ -743,6 +743,128 @@ public final class DefaultPlaybackSessionManagerTest { verify(mockListener, never()).onSessionFinished(any(), anyString(), anyBoolean()); } + @Test + public void timelineUpdate_toNewMediaWithWindowIndexOnly_finishesOtherSessions() { + Timeline firstTimeline = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1000), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 2000), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 3000)); + EventTime eventTimeFirstTimelineWithPeriodId = + createEventTime( + firstTimeline, + /* windowIndex= */ 0, + new MediaPeriodId( + firstTimeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); + EventTime eventTimeFirstTimelineWindowOnly1 = + createEventTime(firstTimeline, /* windowIndex= */ 1, /* mediaPeriodId= */ null); + EventTime eventTimeFirstTimelineWindowOnly2 = + createEventTime(firstTimeline, /* windowIndex= */ 2, /* mediaPeriodId= */ null); + Timeline secondTimeline = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 2000), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 4000)); + EventTime eventTimeSecondTimeline = + createEventTime(secondTimeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null); + sessionManager.updateSessionsWithTimelineChange(eventTimeFirstTimelineWithPeriodId); + sessionManager.updateSessions(eventTimeFirstTimelineWindowOnly1); + sessionManager.updateSessions(eventTimeFirstTimelineWindowOnly2); + + sessionManager.updateSessionsWithTimelineChange(eventTimeSecondTimeline); + + InOrder inOrder = inOrder(mockListener); + ArgumentCaptor firstId = ArgumentCaptor.forClass(String.class); + inOrder + .verify(mockListener) + .onSessionCreated(eq(eventTimeFirstTimelineWithPeriodId), firstId.capture()); + inOrder + .verify(mockListener) + .onSessionActive(eventTimeFirstTimelineWithPeriodId, firstId.getValue()); + ArgumentCaptor secondId = ArgumentCaptor.forClass(String.class); + inOrder + .verify(mockListener) + .onSessionCreated(eq(eventTimeFirstTimelineWindowOnly1), secondId.capture()); + ArgumentCaptor thirdId = ArgumentCaptor.forClass(String.class); + inOrder + .verify(mockListener) + .onSessionCreated(eq(eventTimeFirstTimelineWindowOnly2), thirdId.capture()); + inOrder + .verify(mockListener) + .onSessionFinished( + eventTimeSecondTimeline, + firstId.getValue(), + /* automaticTransitionToNextPlayback= */ false); + inOrder + .verify(mockListener) + .onSessionFinished( + eventTimeSecondTimeline, + thirdId.getValue(), + /* automaticTransitionToNextPlayback= */ false); + inOrder.verify(mockListener).onSessionActive(eventTimeSecondTimeline, secondId.getValue()); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void timelineUpdate_toNewMediaWithMediaPeriodId_finishesOtherSessions() { + Timeline firstTimeline = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1000), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 2000), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 3000)); + EventTime eventTimeFirstTimeline1 = + createEventTime( + firstTimeline, + /* windowIndex= */ 0, + new MediaPeriodId( + firstTimeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0)); + EventTime eventTimeFirstTimeline2 = + createEventTime( + firstTimeline, + /* windowIndex= */ 1, + new MediaPeriodId( + firstTimeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1)); + EventTime eventTimeFirstTimeline3 = + createEventTime( + firstTimeline, + /* windowIndex= */ 2, + new MediaPeriodId( + firstTimeline.getUidOfPeriod(/* periodIndex= */ 2), /* windowSequenceNumber= */ 2)); + Timeline secondTimeline = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 2000), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1000), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 3000)); + EventTime eventTimeSecondTimeline = + createEventTime( + secondTimeline, + /* windowIndex= */ 0, + new MediaPeriodId( + secondTimeline.getUidOfPeriod(/* periodIndex= */ 0), + /* windowSequenceNumber= */ 1)); + sessionManager.updateSessionsWithTimelineChange(eventTimeFirstTimeline1); + sessionManager.updateSessions(eventTimeFirstTimeline2); + sessionManager.updateSessions(eventTimeFirstTimeline3); + + sessionManager.updateSessionsWithTimelineChange(eventTimeSecondTimeline); + + InOrder inOrder = inOrder(mockListener); + ArgumentCaptor firstId = ArgumentCaptor.forClass(String.class); + inOrder.verify(mockListener).onSessionCreated(eq(eventTimeFirstTimeline1), firstId.capture()); + inOrder.verify(mockListener).onSessionActive(eventTimeFirstTimeline1, firstId.getValue()); + ArgumentCaptor secondId = ArgumentCaptor.forClass(String.class); + inOrder.verify(mockListener).onSessionCreated(eq(eventTimeFirstTimeline2), secondId.capture()); + ArgumentCaptor thirdId = ArgumentCaptor.forClass(String.class); + inOrder.verify(mockListener).onSessionCreated(eq(eventTimeFirstTimeline3), thirdId.capture()); + inOrder + .verify(mockListener) + .onSessionFinished( + eventTimeSecondTimeline, + firstId.getValue(), + /* automaticTransitionToNextPlayback= */ false); + inOrder.verify(mockListener).onSessionActive(eventTimeSecondTimeline, secondId.getValue()); + inOrder.verifyNoMoreInteractions(); + } + @Test public void positionDiscontinuity_withinWindow_doesNotFinishSession() { Timeline timeline =