Move playback session manager to core library.

This allows to use the session management capabilities for other analytics
purposes.

PiperOrigin-RevId: 245710588
This commit is contained in:
tonihei 2019-04-29 10:31:36 +01:00 committed by Oliver Woodman
parent 880f403181
commit 1fb128df36
3 changed files with 1436 additions and 0 deletions

View File

@ -0,0 +1,359 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.analytics;
import androidx.annotation.Nullable;
import android.util.Base64;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.DiscontinuityReason;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
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
* timeline and also for each ad within the windows.
*
* <p>Sessions are identified by Base64-encoded, URL-safe, random strings.
*/
public final class DefaultPlaybackSessionManager implements PlaybackSessionManager {
private static final Random RANDOM = new Random();
private static final int SESSION_ID_LENGTH = 12;
private final Timeline.Window window;
private final Timeline.Period period;
private final HashMap<String, SessionDescriptor> sessions;
@MonotonicNonNull private Listener listener;
private Timeline currentTimeline;
@Nullable private MediaPeriodId currentMediaPeriodId;
@Nullable private String activeSessionId;
/** Creates session manager. */
public DefaultPlaybackSessionManager() {
window = new Timeline.Window();
period = new Timeline.Period();
sessions = new HashMap<>();
currentTimeline = Timeline.EMPTY;
}
@Override
public void setListener(Listener listener) {
this.listener = listener;
}
@Override
public synchronized String getSessionForMediaPeriodId(
Timeline timeline, MediaPeriodId mediaPeriodId) {
int windowIndex = timeline.getPeriodByUid(mediaPeriodId.periodUid, period).windowIndex;
return getOrAddSession(windowIndex, mediaPeriodId).sessionId;
}
@Override
public synchronized boolean belongsToSession(EventTime eventTime, String sessionId) {
SessionDescriptor sessionDescriptor = sessions.get(sessionId);
if (sessionDescriptor == null) {
return false;
}
sessionDescriptor.maybeSetWindowSequenceNumber(eventTime.windowIndex, eventTime.mediaPeriodId);
return sessionDescriptor.belongsToSession(eventTime.windowIndex, eventTime.mediaPeriodId);
}
@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);
}
}
}
}
@Override
public synchronized void handleTimelineUpdate(EventTime eventTime) {
Assertions.checkNotNull(listener);
Timeline previousTimeline = currentTimeline;
currentTimeline = eventTime.timeline;
Iterator<SessionDescriptor> iterator = sessions.values().iterator();
while (iterator.hasNext()) {
SessionDescriptor session = iterator.next();
if (!session.tryResolvingToNewTimeline(previousTimeline, currentTimeline)) {
iterator.remove();
if (session.isCreated) {
if (session.sessionId.equals(activeSessionId)) {
activeSessionId = null;
}
listener.onSessionFinished(
eventTime, session.sessionId, /* automaticTransitionToNextPlayback= */ false);
}
}
}
handlePositionDiscontinuity(eventTime, Player.DISCONTINUITY_REASON_INTERNAL);
}
@Override
public synchronized void handlePositionDiscontinuity(
EventTime eventTime, @DiscontinuityReason int reason) {
Assertions.checkNotNull(listener);
boolean hasAutomaticTransition =
reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
|| reason == Player.DISCONTINUITY_REASON_AD_INSERTION;
Iterator<SessionDescriptor> iterator = sessions.values().iterator();
while (iterator.hasNext()) {
SessionDescriptor session = iterator.next();
if (session.isFinishedAtEventTime(eventTime)) {
iterator.remove();
if (session.isCreated) {
boolean isRemovingActiveSession = session.sessionId.equals(activeSessionId);
boolean isAutomaticTransition = hasAutomaticTransition && isRemovingActiveSession;
if (isRemovingActiveSession) {
activeSessionId = null;
}
listener.onSessionFinished(eventTime, session.sessionId, isAutomaticTransition);
}
}
}
SessionDescriptor activeSessionDescriptor =
getOrAddSession(eventTime.windowIndex, eventTime.mediaPeriodId);
if (eventTime.mediaPeriodId != null
&& eventTime.mediaPeriodId.isAd()
&& (currentMediaPeriodId == null
|| currentMediaPeriodId.windowSequenceNumber
!= eventTime.mediaPeriodId.windowSequenceNumber
|| currentMediaPeriodId.adGroupIndex != eventTime.mediaPeriodId.adGroupIndex
|| currentMediaPeriodId.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) {
listener.onAdPlaybackStarted(
eventTime, contentSession.sessionId, activeSessionDescriptor.sessionId);
}
}
updateActiveSession(eventTime, activeSessionDescriptor);
}
private SessionDescriptor getOrAddSession(
int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
// There should only be one matching session if mediaPeriodId is non-null. If mediaPeriodId is
// null, there may be multiple matching sessions with different window sequence numbers or
// adMediaPeriodIds. The best match is the one with the smaller window sequence number, and for
// windows with ads, the content session is preferred over ad sessions.
SessionDescriptor bestMatch = null;
long bestMatchWindowSequenceNumber = Long.MAX_VALUE;
for (SessionDescriptor sessionDescriptor : sessions.values()) {
sessionDescriptor.maybeSetWindowSequenceNumber(windowIndex, mediaPeriodId);
if (sessionDescriptor.belongsToSession(windowIndex, mediaPeriodId)) {
long windowSequenceNumber = sessionDescriptor.windowSequenceNumber;
if (windowSequenceNumber == C.INDEX_UNSET
|| windowSequenceNumber < bestMatchWindowSequenceNumber) {
bestMatch = sessionDescriptor;
bestMatchWindowSequenceNumber = windowSequenceNumber;
} else if (windowSequenceNumber == bestMatchWindowSequenceNumber
&& Util.castNonNull(bestMatch).adMediaPeriodId != null
&& sessionDescriptor.adMediaPeriodId != null) {
bestMatch = sessionDescriptor;
}
}
}
if (bestMatch == null) {
String sessionId = generateSessionId();
bestMatch = new SessionDescriptor(sessionId, windowIndex, mediaPeriodId);
sessions.put(sessionId, bestMatch);
}
return bestMatch;
}
@RequiresNonNull("listener")
private void updateActiveSession(EventTime eventTime, SessionDescriptor sessionDescriptor) {
currentMediaPeriodId = eventTime.mediaPeriodId;
if (sessionDescriptor.isCreated && !sessionDescriptor.isActive) {
sessionDescriptor.isActive = true;
activeSessionId = sessionDescriptor.sessionId;
listener.onSessionActive(eventTime, sessionDescriptor.sessionId);
}
}
private static String generateSessionId() {
byte[] randomBytes = new byte[SESSION_ID_LENGTH];
RANDOM.nextBytes(randomBytes);
return Base64.encodeToString(randomBytes, Base64.URL_SAFE | Base64.NO_WRAP);
}
/**
* Descriptor for a session.
*
* <p>The session may be described in one of three ways:
*
* <ul>
* <li>A window index with unset window sequence number and a null ad media period id
* <li>A content window with index and sequence number, but a null ad media period id.
* <li>An ad with all values set.
* </ul>
*/
private final class SessionDescriptor {
private final String sessionId;
private int windowIndex;
private long windowSequenceNumber;
private @MonotonicNonNull MediaPeriodId adMediaPeriodId;
private boolean isCreated;
private boolean isActive;
public SessionDescriptor(
String sessionId, int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
this.sessionId = sessionId;
this.windowIndex = windowIndex;
this.windowSequenceNumber =
mediaPeriodId == null ? C.INDEX_UNSET : mediaPeriodId.windowSequenceNumber;
if (mediaPeriodId != null && mediaPeriodId.isAd()) {
this.adMediaPeriodId = mediaPeriodId;
}
}
public boolean tryResolvingToNewTimeline(Timeline oldTimeline, Timeline newTimeline) {
windowIndex = resolveWindowIndexToNewTimeline(oldTimeline, newTimeline, windowIndex);
if (windowIndex == C.INDEX_UNSET) {
return false;
}
if (adMediaPeriodId != null) {
int newPeriodIndex = newTimeline.getIndexOfPeriod(adMediaPeriodId.periodUid);
if (newPeriodIndex == C.INDEX_UNSET) {
return false;
}
}
return true;
}
public boolean belongsToSession(
int eventWindowIndex, @Nullable MediaPeriodId eventMediaPeriodId) {
if (eventMediaPeriodId == null) {
// Events without concrete media period id are for all sessions of the same window.
return eventWindowIndex == windowIndex;
}
if (adMediaPeriodId == null) {
// If this is a content session, only events for content with the same window sequence
// number belong to this session.
return !eventMediaPeriodId.isAd()
&& eventMediaPeriodId.windowSequenceNumber == windowSequenceNumber;
}
// If this is an ad session, only events for this ad belong to the session.
return eventMediaPeriodId.windowSequenceNumber == adMediaPeriodId.windowSequenceNumber
&& eventMediaPeriodId.adGroupIndex == adMediaPeriodId.adGroupIndex
&& eventMediaPeriodId.adIndexInAdGroup == adMediaPeriodId.adIndexInAdGroup;
}
public void maybeSetWindowSequenceNumber(
int eventWindowIndex, @Nullable MediaPeriodId eventMediaPeriodId) {
if (windowSequenceNumber == C.INDEX_UNSET
&& eventWindowIndex == windowIndex
&& eventMediaPeriodId != null
&& !eventMediaPeriodId.isAd()) {
// Set window sequence number for this session as soon as we have one.
windowSequenceNumber = eventMediaPeriodId.windowSequenceNumber;
}
}
public boolean isFinishedAtEventTime(EventTime eventTime) {
if (windowSequenceNumber == C.INDEX_UNSET) {
// Sessions with unspecified window sequence number are kept until we know more.
return false;
}
if (eventTime.mediaPeriodId == null) {
// For event times without media period id (e.g. after seek to new window), we only keep
// sessions of this window.
return windowIndex != eventTime.windowIndex;
}
if (eventTime.mediaPeriodId.windowSequenceNumber > windowSequenceNumber) {
// All past window sequence numbers are finished.
return true;
}
if (adMediaPeriodId == null) {
// Current or future content is not finished.
return false;
}
int eventPeriodIndex = eventTime.timeline.getIndexOfPeriod(eventTime.mediaPeriodId.periodUid);
int adPeriodIndex = eventTime.timeline.getIndexOfPeriod(adMediaPeriodId.periodUid);
if (eventTime.mediaPeriodId.windowSequenceNumber < adMediaPeriodId.windowSequenceNumber
|| eventPeriodIndex < adPeriodIndex) {
// Ads in future windows or periods are not finished.
return false;
}
if (eventPeriodIndex > adPeriodIndex) {
// Ads in past periods are finished.
return true;
}
if (eventTime.mediaPeriodId.isAd()) {
int eventAdGroup = eventTime.mediaPeriodId.adGroupIndex;
int eventAdIndex = eventTime.mediaPeriodId.adIndexInAdGroup;
// Finished if event is for an ad after this one in the same period.
return eventAdGroup > adMediaPeriodId.adGroupIndex
|| (eventAdGroup == adMediaPeriodId.adGroupIndex
&& eventAdIndex > adMediaPeriodId.adIndexInAdGroup);
} else {
eventTime.timeline.getPeriod(adPeriodIndex, period);
long adGroupTimeMs =
adMediaPeriodId.adGroupIndex < period.getAdGroupCount()
? C.usToMs(period.getAdGroupTimeUs(adMediaPeriodId.adGroupIndex))
: 0;
// Finished if the event is for content after this ad.
return adGroupTimeMs <= eventTime.currentPlaybackPositionMs;
}
}
private int resolveWindowIndexToNewTimeline(
Timeline oldTimeline, Timeline newTimeline, int windowIndex) {
if (windowIndex >= oldTimeline.getWindowCount()) {
return windowIndex < newTimeline.getWindowCount() ? windowIndex : C.INDEX_UNSET;
}
oldTimeline.getWindow(windowIndex, window);
for (int periodIndex = window.firstPeriodIndex;
periodIndex <= window.lastPeriodIndex;
periodIndex++) {
Object periodUid = oldTimeline.getUidOfPeriod(periodIndex);
int newPeriodIndex = newTimeline.getIndexOfPeriod(periodUid);
if (newPeriodIndex != C.INDEX_UNSET) {
return newTimeline.getPeriod(newPeriodIndex, period).windowIndex;
}
}
return C.INDEX_UNSET;
}
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.analytics;
import com.google.android.exoplayer2.Player.DiscontinuityReason;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
/**
* Manager for active playback sessions.
*
* <p>The manager keeps track of the association between window index and/or media period id to
* session identifier.
*/
public interface PlaybackSessionManager {
/** A listener for session updates. */
interface Listener {
/**
* Called when a new session is created as a result of {@link #updateSessions(EventTime)}.
*
* @param eventTime The {@link EventTime} at which the session is created.
* @param sessionId The identifier of the new session.
*/
void onSessionCreated(EventTime eventTime, String sessionId);
/**
* Called when a session becomes active, i.e. playing in the foreground.
*
* @param eventTime The {@link EventTime} at which the session becomes active.
* @param sessionId The identifier of the session.
*/
void onSessionActive(EventTime eventTime, String sessionId);
/**
* Called when a session is interrupted by ad playback.
*
* @param eventTime The {@link EventTime} at which the ad playback starts.
* @param contentSessionId The session identifier of the content session.
* @param adSessionId The identifier of the ad session.
*/
void onAdPlaybackStarted(EventTime eventTime, String contentSessionId, String adSessionId);
/**
* Called when a session is permanently finished.
*
* @param eventTime The {@link EventTime} at which the session finished.
* @param sessionId The identifier of the finished session.
* @param automaticTransitionToNextPlayback Whether the session finished because of an automatic
* transition to the next playback item.
*/
void onSessionFinished(
EventTime eventTime, String sessionId, boolean automaticTransitionToNextPlayback);
}
/**
* Sets the listener to be notified of session updates. Must be called before the session manager
* is used.
*
* @param listener The {@link Listener} to be notified of session updates.
*/
void setListener(Listener listener);
/**
* Returns the session identifier for the given media period id.
*
* <p>Note that this will reserve a new session identifier if it doesn't exist yet, but will not
* call any {@link Listener} callbacks.
*
* @param timeline The timeline, {@code mediaPeriodId} is part of.
* @param mediaPeriodId A {@link MediaPeriodId}.
*/
String getSessionForMediaPeriodId(Timeline timeline, MediaPeriodId mediaPeriodId);
/**
* Returns whether an event time belong to a session.
*
* @param eventTime The {@link EventTime}.
* @param sessionId A session identifier.
* @return Whether the event belongs to the specified session.
*/
boolean belongsToSession(EventTime eventTime, String sessionId);
/**
* Updates or creates sessions based on a player {@link EventTime}.
*
* @param eventTime The {@link EventTime}.
*/
void updateSessions(EventTime eventTime);
/**
* Updates the session associations to a new timeline.
*
* @param eventTime The event time with the timeline change.
*/
void handleTimelineUpdate(EventTime eventTime);
/**
* Handles a position discontinuity.
*
* @param eventTime The event time of the position discontinuity.
* @param reason The {@link DiscontinuityReason}.
*/
void handlePositionDiscontinuity(EventTime eventTime, @DiscontinuityReason int reason);
}

View File

@ -0,0 +1,957 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/** Unit test for {@link DefaultPlaybackSessionManager}. */
@RunWith(AndroidJUnit4.class)
public final class DefaultPlaybackSessionManagerTest {
private DefaultPlaybackSessionManager sessionManager;
@Mock private PlaybackSessionManager.Listener mockListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
sessionManager = new DefaultPlaybackSessionManager();
sessionManager.setListener(mockListener);
}
@Test
public void updateSessions_withoutMediaPeriodId_createsNewSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
EventTime eventTime = createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId */ null);
sessionManager.updateSessions(eventTime);
verify(mockListener).onSessionCreated(eq(eventTime), anyString());
verify(mockListener).onSessionActive(eq(eventTime), anyString());
verifyNoMoreInteractions(mockListener);
}
@Test
public void updateSessions_withMediaPeriodId_createsNewSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaPeriodId mediaPeriodId =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
EventTime eventTime = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId);
sessionManager.updateSessions(eventTime);
ArgumentCaptor<String> sessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime), sessionId.capture());
verify(mockListener).onSessionActive(eventTime, sessionId.getValue());
verifyNoMoreInteractions(mockListener);
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId))
.isEqualTo(sessionId.getValue());
}
@Test
public void
updateSessions_ofSameWindow_withMediaPeriodId_afterWithoutMediaPeriodId_doesNotCreateNewSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaPeriodId mediaPeriodId =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
EventTime eventTime1 =
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
EventTime eventTime2 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
ArgumentCaptor<String> sessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), sessionId.capture());
verify(mockListener).onSessionActive(eventTime1, sessionId.getValue());
verifyNoMoreInteractions(mockListener);
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId))
.isEqualTo(sessionId.getValue());
}
@Test
public void updateSessions_ofSameWindow_withAd_afterWithoutMediaPeriodId_createsNewSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaPeriodId mediaPeriodId =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0),
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0);
EventTime eventTime1 =
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
EventTime eventTime2 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
ArgumentCaptor<String> contentSessionId = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> adSessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), contentSessionId.capture());
verify(mockListener).onSessionCreated(eq(eventTime2), adSessionId.capture());
verify(mockListener).onSessionActive(eventTime1, contentSessionId.getValue());
verifyNoMoreInteractions(mockListener);
assertThat(contentSessionId).isNotEqualTo(adSessionId);
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId))
.isEqualTo(adSessionId.getValue());
}
@Test
public void
updateSessions_ofSameWindow_withoutMediaPeriodId_afterMediaPeriodId_doesNotCreateNewSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaPeriodId mediaPeriodId =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
EventTime eventTime1 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId);
EventTime eventTime2 =
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
ArgumentCaptor<String> sessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), sessionId.capture());
verify(mockListener).onSessionActive(eventTime1, sessionId.getValue());
verifyNoMoreInteractions(mockListener);
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId))
.isEqualTo(sessionId.getValue());
}
@Test
public void updateSessions_ofSameWindow_withoutMediaPeriodId_afterAd_doesNotCreateNewSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaPeriodId mediaPeriodId =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0),
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0);
EventTime eventTime1 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId);
EventTime eventTime2 =
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
ArgumentCaptor<String> sessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), sessionId.capture());
verify(mockListener).onSessionActive(eventTime1, sessionId.getValue());
verifyNoMoreInteractions(mockListener);
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId))
.isEqualTo(sessionId.getValue());
}
@Test
public void updateSessions_withOtherMediaPeriodId_ofSameWindow_doesNotCreateNewSession() {
Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ 0));
MediaPeriodId mediaPeriodId1 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
MediaPeriodId mediaPeriodId2 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 0);
EventTime eventTime1 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId1);
EventTime eventTime2 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId2);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
ArgumentCaptor<String> sessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), sessionId.capture());
verify(mockListener).onSessionActive(eventTime1, sessionId.getValue());
verifyNoMoreInteractions(mockListener);
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId1))
.isEqualTo(sessionId.getValue());
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId2))
.isEqualTo(sessionId.getValue());
}
@Test
public void updateSessions_withAd_ofSameWindow_createsNewSession() {
Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ 0));
MediaPeriodId mediaPeriodId1 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
MediaPeriodId mediaPeriodId2 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0),
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0);
EventTime eventTime1 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId1);
EventTime eventTime2 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId2);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
ArgumentCaptor<String> contentSessionId = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> adSessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), contentSessionId.capture());
verify(mockListener).onSessionActive(eventTime1, contentSessionId.getValue());
verify(mockListener).onSessionCreated(eq(eventTime2), adSessionId.capture());
verifyNoMoreInteractions(mockListener);
assertThat(contentSessionId).isNotEqualTo(adSessionId);
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId1))
.isEqualTo(contentSessionId.getValue());
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId2))
.isEqualTo(adSessionId.getValue());
}
@Test
public void updateSessions_ofOtherWindow_createsNewSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
EventTime eventTime1 =
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
EventTime eventTime2 =
createEventTime(timeline, /* windowIndex= */ 1, /* mediaPeriodId= */ null);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
ArgumentCaptor<String> sessionId1 = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> sessionId2 = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), sessionId1.capture());
verify(mockListener).onSessionCreated(eq(eventTime2), sessionId2.capture());
verify(mockListener).onSessionActive(eventTime1, sessionId1.getValue());
verifyNoMoreInteractions(mockListener);
assertThat(sessionId1).isNotEqualTo(sessionId2);
}
@Test
public void updateSessions_withMediaPeriodId_ofOtherWindow_createsNewSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
MediaPeriodId mediaPeriodId1 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
MediaPeriodId mediaPeriodId2 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1);
EventTime eventTime1 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId1);
EventTime eventTime2 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId2);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
ArgumentCaptor<String> sessionId1 = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> sessionId2 = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), sessionId1.capture());
verify(mockListener).onSessionCreated(eq(eventTime2), sessionId2.capture());
verify(mockListener).onSessionActive(eventTime1, sessionId1.getValue());
verifyNoMoreInteractions(mockListener);
assertThat(sessionId1).isNotEqualTo(sessionId2);
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId1))
.isEqualTo(sessionId1.getValue());
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId2))
.isEqualTo(sessionId2.getValue());
}
@Test
public void updateSessions_ofSameWindow_withNewWindowSequenceNumber_createsNewSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaPeriodId mediaPeriodId1 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
MediaPeriodId mediaPeriodId2 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 1);
EventTime eventTime1 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId1);
EventTime eventTime2 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId2);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
ArgumentCaptor<String> sessionId1 = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> sessionId2 = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), sessionId1.capture());
verify(mockListener).onSessionActive(eventTime1, sessionId1.getValue());
verify(mockListener).onSessionCreated(eq(eventTime2), sessionId2.capture());
verifyNoMoreInteractions(mockListener);
assertThat(sessionId1).isNotEqualTo(sessionId2);
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId1))
.isEqualTo(sessionId1.getValue());
assertThat(sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId2))
.isEqualTo(sessionId2.getValue());
}
@Test
public void
updateSessions_withoutMediaPeriodId_andPreviouslyCreatedSessions_doesNotCreateNewSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaPeriodId mediaPeriodId1 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
MediaPeriodId mediaPeriodId2 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 1);
MediaPeriodId mediaPeriodIdWithAd =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0),
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* windowSequenceNumber= */ 0);
EventTime eventTime1 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId1);
EventTime eventTime2 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId2);
EventTime eventTime3 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodIdWithAd);
EventTime eventTime4 =
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
sessionManager.updateSessions(eventTime3);
sessionManager.updateSessions(eventTime4);
verify(mockListener).onSessionCreated(eq(eventTime1), anyString());
verify(mockListener).onSessionActive(eq(eventTime1), anyString());
verify(mockListener).onSessionCreated(eq(eventTime2), anyString());
verify(mockListener).onSessionCreated(eq(eventTime3), anyString());
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);
MediaPeriodId mediaPeriodId =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
EventTime eventTime = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId);
String expectedSessionId = sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId);
sessionManager.updateSessions(eventTime);
ArgumentCaptor<String> sessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime), sessionId.capture());
verify(mockListener).onSessionActive(eventTime, sessionId.getValue());
verifyNoMoreInteractions(mockListener);
assertThat(sessionId.getValue()).isEqualTo(expectedSessionId);
}
@Test
public void updateSessions_withoutMediaPeriodId_afterSessionForMediaPeriodId_returnsSameValue() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaPeriodId mediaPeriodId =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
EventTime eventTime =
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
String expectedSessionId = sessionManager.getSessionForMediaPeriodId(timeline, mediaPeriodId);
sessionManager.updateSessions(eventTime);
ArgumentCaptor<String> sessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime), sessionId.capture());
verify(mockListener).onSessionActive(eventTime, sessionId.getValue());
verifyNoMoreInteractions(mockListener);
assertThat(sessionId.getValue()).isEqualTo(expectedSessionId);
}
@Test
public void belongsToSession_withSameWindowIndex_returnsTrue() {
EventTime eventTime =
createEventTime(Timeline.EMPTY, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
EventTime eventTimeWithTimeline =
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
MediaPeriodId mediaPeriodId =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
EventTime eventTimeWithMediaPeriodId =
createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId);
sessionManager.updateSessions(eventTime);
ArgumentCaptor<String> sessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime), sessionId.capture());
assertThat(sessionManager.belongsToSession(eventTime, sessionId.getValue())).isTrue();
assertThat(sessionManager.belongsToSession(eventTimeWithTimeline, sessionId.getValue()))
.isTrue();
assertThat(sessionManager.belongsToSession(eventTimeWithMediaPeriodId, sessionId.getValue()))
.isTrue();
}
@Test
public void belongsToSession_withOtherWindowIndex_returnsFalse() {
EventTime eventTime =
createEventTime(Timeline.EMPTY, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
EventTime eventTimeOtherWindow =
createEventTime(Timeline.EMPTY, /* windowIndex= */ 1, /* mediaPeriodId= */ null);
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
MediaPeriodId mediaPeriodId =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1);
EventTime eventTimeWithOtherMediaPeriodId =
createEventTime(timeline, /* windowIndex= */ 1, mediaPeriodId);
sessionManager.updateSessions(eventTime);
ArgumentCaptor<String> sessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime), sessionId.capture());
assertThat(sessionManager.belongsToSession(eventTimeOtherWindow, sessionId.getValue()))
.isFalse();
assertThat(
sessionManager.belongsToSession(eventTimeWithOtherMediaPeriodId, sessionId.getValue()))
.isFalse();
}
@Test
public void belongsToSession_withOtherWindowSequenceNumber_returnsFalse() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaPeriodId mediaPeriodId1 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
MediaPeriodId mediaPeriodId2 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 1);
EventTime eventTime1 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId1);
EventTime eventTime2 = createEventTime(timeline, /* windowIndex= */ 0, mediaPeriodId2);
sessionManager.updateSessions(eventTime1);
ArgumentCaptor<String> sessionId = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), sessionId.capture());
assertThat(sessionManager.belongsToSession(eventTime2, sessionId.getValue())).isFalse();
}
@Test
public void belongsToSession_withAd_returnsFalse() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
MediaPeriodId mediaPeriodId1 =
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0);
MediaPeriodId mediaPeriodId2 =
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);
ArgumentCaptor<String> sessionId1 = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> 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();
}
@Test
public void initialTimelineUpdate_finishesAllSessionsOutsideTimeline() {
EventTime eventTime1 =
createEventTime(Timeline.EMPTY, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
EventTime eventTime2 =
createEventTime(Timeline.EMPTY, /* windowIndex= */ 1, /* mediaPeriodId= */ null);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
EventTime newTimelineEventTime =
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
sessionManager.handleTimelineUpdate(newTimelineEventTime);
ArgumentCaptor<String> sessionId1 = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> sessionId2 = ArgumentCaptor.forClass(String.class);
verify(mockListener).onSessionCreated(eq(eventTime1), sessionId1.capture());
verify(mockListener).onSessionCreated(eq(eventTime2), sessionId2.capture());
verify(mockListener).onSessionActive(eventTime1, sessionId1.getValue());
verify(mockListener)
.onSessionFinished(
newTimelineEventTime,
sessionId2.getValue(),
/* automaticTransitionToNextPlayback= */ false);
verifyNoMoreInteractions(mockListener);
}
@Test
public void dynamicTimelineUpdate_resolvesWindowIndices() {
Timeline initialTimeline =
new FakeTimeline(
new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ 100),
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 200),
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 300));
EventTime eventForInitialTimelineId100 =
createEventTime(
initialTimeline,
/* windowIndex= */ 0,
new MediaPeriodId(
initialTimeline.getUidOfPeriod(/* periodIndex= */ 1),
/* windowSequenceNumber= */ 0));
EventTime eventForInitialTimelineId200 =
createEventTime(
initialTimeline,
/* windowIndex= */ 1,
new MediaPeriodId(
initialTimeline.getUidOfPeriod(/* periodIndex= */ 2),
/* windowSequenceNumber= */ 1));
EventTime eventForInitialTimelineId300 =
createEventTime(
initialTimeline,
/* windowIndex= */ 2,
new MediaPeriodId(
initialTimeline.getUidOfPeriod(/* periodIndex= */ 3),
/* windowSequenceNumber= */ 2));
sessionManager.handleTimelineUpdate(eventForInitialTimelineId100);
sessionManager.updateSessions(eventForInitialTimelineId100);
sessionManager.updateSessions(eventForInitialTimelineId200);
sessionManager.updateSessions(eventForInitialTimelineId300);
String sessionId100 =
sessionManager.getSessionForMediaPeriodId(
initialTimeline, eventForInitialTimelineId100.mediaPeriodId);
String sessionId200 =
sessionManager.getSessionForMediaPeriodId(
initialTimeline, eventForInitialTimelineId200.mediaPeriodId);
String sessionId300 =
sessionManager.getSessionForMediaPeriodId(
initialTimeline, eventForInitialTimelineId300.mediaPeriodId);
Timeline timelineUpdate =
new FakeTimeline(
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 300),
new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 100));
EventTime eventForTimelineUpdateId100 =
createEventTime(
timelineUpdate,
/* windowIndex= */ 1,
new MediaPeriodId(
timelineUpdate.getUidOfPeriod(/* periodIndex= */ 1),
/* windowSequenceNumber= */ 0));
EventTime eventForTimelineUpdateId300 =
createEventTime(
timelineUpdate,
/* windowIndex= */ 0,
new MediaPeriodId(
timelineUpdate.getUidOfPeriod(/* periodIndex= */ 0),
/* windowSequenceNumber= */ 2));
sessionManager.handleTimelineUpdate(eventForTimelineUpdateId100);
String updatedSessionId100 =
sessionManager.getSessionForMediaPeriodId(
timelineUpdate, eventForTimelineUpdateId100.mediaPeriodId);
String updatedSessionId300 =
sessionManager.getSessionForMediaPeriodId(
timelineUpdate, eventForTimelineUpdateId300.mediaPeriodId);
verify(mockListener).onSessionCreated(eventForInitialTimelineId100, sessionId100);
verify(mockListener).onSessionActive(eventForInitialTimelineId100, sessionId100);
verify(mockListener).onSessionCreated(eventForInitialTimelineId200, sessionId200);
verify(mockListener).onSessionCreated(eventForInitialTimelineId300, sessionId300);
verify(mockListener)
.onSessionFinished(
eventForTimelineUpdateId100,
sessionId200,
/* automaticTransitionToNextPlayback= */ false);
verifyNoMoreInteractions(mockListener);
assertThat(updatedSessionId100).isEqualTo(sessionId100);
assertThat(updatedSessionId300).isEqualTo(sessionId300);
}
@Test
public void positionDiscontinuity_withinWindow_doesNotFinishSession() {
Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 2, /* id= */ 100));
EventTime eventTime1 =
createEventTime(
timeline,
/* windowIndex= */ 0,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0));
EventTime eventTime2 =
createEventTime(
timeline,
/* windowIndex= */ 0,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 0));
sessionManager.handleTimelineUpdate(eventTime1);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
sessionManager.handlePositionDiscontinuity(
eventTime2, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION);
verify(mockListener).onSessionCreated(eq(eventTime1), anyString());
verify(mockListener).onSessionActive(eq(eventTime1), anyString());
verifyNoMoreInteractions(mockListener);
}
@Test
public void positionDiscontinuity_toNewWindow_withPeriodTransitionReason_finishesSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
EventTime eventTime1 =
createEventTime(
timeline,
/* windowIndex= */ 0,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0));
EventTime eventTime2 =
createEventTime(
timeline,
/* windowIndex= */ 1,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1));
sessionManager.handleTimelineUpdate(eventTime1);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
String sessionId1 =
sessionManager.getSessionForMediaPeriodId(timeline, eventTime1.mediaPeriodId);
String sessionId2 =
sessionManager.getSessionForMediaPeriodId(timeline, eventTime2.mediaPeriodId);
sessionManager.handlePositionDiscontinuity(
eventTime2, Player.DISCONTINUITY_REASON_PERIOD_TRANSITION);
verify(mockListener).onSessionCreated(eventTime1, sessionId1);
verify(mockListener).onSessionActive(eventTime1, sessionId1);
verify(mockListener).onSessionCreated(eq(eventTime2), anyString());
verify(mockListener)
.onSessionFinished(eventTime2, sessionId1, /* automaticTransitionToNextPlayback= */ true);
verify(mockListener).onSessionActive(eventTime2, sessionId2);
verifyNoMoreInteractions(mockListener);
}
@Test
public void positionDiscontinuity_toNewWindow_withSeekTransitionReason_finishesSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
EventTime eventTime1 =
createEventTime(
timeline,
/* windowIndex= */ 0,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0));
EventTime eventTime2 =
createEventTime(
timeline,
/* windowIndex= */ 1,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1));
sessionManager.handleTimelineUpdate(eventTime1);
sessionManager.updateSessions(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);
verify(mockListener).onSessionCreated(eventTime1, sessionId1);
verify(mockListener).onSessionActive(eventTime1, sessionId1);
verify(mockListener).onSessionCreated(eq(eventTime2), anyString());
verify(mockListener)
.onSessionFinished(eventTime2, sessionId1, /* automaticTransitionToNextPlayback= */ false);
verify(mockListener).onSessionActive(eventTime2, sessionId2);
verifyNoMoreInteractions(mockListener);
}
@Test
public void positionDiscontinuity_toSameWindow_withoutMediaPeriodId_doesNotFinishSession() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
EventTime eventTime1 =
createEventTime(
timeline,
/* windowIndex= */ 0,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0));
EventTime eventTime2 =
createEventTime(timeline, /* windowIndex= */ 0, /* mediaPeriodId= */ null);
sessionManager.handleTimelineUpdate(eventTime1);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
sessionManager.handlePositionDiscontinuity(eventTime2, Player.DISCONTINUITY_REASON_SEEK);
verify(mockListener, never()).onSessionFinished(any(), anyString(), anyBoolean());
}
@Test
public void positionDiscontinuity_toNewWindow_finishesOnlyPastSessions() {
Timeline timeline = new FakeTimeline(/* windowCount= */ 4);
EventTime eventTime1 =
createEventTime(
timeline,
/* windowIndex= */ 0,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 0), /* windowSequenceNumber= */ 0));
EventTime eventTime2 =
createEventTime(
timeline,
/* windowIndex= */ 1,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 1), /* windowSequenceNumber= */ 1));
EventTime eventTime3 =
createEventTime(
timeline,
/* windowIndex= */ 2,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 2), /* windowSequenceNumber= */ 2));
EventTime eventTime4 =
createEventTime(
timeline,
/* windowIndex= */ 3,
new MediaPeriodId(
timeline.getUidOfPeriod(/* periodIndex= */ 3), /* windowSequenceNumber= */ 3));
sessionManager.handleTimelineUpdate(eventTime1);
sessionManager.updateSessions(eventTime1);
sessionManager.updateSessions(eventTime2);
sessionManager.updateSessions(eventTime3);
sessionManager.updateSessions(eventTime4);
String sessionId1 =
sessionManager.getSessionForMediaPeriodId(timeline, eventTime1.mediaPeriodId);
String sessionId2 =
sessionManager.getSessionForMediaPeriodId(timeline, eventTime2.mediaPeriodId);
sessionManager.handlePositionDiscontinuity(eventTime3, Player.DISCONTINUITY_REASON_SEEK);
verify(mockListener).onSessionCreated(eventTime1, sessionId1);
verify(mockListener).onSessionActive(eventTime1, sessionId1);
verify(mockListener).onSessionCreated(eventTime2, sessionId2);
verify(mockListener).onSessionCreated(eq(eventTime3), anyString());
verify(mockListener).onSessionCreated(eq(eventTime4), anyString());
verify(mockListener)
.onSessionFinished(eventTime3, sessionId1, /* automaticTransitionToNextPlayback= */ false);
verify(mockListener)
.onSessionFinished(eventTime3, sessionId2, /* automaticTransitionToNextPlayback= */ false);
verify(mockListener).onSessionActive(eq(eventTime3), anyString());
verifyNoMoreInteractions(mockListener);
}
@Test
public void positionDiscontinuity_fromAdToContent_finishesAd() {
Timeline adTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* periodCount= */ 1,
/* id= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs =*/ 10 * C.MICROS_PER_SECOND,
new AdPlaybackState(/* adGroupTimesUs= */ 0, 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 contentEventTime =
createEventTime(
adTimeline,
/* windowIndex= */ 0,
new MediaPeriodId(
adTimeline.getUidOfPeriod(/* periodIndex= */ 0),
/* windowSequenceNumber= */ 0,
/* nextAdGroupIndex= */ 1));
sessionManager.handleTimelineUpdate(adEventTime1);
sessionManager.updateSessions(adEventTime1);
sessionManager.updateSessions(adEventTime2);
String adSessionId1 =
sessionManager.getSessionForMediaPeriodId(adTimeline, adEventTime1.mediaPeriodId);
sessionManager.handlePositionDiscontinuity(
contentEventTime, Player.DISCONTINUITY_REASON_AD_INSERTION);
verify(mockListener).onSessionCreated(adEventTime1, adSessionId1);
verify(mockListener).onSessionActive(adEventTime1, adSessionId1);
verify(mockListener).onSessionCreated(eq(adEventTime2), anyString());
verify(mockListener)
.onSessionFinished(
contentEventTime, adSessionId1, /* automaticTransitionToNextPlayback= */ true);
verifyNoMoreInteractions(mockListener);
}
@Test
public void positionDiscontinuity_fromContentToAd_doesNotFinishSessions() {
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 contentEventTime =
createEventTime(
adTimeline,
/* windowIndex= */ 0,
new MediaPeriodId(
adTimeline.getUidOfPeriod(/* periodIndex= */ 0),
/* windowSequenceNumber= */ 0,
/* nextAdGroupIndex= */ 0));
sessionManager.handleTimelineUpdate(contentEventTime);
sessionManager.updateSessions(contentEventTime);
sessionManager.updateSessions(adEventTime1);
sessionManager.updateSessions(adEventTime2);
sessionManager.handlePositionDiscontinuity(
adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION);
verify(mockListener, never()).onSessionFinished(any(), anyString(), anyBoolean());
}
@Test
public void positionDiscontinuity_fromAdToAd_finishesPastAds_andNotifiesAdPlaybackStated() {
Timeline adTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* periodCount= */ 1,
/* id= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs =*/ 10 * C.MICROS_PER_SECOND,
new AdPlaybackState(/* adGroupTimesUs= */ 0, 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 contentEventTime =
createEventTime(
adTimeline,
/* windowIndex= */ 0,
new MediaPeriodId(
adTimeline.getUidOfPeriod(/* periodIndex= */ 0),
/* windowSequenceNumber= */ 0,
/* nextAdGroupIndex= */ 1));
sessionManager.handleTimelineUpdate(contentEventTime);
sessionManager.updateSessions(contentEventTime);
sessionManager.updateSessions(adEventTime1);
sessionManager.updateSessions(adEventTime2);
String contentSessionId =
sessionManager.getSessionForMediaPeriodId(adTimeline, contentEventTime.mediaPeriodId);
String adSessionId1 =
sessionManager.getSessionForMediaPeriodId(adTimeline, adEventTime1.mediaPeriodId);
String adSessionId2 =
sessionManager.getSessionForMediaPeriodId(adTimeline, adEventTime2.mediaPeriodId);
sessionManager.handlePositionDiscontinuity(
adEventTime1, Player.DISCONTINUITY_REASON_AD_INSERTION);
sessionManager.handlePositionDiscontinuity(adEventTime2, Player.DISCONTINUITY_REASON_SEEK);
verify(mockListener).onSessionCreated(eq(contentEventTime), anyString());
verify(mockListener).onSessionActive(eq(contentEventTime), anyString());
verify(mockListener).onSessionCreated(adEventTime1, adSessionId1);
verify(mockListener).onSessionCreated(adEventTime2, adSessionId2);
verify(mockListener).onAdPlaybackStarted(adEventTime1, contentSessionId, adSessionId1);
verify(mockListener).onSessionActive(adEventTime1, adSessionId1);
verify(mockListener)
.onSessionFinished(
adEventTime2, adSessionId1, /* automaticTransitionToNextPlayback= */ false);
verify(mockListener).onAdPlaybackStarted(adEventTime2, contentSessionId, adSessionId2);
verify(mockListener).onSessionActive(adEventTime2, adSessionId2);
verifyNoMoreInteractions(mockListener);
}
private static EventTime createEventTime(
Timeline timeline, int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
return new EventTime(
/* realtimeMs = */ 0,
timeline,
windowIndex,
mediaPeriodId,
/* eventPlaybackPositionMs= */ 0,
/* currentPlaybackPositionMs= */ 0,
/* totalBufferedDurationMs= */ 0);
}
}