From 7c60e67f84af9fa4db84e10474f24ebc5995c446 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 20 Jan 2022 16:36:54 +0000 Subject: [PATCH] End finished sessions on timeline updates. We currently only end sessions on Timeline updates if the associated media is no longer in the playlist. But we should also end all sessions that are finished as a result of the timeline update (similar to how this is done for discontinuities). This issue was introduced by https://github.com/google/ExoPlayer/commit/394ab7bcfd53bc8ffb8d2e37a5aeb71fef04d13c PiperOrigin-RevId: 423075855 --- .../DefaultPlaybackSessionManager.java | 3 +- .../DefaultPlaybackSessionManagerTest.java | 122 ++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) 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 =