Forward ad group and ad index when creating period from concatanted media sources.

Also added tests which verify the intended behaviour.

GitHub:#3452

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=175656478
This commit is contained in:
tonihei 2017-11-14 03:01:58 -08:00 committed by Oliver Woodman
parent b17ae80679
commit b865259e63
8 changed files with 180 additions and 36 deletions

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
import com.google.android.exoplayer2.testutil.FakeTimeline;
@ -196,6 +197,31 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
}
}
public void testPeriodCreationWithAds() throws InterruptedException {
// Create media source with ad child source.
Timeline timelineContentOnly = new FakeTimeline(
new TimelineWindowDefinition(2, 111, true, false, 10 * C.MICROS_PER_SECOND));
Timeline timelineWithAds = new FakeTimeline(
new TimelineWindowDefinition(2, 222, true, false, 10 * C.MICROS_PER_SECOND, 1, 1));
FakeMediaSource mediaSourceContentOnly = new FakeMediaSource(timelineContentOnly, null);
FakeMediaSource mediaSourceWithAds = new FakeMediaSource(timelineWithAds, null);
ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(mediaSourceContentOnly,
mediaSourceWithAds);
// Prepare and assert timeline contains ad groups.
Timeline timeline = TestUtil.extractTimelineFromMediaSource(mediaSource);
TimelineAsserts.assertAdGroupCounts(timeline, 0, 0, 1, 1);
// Create all periods and assert period creation of child media sources has been called.
TimelineAsserts.assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline, 10_000);
mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(0));
mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(1));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0, 0, 0));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0));
}
/**
* Wraps the specified timelines in a {@link ConcatenatingMediaSource} and returns
* the concatenated timeline.

View File

@ -154,7 +154,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
assertEquals(0, timeline.getLastWindowIndex(true));
// Assert all periods can be prepared.
assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline.getPeriodCount());
TimelineAsserts.assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline,
TIMEOUT_MS);
// Remove at front of queue.
mediaSource.removeMediaSource(0);
@ -205,7 +206,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true,
1, 2, C.INDEX_UNSET);
assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline.getPeriodCount());
TimelineAsserts.assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline,
TIMEOUT_MS);
mediaSource.releaseSource();
for (int i = 1; i < 4; i++) {
childSources[i].assertReleased();
@ -239,7 +241,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
TimelineAsserts.assertPeriodCounts(timeline, 1, 9);
TimelineAsserts.assertWindowIds(timeline, 111, 999);
TimelineAsserts.assertWindowIsDynamic(timeline, false, false);
assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline.getPeriodCount());
TimelineAsserts.assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline,
TIMEOUT_MS);
//Add lazy sources after preparation (and also try to prepare media period from lazy source).
mediaSource.addMediaSource(1, lazySources[2]);
@ -335,7 +338,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
assertEquals(2, timeline.getLastWindowIndex(false));
assertEquals(2, timeline.getFirstWindowIndex(true));
assertEquals(0, timeline.getLastWindowIndex(true));
assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline.getPeriodCount());
TimelineAsserts.assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline,
TIMEOUT_MS);
}
public void testIllegalArguments() {
@ -533,6 +537,35 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
waitForCustomRunnable();
}
public void testPeriodCreationWithAds() throws InterruptedException {
// Create dynamic media source with ad child source.
Timeline timelineContentOnly = new FakeTimeline(
new TimelineWindowDefinition(2, 111, true, false, 10 * C.MICROS_PER_SECOND));
Timeline timelineWithAds = new FakeTimeline(
new TimelineWindowDefinition(2, 222, true, false, 10 * C.MICROS_PER_SECOND, 1, 1));
FakeMediaSource mediaSourceContentOnly = new FakeMediaSource(timelineContentOnly, null);
FakeMediaSource mediaSourceWithAds = new FakeMediaSource(timelineWithAds, null);
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
mediaSource.addMediaSource(mediaSourceContentOnly);
mediaSource.addMediaSource(mediaSourceWithAds);
assertNull(timeline);
// Prepare and assert timeline contains ad groups.
prepareAndListenToTimelineUpdates(mediaSource);
waitForTimelineUpdate();
TimelineAsserts.assertAdGroupCounts(timeline, 0, 0, 1, 1);
// Create all periods and assert period creation of child media sources has been called.
TimelineAsserts.assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline,
TIMEOUT_MS);
mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(0));
mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(1));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0, 0, 0));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0));
}
private DynamicConcatenatingMediaSourceAndHandler setUpDynamicMediaSourceOnHandlerThread()
throws InterruptedException {
HandlerThread handlerThread = new HandlerThread("TestCustomCallbackExecutionThread");
@ -616,28 +649,6 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
return new FakeTimeline(new TimelineWindowDefinition(index + 1, (index + 1) * 111));
}
private static void assertAllPeriodsCanBeCreatedPreparedAndReleased(MediaSource mediaSource,
int periodCount) {
for (int i = 0; i < periodCount; i++) {
MediaPeriod mediaPeriod = mediaSource.createPeriod(new MediaPeriodId(i), null);
assertNotNull(mediaPeriod);
final ConditionVariable mediaPeriodPrepared = new ConditionVariable();
mediaPeriod.prepare(new Callback() {
@Override
public void onPrepared(MediaPeriod mediaPeriod) {
mediaPeriodPrepared.open();
}
@Override
public void onContinueLoadingRequested(MediaPeriod source) {}
}, 0);
assertTrue(mediaPeriodPrepared.block(TIMEOUT_MS));
MediaPeriod secondMediaPeriod = mediaSource.createPeriod(new MediaPeriodId(i), null);
assertNotNull(secondMediaPeriod);
mediaSource.releasePeriod(secondMediaPeriod);
mediaSource.releasePeriod(mediaPeriod);
}
}
private static class DynamicConcatenatingMediaSourceAndHandler {
public final DynamicConcatenatingMediaSource mediaSource;

View File

@ -120,8 +120,8 @@ public final class ConcatenatingMediaSource implements MediaSource {
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
int sourceIndex = timeline.getChildIndexByPeriodIndex(id.periodIndex);
MediaPeriodId periodIdInSource =
new MediaPeriodId(id.periodIndex - timeline.getFirstPeriodIndexByChildIndex(sourceIndex));
MediaPeriodId periodIdInSource = id.copyWithPeriodIndex(
id.periodIndex - timeline.getFirstPeriodIndexByChildIndex(sourceIndex));
MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIdInSource, allocator);
sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex);
return mediaPeriod;

View File

@ -341,7 +341,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
int mediaSourceHolderIndex = findMediaSourceHolderByPeriodIndex(id.periodIndex);
MediaSourceHolder holder = mediaSourceHolders.get(mediaSourceHolderIndex);
MediaPeriodId idInSource = new MediaPeriodId(id.periodIndex - holder.firstPeriodIndexInChild);
MediaPeriodId idInSource = id.copyWithPeriodIndex(
id.periodIndex - holder.firstPeriodIndexInChild);
MediaPeriod mediaPeriod;
if (!holder.isPrepared) {
mediaPeriod = new DeferredMediaPeriod(holder.mediaSource, idInSource, allocator);

View File

@ -80,7 +80,8 @@ public final class LoopingMediaSource implements MediaSource {
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
return loopCount != Integer.MAX_VALUE
? childSource.createPeriod(new MediaPeriodId(id.periodIndex % childPeriodCount), allocator)
? childSource.createPeriod(id.copyWithPeriodIndex(id.periodIndex % childPeriodCount),
allocator)
: childSource.createPeriod(id, allocator);
}

View File

@ -38,6 +38,7 @@ public class FakeMediaSource implements MediaSource {
private final Object manifest;
private final TrackGroupArray trackGroupArray;
private final ArrayList<FakeMediaPeriod> activeMediaPeriods;
private final ArrayList<MediaPeriodId> createdMediaPeriods;
private boolean preparedSource;
private boolean releasedSource;
@ -58,13 +59,10 @@ public class FakeMediaSource implements MediaSource {
this.timeline = timeline;
this.manifest = manifest;
this.activeMediaPeriods = new ArrayList<>();
this.createdMediaPeriods = new ArrayList<>();
this.trackGroupArray = trackGroupArray;
}
public void assertReleased() {
Assert.assertTrue(releasedSource);
}
@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
Assert.assertFalse(preparedSource);
@ -84,6 +82,7 @@ public class FakeMediaSource implements MediaSource {
Assert.assertFalse(releasedSource);
FakeMediaPeriod mediaPeriod = createFakeMediaPeriod(id, trackGroupArray, allocator);
activeMediaPeriods.add(mediaPeriod);
createdMediaPeriods.add(id);
return mediaPeriod;
}
@ -104,6 +103,20 @@ public class FakeMediaSource implements MediaSource {
releasedSource = true;
}
/**
* Assert that the source and all periods have been released.
*/
public void assertReleased() {
Assert.assertTrue(releasedSource);
}
/**
* Assert that a media period for the given id has been created.
*/
public void assertMediaPeriodCreated(MediaPeriodId mediaPeriodId) {
Assert.assertTrue(createdMediaPeriods.contains(mediaPeriodId));
}
protected FakeMediaPeriod createFakeMediaPeriod(MediaPeriodId id, TrackGroupArray trackGroupArray,
Allocator allocator) {
return new FakeMediaPeriod(trackGroupArray);

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
/**
* Fake {@link Timeline} which can be setup to return custom {@link TimelineWindowDefinition}s.
@ -36,6 +37,8 @@ public final class FakeTimeline extends Timeline {
public final boolean isSeekable;
public final boolean isDynamic;
public final long durationUs;
public final int adGroupsPerPeriodCount;
public final int adsPerAdGroupCount;
public TimelineWindowDefinition(int periodCount, Object id) {
this(periodCount, id, true, false, WINDOW_DURATION_US);
@ -47,15 +50,24 @@ public final class FakeTimeline extends Timeline {
public TimelineWindowDefinition(int periodCount, Object id, boolean isSeekable,
boolean isDynamic, long durationUs) {
this(periodCount, id, isSeekable, isDynamic, durationUs, 0, 0);
}
public TimelineWindowDefinition(int periodCount, Object id, boolean isSeekable,
boolean isDynamic, long durationUs, int adGroupsCountPerPeriod, int adsPerAdGroupCount) {
this.periodCount = periodCount;
this.id = id;
this.isSeekable = isSeekable;
this.isDynamic = isDynamic;
this.durationUs = durationUs;
this.adGroupsPerPeriodCount = adGroupsCountPerPeriod;
this.adsPerAdGroupCount = adsPerAdGroupCount;
}
}
private static final long AD_DURATION_US = 10 * C.MICROS_PER_SECOND;
private final TimelineWindowDefinition[] windowDefinitions;
private final int[] periodOffsets;
@ -96,7 +108,28 @@ public final class FakeTimeline extends Timeline {
Object id = setIds ? windowPeriodIndex : null;
Object uid = setIds ? periodIndex : null;
long periodDurationUs = windowDefinition.durationUs / windowDefinition.periodCount;
return period.set(id, uid, windowIndex, periodDurationUs, periodDurationUs * windowPeriodIndex);
long positionInWindowUs = periodDurationUs * windowPeriodIndex;
if (windowDefinition.adGroupsPerPeriodCount == 0) {
return period.set(id, uid, windowIndex, periodDurationUs, positionInWindowUs);
} else {
int adGroups = windowDefinition.adGroupsPerPeriodCount;
long[] adGroupTimesUs = new long[adGroups];
int[] adCounts = new int[adGroups];
int[] adLoadedAndPlayedCounts = new int[adGroups];
long[][] adDurationsUs = new long[adGroups][];
long adResumePositionUs = 0;
long adGroupOffset = adGroups > 1 ? periodDurationUs / (adGroups - 1) : 0;
for (int i = 0; i < adGroups; i++) {
adGroupTimesUs[i] = i * adGroupOffset;
adCounts[i] = windowDefinition.adsPerAdGroupCount;
adLoadedAndPlayedCounts[i] = 0;
adDurationsUs[i] = new long[adCounts[i]];
Arrays.fill(adDurationsUs[i], AD_DURATION_US);
}
return period.set(id, uid, windowIndex, periodDurationUs, positionInWindowUs, adGroupTimesUs,
adCounts, adLoadedAndPlayedCounts, adLoadedAndPlayedCounts, adDurationsUs,
adResumePositionUs);
}
}
@Override

View File

@ -16,12 +16,19 @@
package com.google.android.exoplayer2.testutil;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import android.os.ConditionVariable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.Timeline.Window;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
/**
* Unit test for {@link Timeline}.
@ -60,7 +67,7 @@ public final class TimelineAsserts {
}
/**
* Asserts that window properties {@link Window}.isDynamic are set correctly..
* Asserts that window properties {@link Window}.isDynamic are set correctly.
*/
public static void assertWindowIsDynamic(Timeline timeline, boolean... windowIsDynamic) {
Window window = new Window();
@ -139,5 +146,57 @@ public final class TimelineAsserts {
}
}
/**
* Asserts that periods' {@link Period#getAdGroupCount()} are set correctly.
*/
public static void assertAdGroupCounts(Timeline timeline, int... expectedAdGroupCounts) {
Period period = new Period();
for (int i = 0; i < timeline.getPeriodCount(); i++) {
timeline.getPeriod(i, period);
assertEquals(expectedAdGroupCounts[i], period.getAdGroupCount());
}
}
/**
* Asserts that all period (including ad periods) can be created from the source, prepared, and
* released without exception and within timeout.
*/
public static void assertAllPeriodsCanBeCreatedPreparedAndReleased(MediaSource mediaSource,
Timeline timeline, long timeoutMs) {
Period period = new Period();
for (int i = 0; i < timeline.getPeriodCount(); i++) {
assertPeriodCanBeCreatedPreparedAndReleased(mediaSource, new MediaPeriodId(i), timeoutMs);
timeline.getPeriod(i, period);
for (int adGroupIndex = 0; adGroupIndex < period.getAdGroupCount(); adGroupIndex++) {
for (int adIndex = 0; adIndex < period.getAdCountInAdGroup(adGroupIndex); adIndex++) {
assertPeriodCanBeCreatedPreparedAndReleased(mediaSource,
new MediaPeriodId(i, adGroupIndex, adIndex), timeoutMs);
}
}
}
}
private static void assertPeriodCanBeCreatedPreparedAndReleased(MediaSource mediaSource,
MediaPeriodId mediaPeriodId, long timeoutMs) {
MediaPeriod mediaPeriod = mediaSource.createPeriod(mediaPeriodId, null);
assertNotNull(mediaPeriod);
final ConditionVariable mediaPeriodPrepared = new ConditionVariable();
mediaPeriod.prepare(new Callback() {
@Override
public void onPrepared(MediaPeriod mediaPeriod) {
mediaPeriodPrepared.open();
}
@Override
public void onContinueLoadingRequested(MediaPeriod source) {}
}, /* positionUs= */ 0);
assertTrue(mediaPeriodPrepared.block(timeoutMs));
// MediaSource is supposed to support multiple calls to createPeriod with the same id without an
// intervening call to releasePeriod.
MediaPeriod secondMediaPeriod = mediaSource.createPeriod(mediaPeriodId, null);
assertNotNull(secondMediaPeriod);
mediaSource.releasePeriod(secondMediaPeriod);
mediaSource.releasePeriod(mediaPeriod);
}
}