mirror of
https://github.com/samsonjs/media.git
synced 2026-04-08 11:45:51 +00:00
Add MediaSourceTestRunner for MediaSource tests.
- MediaSourceTestRunner aims to encapsulate some of the logic currently used in DynamicConcatenatingMediaSourceTest, so it can be re-used for testing other MediaSource implementations. - The change also fixes DynamicConcatenatingMediaSourceTest to execute calls on the correct threads, and to release handler threads at the end of each test. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=176117535
This commit is contained in:
parent
8455d10366
commit
676f3bfc49
5 changed files with 572 additions and 359 deletions
|
|
@ -23,6 +23,7 @@ import com.google.android.exoplayer2.testutil.FakeMediaSource;
|
|||
import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||
import com.google.android.exoplayer2.testutil.MediaSourceTestRunner;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import com.google.android.exoplayer2.testutil.TimelineAsserts;
|
||||
import junit.framework.TestCase;
|
||||
|
|
@ -32,6 +33,8 @@ import junit.framework.TestCase;
|
|||
*/
|
||||
public final class ConcatenatingMediaSourceTest extends TestCase {
|
||||
|
||||
private static final int TIMEOUT_MS = 10000;
|
||||
|
||||
public void testEmptyConcatenation() {
|
||||
for (boolean atomic : new boolean[] {false, true}) {
|
||||
Timeline timeline = getConcatenatedTimeline(atomic);
|
||||
|
|
@ -208,18 +211,22 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
|
|||
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);
|
||||
MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null, TIMEOUT_MS);
|
||||
try {
|
||||
Timeline timeline = testRunner.prepareSource();
|
||||
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));
|
||||
// Create all periods and assert period creation of child media sources has been called.
|
||||
testRunner.assertPrepareAndReleaseAllPeriods();
|
||||
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));
|
||||
} finally {
|
||||
testRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,19 +20,15 @@ import static org.mockito.Mockito.verify;
|
|||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Message;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod.Callback;
|
||||
import com.google.android.exoplayer2.source.MediaSource.Listener;
|
||||
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;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||
import com.google.android.exoplayer2.testutil.StubExoPlayer;
|
||||
import com.google.android.exoplayer2.testutil.MediaSourceTestRunner;
|
||||
import com.google.android.exoplayer2.testutil.TimelineAsserts;
|
||||
import java.util.Arrays;
|
||||
import junit.framework.TestCase;
|
||||
|
|
@ -45,78 +41,84 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
|
||||
private static final int TIMEOUT_MS = 10000;
|
||||
|
||||
private Timeline timeline;
|
||||
private boolean timelineUpdated;
|
||||
private boolean customRunnableCalled;
|
||||
private DynamicConcatenatingMediaSource mediaSource;
|
||||
private MediaSourceTestRunner testRunner;
|
||||
|
||||
public void testPlaylistChangesAfterPreparation() throws InterruptedException {
|
||||
timeline = null;
|
||||
FakeMediaSource[] childSources = createMediaSources(7);
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource(
|
||||
new FakeShuffleOrder(0));
|
||||
prepareAndListenToTimelineUpdates(mediaSource);
|
||||
assertNotNull(timeline);
|
||||
waitForTimelineUpdate();
|
||||
@Override
|
||||
public void setUp() {
|
||||
mediaSource = new DynamicConcatenatingMediaSource(new FakeShuffleOrder(0));
|
||||
testRunner = new MediaSourceTestRunner(mediaSource, null, TIMEOUT_MS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() {
|
||||
testRunner.release();
|
||||
}
|
||||
|
||||
public void testPlaylistChangesAfterPreparation() {
|
||||
Timeline timeline = testRunner.prepareSource();
|
||||
TimelineAsserts.assertEmpty(timeline);
|
||||
|
||||
FakeMediaSource[] childSources = createMediaSources(7);
|
||||
|
||||
// Add first source.
|
||||
mediaSource.addMediaSource(childSources[0]);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 1);
|
||||
TimelineAsserts.assertWindowIds(timeline, 111);
|
||||
|
||||
// Add at front of queue.
|
||||
mediaSource.addMediaSource(0, childSources[1]);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 2, 1);
|
||||
TimelineAsserts.assertWindowIds(timeline, 222, 111);
|
||||
|
||||
// Add at back of queue.
|
||||
mediaSource.addMediaSource(childSources[2]);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 2, 1, 3);
|
||||
TimelineAsserts.assertWindowIds(timeline, 222, 111, 333);
|
||||
|
||||
// Add in the middle.
|
||||
mediaSource.addMediaSource(1, childSources[3]);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 3);
|
||||
TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 333);
|
||||
|
||||
// Add bulk.
|
||||
mediaSource.addMediaSources(3, Arrays.asList((MediaSource) childSources[4],
|
||||
(MediaSource) childSources[5], (MediaSource) childSources[6]));
|
||||
waitForTimelineUpdate();
|
||||
mediaSource.addMediaSources(3, Arrays.<MediaSource>asList(childSources[4], childSources[5],
|
||||
childSources[6]));
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3);
|
||||
TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333);
|
||||
|
||||
// Move sources.
|
||||
mediaSource.moveMediaSource(2, 3);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 5, 1, 6, 7, 3);
|
||||
TimelineAsserts.assertWindowIds(timeline, 222, 444, 555, 111, 666, 777, 333);
|
||||
mediaSource.moveMediaSource(3, 2);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3);
|
||||
TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333);
|
||||
mediaSource.moveMediaSource(0, 6);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 4, 1, 5, 6, 7, 3, 2);
|
||||
TimelineAsserts.assertWindowIds(timeline, 444, 111, 555, 666, 777, 333, 222);
|
||||
mediaSource.moveMediaSource(6, 0);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3);
|
||||
TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333);
|
||||
|
||||
// Remove in the middle.
|
||||
mediaSource.removeMediaSource(3);
|
||||
waitForTimelineUpdate();
|
||||
testRunner.assertTimelineChangeBlocking();
|
||||
mediaSource.removeMediaSource(3);
|
||||
waitForTimelineUpdate();
|
||||
testRunner.assertTimelineChangeBlocking();
|
||||
mediaSource.removeMediaSource(3);
|
||||
waitForTimelineUpdate();
|
||||
testRunner.assertTimelineChangeBlocking();
|
||||
mediaSource.removeMediaSource(1);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 2, 1, 3);
|
||||
TimelineAsserts.assertWindowIds(timeline, 222, 111, 333);
|
||||
for (int i = 3; i <= 6; i++) {
|
||||
|
|
@ -146,35 +148,31 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
assertEquals(0, timeline.getLastWindowIndex(true));
|
||||
|
||||
// Assert all periods can be prepared.
|
||||
TimelineAsserts.assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline,
|
||||
TIMEOUT_MS);
|
||||
testRunner.assertPrepareAndReleaseAllPeriods();
|
||||
|
||||
// Remove at front of queue.
|
||||
mediaSource.removeMediaSource(0);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 1, 3);
|
||||
TimelineAsserts.assertWindowIds(timeline, 111, 333);
|
||||
childSources[1].assertReleased();
|
||||
|
||||
// Remove at back of queue.
|
||||
mediaSource.removeMediaSource(1);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 1);
|
||||
TimelineAsserts.assertWindowIds(timeline, 111);
|
||||
childSources[2].assertReleased();
|
||||
|
||||
// Remove last source.
|
||||
mediaSource.removeMediaSource(0);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertEmpty(timeline);
|
||||
childSources[3].assertReleased();
|
||||
}
|
||||
|
||||
public void testPlaylistChangesBeforePreparation() throws InterruptedException {
|
||||
timeline = null;
|
||||
public void testPlaylistChangesBeforePreparation() {
|
||||
FakeMediaSource[] childSources = createMediaSources(4);
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource(
|
||||
new FakeShuffleOrder(0));
|
||||
mediaSource.addMediaSource(childSources[0]);
|
||||
mediaSource.addMediaSource(childSources[1]);
|
||||
mediaSource.addMediaSource(0, childSources[2]);
|
||||
|
|
@ -182,11 +180,9 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
mediaSource.removeMediaSource(0);
|
||||
mediaSource.moveMediaSource(1, 0);
|
||||
mediaSource.addMediaSource(1, childSources[3]);
|
||||
assertNull(timeline);
|
||||
testRunner.assertNoTimelineChange();
|
||||
|
||||
prepareAndListenToTimelineUpdates(mediaSource);
|
||||
waitForTimelineUpdate();
|
||||
assertNotNull(timeline);
|
||||
Timeline timeline = testRunner.prepareSource();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 3, 4, 2);
|
||||
TimelineAsserts.assertWindowIds(timeline, 333, 444, 222);
|
||||
TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
|
||||
|
|
@ -198,98 +194,94 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true,
|
||||
1, 2, C.INDEX_UNSET);
|
||||
|
||||
TimelineAsserts.assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline,
|
||||
TIMEOUT_MS);
|
||||
testRunner.assertPrepareAndReleaseAllPeriods();
|
||||
mediaSource.releaseSource();
|
||||
for (int i = 1; i < 4; i++) {
|
||||
childSources[i].assertReleased();
|
||||
}
|
||||
}
|
||||
|
||||
public void testPlaylistWithLazyMediaSource() throws InterruptedException {
|
||||
timeline = null;
|
||||
|
||||
public void testPlaylistWithLazyMediaSource() {
|
||||
// Create some normal (immediately preparing) sources and some lazy sources whose timeline
|
||||
// updates need to be triggered.
|
||||
FakeMediaSource[] fastSources = createMediaSources(2);
|
||||
FakeMediaSource[] lazySources = new FakeMediaSource[4];
|
||||
final FakeMediaSource[] lazySources = new FakeMediaSource[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
lazySources[i] = new FakeMediaSource(null, null);
|
||||
}
|
||||
|
||||
// Add lazy sources and normal sources before preparation. Also remove one lazy source again
|
||||
// before preparation to check it doesn't throw or change the result.
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
mediaSource.addMediaSource(lazySources[0]);
|
||||
mediaSource.addMediaSource(0, fastSources[0]);
|
||||
mediaSource.removeMediaSource(1);
|
||||
mediaSource.addMediaSource(1, lazySources[1]);
|
||||
assertNull(timeline);
|
||||
testRunner.assertNoTimelineChange();
|
||||
|
||||
// Prepare and assert that the timeline contains all information for normal sources while having
|
||||
// placeholder information for lazy sources.
|
||||
prepareAndListenToTimelineUpdates(mediaSource);
|
||||
waitForTimelineUpdate();
|
||||
assertNotNull(timeline);
|
||||
Timeline timeline = testRunner.prepareSource();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 1, 1);
|
||||
TimelineAsserts.assertWindowIds(timeline, 111, null);
|
||||
TimelineAsserts.assertWindowIsDynamic(timeline, false, true);
|
||||
|
||||
// Trigger source info refresh for lazy source and check that the timeline now contains all
|
||||
// information for all windows.
|
||||
lazySources[1].setNewSourceInfo(createFakeTimeline(8), null);
|
||||
waitForTimelineUpdate();
|
||||
testRunner.runOnPlaybackThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
lazySources[1].setNewSourceInfo(createFakeTimeline(8), null);
|
||||
}
|
||||
});
|
||||
timeline = testRunner.assertTimelineChange();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 1, 9);
|
||||
TimelineAsserts.assertWindowIds(timeline, 111, 999);
|
||||
TimelineAsserts.assertWindowIsDynamic(timeline, false, false);
|
||||
TimelineAsserts.assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline,
|
||||
TIMEOUT_MS);
|
||||
testRunner.assertPrepareAndReleaseAllPeriods();
|
||||
|
||||
// Add further lazy and normal sources after preparation. Also remove one lazy source again to
|
||||
// check it doesn't throw or change the result.
|
||||
mediaSource.addMediaSource(1, lazySources[2]);
|
||||
waitForTimelineUpdate();
|
||||
testRunner.assertTimelineChangeBlocking();
|
||||
mediaSource.addMediaSource(2, fastSources[1]);
|
||||
waitForTimelineUpdate();
|
||||
testRunner.assertTimelineChangeBlocking();
|
||||
mediaSource.addMediaSource(0, lazySources[3]);
|
||||
waitForTimelineUpdate();
|
||||
testRunner.assertTimelineChangeBlocking();
|
||||
mediaSource.removeMediaSource(2);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 2, 9);
|
||||
TimelineAsserts.assertWindowIds(timeline, null, 111, 222, 999);
|
||||
TimelineAsserts.assertWindowIsDynamic(timeline, true, false, false, false);
|
||||
|
||||
// Create a period from an unprepared lazy media source and assert Callback.onPrepared is not
|
||||
// called yet.
|
||||
MediaPeriod lazyPeriod = mediaSource.createPeriod(new MediaPeriodId(0), null);
|
||||
assertNotNull(lazyPeriod);
|
||||
final ConditionVariable lazyPeriodPrepared = new ConditionVariable();
|
||||
lazyPeriod.prepare(new Callback() {
|
||||
@Override
|
||||
public void onPrepared(MediaPeriod mediaPeriod) {
|
||||
lazyPeriodPrepared.open();
|
||||
}
|
||||
@Override
|
||||
public void onContinueLoadingRequested(MediaPeriod source) {}
|
||||
}, 0);
|
||||
assertFalse(lazyPeriodPrepared.block(1));
|
||||
MediaPeriod lazyPeriod = testRunner.createPeriod(new MediaPeriodId(0));
|
||||
ConditionVariable preparedCondition = testRunner.preparePeriod(lazyPeriod, 0);
|
||||
assertFalse(preparedCondition.block(1));
|
||||
|
||||
// Assert that a second period can also be created and released without problems.
|
||||
MediaPeriod secondLazyPeriod = mediaSource.createPeriod(new MediaPeriodId(0), null);
|
||||
assertNotNull(secondLazyPeriod);
|
||||
mediaSource.releasePeriod(secondLazyPeriod);
|
||||
MediaPeriod secondLazyPeriod = testRunner.createPeriod(new MediaPeriodId(0));
|
||||
testRunner.releasePeriod(secondLazyPeriod);
|
||||
|
||||
// Trigger source info refresh for lazy media source. Assert that now all information is
|
||||
// available again and the previously created period now also finished preparing.
|
||||
lazySources[3].setNewSourceInfo(createFakeTimeline(7), null);
|
||||
waitForTimelineUpdate();
|
||||
testRunner.runOnPlaybackThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
lazySources[3].setNewSourceInfo(createFakeTimeline(7), null);
|
||||
}
|
||||
});
|
||||
timeline = testRunner.assertTimelineChange();
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 8, 1, 2, 9);
|
||||
TimelineAsserts.assertWindowIds(timeline, 888, 111, 222, 999);
|
||||
TimelineAsserts.assertWindowIsDynamic(timeline, false, false, false, false);
|
||||
assertTrue(lazyPeriodPrepared.block(TIMEOUT_MS));
|
||||
mediaSource.releasePeriod(lazyPeriod);
|
||||
assertTrue(preparedCondition.block(1));
|
||||
|
||||
// Release media source and assert all normal and lazy media sources are fully released as well.
|
||||
mediaSource.releaseSource();
|
||||
// Release the period and source.
|
||||
testRunner.releasePeriod(lazyPeriod);
|
||||
testRunner.releaseSource();
|
||||
|
||||
// Assert all sources were fully released.
|
||||
for (FakeMediaSource fastSource : fastSources) {
|
||||
fastSource.assertReleased();
|
||||
}
|
||||
|
|
@ -298,17 +290,12 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testEmptyTimelineMediaSource() throws InterruptedException {
|
||||
timeline = null;
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource(
|
||||
new FakeShuffleOrder(0));
|
||||
prepareAndListenToTimelineUpdates(mediaSource);
|
||||
assertNotNull(timeline);
|
||||
waitForTimelineUpdate();
|
||||
public void testEmptyTimelineMediaSource() {
|
||||
Timeline timeline = testRunner.prepareSource();
|
||||
TimelineAsserts.assertEmpty(timeline);
|
||||
|
||||
mediaSource.addMediaSource(new FakeMediaSource(Timeline.EMPTY, null));
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertEmpty(timeline);
|
||||
|
||||
mediaSource.addMediaSources(Arrays.asList(new MediaSource[] {
|
||||
|
|
@ -316,18 +303,18 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
new FakeMediaSource(Timeline.EMPTY, null), new FakeMediaSource(Timeline.EMPTY, null),
|
||||
new FakeMediaSource(Timeline.EMPTY, null), new FakeMediaSource(Timeline.EMPTY, null)
|
||||
}));
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertEmpty(timeline);
|
||||
|
||||
// Insert non-empty media source to leave empty sources at the start, the end, and the middle
|
||||
// (with single and multiple empty sources in a row).
|
||||
MediaSource[] mediaSources = createMediaSources(3);
|
||||
mediaSource.addMediaSource(1, mediaSources[0]);
|
||||
waitForTimelineUpdate();
|
||||
testRunner.assertTimelineChangeBlocking();
|
||||
mediaSource.addMediaSource(4, mediaSources[1]);
|
||||
waitForTimelineUpdate();
|
||||
testRunner.assertTimelineChangeBlocking();
|
||||
mediaSource.addMediaSource(6, mediaSources[2]);
|
||||
waitForTimelineUpdate();
|
||||
timeline = testRunner.assertTimelineChangeBlocking();
|
||||
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333);
|
||||
TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3);
|
||||
TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false,
|
||||
|
|
@ -350,12 +337,10 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
assertEquals(2, timeline.getLastWindowIndex(false));
|
||||
assertEquals(2, timeline.getFirstWindowIndex(true));
|
||||
assertEquals(0, timeline.getLastWindowIndex(true));
|
||||
TimelineAsserts.assertAllPeriodsCanBeCreatedPreparedAndReleased(mediaSource, timeline,
|
||||
TIMEOUT_MS);
|
||||
testRunner.assertPrepareAndReleaseAllPeriods();
|
||||
}
|
||||
|
||||
public void testIllegalArguments() {
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
MediaSource validSource = new FakeMediaSource(createFakeTimeline(1), null);
|
||||
|
||||
// Null sources.
|
||||
|
|
@ -394,7 +379,6 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
}
|
||||
|
||||
public void testCustomCallbackBeforePreparationAddSingle() {
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
Runnable runnable = Mockito.mock(Runnable.class);
|
||||
|
||||
mediaSource.addMediaSource(createFakeMediaSource(), runnable);
|
||||
|
|
@ -402,7 +386,6 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
}
|
||||
|
||||
public void testCustomCallbackBeforePreparationAddMultiple() {
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
Runnable runnable = Mockito.mock(Runnable.class);
|
||||
|
||||
mediaSource.addMediaSources(Arrays.asList(
|
||||
|
|
@ -411,7 +394,6 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
}
|
||||
|
||||
public void testCustomCallbackBeforePreparationAddSingleWithIndex() {
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
Runnable runnable = Mockito.mock(Runnable.class);
|
||||
|
||||
mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(), runnable);
|
||||
|
|
@ -419,134 +401,159 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
}
|
||||
|
||||
public void testCustomCallbackBeforePreparationAddMultipleWithIndex() {
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
Runnable runnable = Mockito.mock(Runnable.class);
|
||||
|
||||
mediaSource.addMediaSources(/* index */ 0, Arrays.asList(
|
||||
new MediaSource[]{createFakeMediaSource(), createFakeMediaSource()}), runnable);
|
||||
mediaSource.addMediaSources(/* index */ 0,
|
||||
Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
|
||||
runnable);
|
||||
verify(runnable).run();
|
||||
}
|
||||
|
||||
public void testCustomCallbackBeforePreparationRemove() throws InterruptedException {
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
public void testCustomCallbackBeforePreparationRemove() {
|
||||
Runnable runnable = Mockito.mock(Runnable.class);
|
||||
mediaSource.addMediaSource(createFakeMediaSource());
|
||||
|
||||
mediaSource.addMediaSource(createFakeMediaSource());
|
||||
mediaSource.removeMediaSource(/* index */ 0, runnable);
|
||||
verify(runnable).run();
|
||||
}
|
||||
|
||||
public void testCustomCallbackBeforePreparationMove() throws InterruptedException {
|
||||
DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
public void testCustomCallbackBeforePreparationMove() {
|
||||
Runnable runnable = Mockito.mock(Runnable.class);
|
||||
mediaSource.addMediaSources(Arrays.asList(
|
||||
new MediaSource[]{createFakeMediaSource(), createFakeMediaSource()}));
|
||||
|
||||
mediaSource.addMediaSources(
|
||||
Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}));
|
||||
mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0, runnable);
|
||||
verify(runnable).run();
|
||||
}
|
||||
|
||||
public void testCustomCallbackAfterPreparationAddSingle() throws InterruptedException {
|
||||
final DynamicConcatenatingMediaSourceAndHandler sourceHandlerPair =
|
||||
setUpDynamicMediaSourceOnHandlerThread();
|
||||
final Runnable runnable = createCustomRunnable();
|
||||
|
||||
sourceHandlerPair.mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sourceHandlerPair.mediaSource.addMediaSource(createFakeMediaSource(), runnable);
|
||||
}
|
||||
});
|
||||
waitForCustomRunnable();
|
||||
public void testCustomCallbackAfterPreparationAddSingle() {
|
||||
DummyMainThread dummyMainThread = new DummyMainThread();
|
||||
try {
|
||||
testRunner.prepareSource();
|
||||
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
|
||||
dummyMainThread.runOnMainThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.addMediaSource(createFakeMediaSource(), timelineGrabber);
|
||||
}
|
||||
});
|
||||
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
|
||||
assertEquals(1, timeline.getWindowCount());
|
||||
} finally {
|
||||
dummyMainThread.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void testCustomCallbackAfterPreparationAddMultiple() throws InterruptedException {
|
||||
final DynamicConcatenatingMediaSourceAndHandler sourceHandlerPair =
|
||||
setUpDynamicMediaSourceOnHandlerThread();
|
||||
final Runnable runnable = createCustomRunnable();
|
||||
|
||||
sourceHandlerPair.mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sourceHandlerPair.mediaSource.addMediaSources(Arrays.asList(
|
||||
new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}), runnable);
|
||||
}
|
||||
});
|
||||
waitForCustomRunnable();
|
||||
public void testCustomCallbackAfterPreparationAddMultiple() {
|
||||
DummyMainThread dummyMainThread = new DummyMainThread();
|
||||
try {
|
||||
testRunner.prepareSource();
|
||||
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
|
||||
dummyMainThread.runOnMainThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.addMediaSources(
|
||||
Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
|
||||
timelineGrabber);
|
||||
}
|
||||
});
|
||||
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
|
||||
assertEquals(2, timeline.getWindowCount());
|
||||
} finally {
|
||||
dummyMainThread.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void testCustomCallbackAfterPreparationAddSingleWithIndex() throws InterruptedException {
|
||||
final DynamicConcatenatingMediaSourceAndHandler sourceHandlerPair =
|
||||
setUpDynamicMediaSourceOnHandlerThread();
|
||||
final Runnable runnable = createCustomRunnable();
|
||||
|
||||
sourceHandlerPair.mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sourceHandlerPair.mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(),
|
||||
runnable);
|
||||
}
|
||||
});
|
||||
waitForCustomRunnable();
|
||||
public void testCustomCallbackAfterPreparationAddSingleWithIndex() {
|
||||
DummyMainThread dummyMainThread = new DummyMainThread();
|
||||
try {
|
||||
testRunner.prepareSource();
|
||||
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
|
||||
dummyMainThread.runOnMainThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(), timelineGrabber);
|
||||
}
|
||||
});
|
||||
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
|
||||
assertEquals(1, timeline.getWindowCount());
|
||||
} finally {
|
||||
dummyMainThread.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void testCustomCallbackAfterPreparationAddMultipleWithIndex() throws InterruptedException {
|
||||
final DynamicConcatenatingMediaSourceAndHandler sourceHandlerPair =
|
||||
setUpDynamicMediaSourceOnHandlerThread();
|
||||
final Runnable runnable = createCustomRunnable();
|
||||
|
||||
sourceHandlerPair.mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sourceHandlerPair.mediaSource.addMediaSources(/* index */ 0, Arrays.asList(
|
||||
new MediaSource[]{createFakeMediaSource(), createFakeMediaSource()}), runnable);
|
||||
}
|
||||
});
|
||||
waitForCustomRunnable();
|
||||
public void testCustomCallbackAfterPreparationAddMultipleWithIndex() {
|
||||
DummyMainThread dummyMainThread = new DummyMainThread();
|
||||
try {
|
||||
testRunner.prepareSource();
|
||||
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
|
||||
dummyMainThread.runOnMainThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.addMediaSources(/* index */ 0,
|
||||
Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
|
||||
timelineGrabber);
|
||||
}
|
||||
});
|
||||
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
|
||||
assertEquals(2, timeline.getWindowCount());
|
||||
} finally {
|
||||
dummyMainThread.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void testCustomCallbackAfterPreparationRemove() throws InterruptedException {
|
||||
final DynamicConcatenatingMediaSourceAndHandler sourceHandlerPair =
|
||||
setUpDynamicMediaSourceOnHandlerThread();
|
||||
final Runnable runnable = createCustomRunnable();
|
||||
sourceHandlerPair.mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sourceHandlerPair.mediaSource.addMediaSource(createFakeMediaSource());
|
||||
}
|
||||
});
|
||||
waitForTimelineUpdate();
|
||||
public void testCustomCallbackAfterPreparationRemove() {
|
||||
DummyMainThread dummyMainThread = new DummyMainThread();
|
||||
try {
|
||||
testRunner.prepareSource();
|
||||
dummyMainThread.runOnMainThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.addMediaSource(createFakeMediaSource());
|
||||
}
|
||||
});
|
||||
testRunner.assertTimelineChangeBlocking();
|
||||
|
||||
sourceHandlerPair.mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sourceHandlerPair.mediaSource.removeMediaSource(/* index */ 0, runnable);
|
||||
}
|
||||
});
|
||||
waitForCustomRunnable();
|
||||
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
|
||||
dummyMainThread.runOnMainThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.removeMediaSource(/* index */ 0, timelineGrabber);
|
||||
}
|
||||
});
|
||||
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
|
||||
assertEquals(0, timeline.getWindowCount());
|
||||
} finally {
|
||||
dummyMainThread.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void testCustomCallbackAfterPreparationMove() throws InterruptedException {
|
||||
final DynamicConcatenatingMediaSourceAndHandler sourceHandlerPair =
|
||||
setUpDynamicMediaSourceOnHandlerThread();
|
||||
final Runnable runnable = createCustomRunnable();
|
||||
sourceHandlerPair.mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sourceHandlerPair.mediaSource.addMediaSources(Arrays.asList(
|
||||
new MediaSource[]{createFakeMediaSource(), createFakeMediaSource()}));
|
||||
}
|
||||
});
|
||||
waitForTimelineUpdate();
|
||||
public void testCustomCallbackAfterPreparationMove() {
|
||||
DummyMainThread dummyMainThread = new DummyMainThread();
|
||||
try {
|
||||
testRunner.prepareSource();
|
||||
dummyMainThread.runOnMainThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.addMediaSources(Arrays.asList(
|
||||
new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}));
|
||||
}
|
||||
});
|
||||
testRunner.assertTimelineChangeBlocking();
|
||||
|
||||
sourceHandlerPair.mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sourceHandlerPair.mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0,
|
||||
runnable);
|
||||
}
|
||||
});
|
||||
waitForCustomRunnable();
|
||||
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
|
||||
dummyMainThread.runOnMainThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0,
|
||||
timelineGrabber);
|
||||
}
|
||||
});
|
||||
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
|
||||
assertEquals(2, timeline.getWindowCount());
|
||||
} finally {
|
||||
dummyMainThread.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void testPeriodCreationWithAds() throws InterruptedException {
|
||||
|
|
@ -557,19 +564,16 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
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();
|
||||
Timeline timeline = testRunner.prepareSource();
|
||||
|
||||
// Assert the timeline contains ad groups.
|
||||
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);
|
||||
testRunner.assertPrepareAndReleaseAllPeriods();
|
||||
mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(0));
|
||||
mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(1));
|
||||
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0));
|
||||
|
|
@ -578,66 +582,6 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0));
|
||||
}
|
||||
|
||||
private DynamicConcatenatingMediaSourceAndHandler setUpDynamicMediaSourceOnHandlerThread()
|
||||
throws InterruptedException {
|
||||
final DynamicConcatenatingMediaSource mediaSource = new DynamicConcatenatingMediaSource();
|
||||
prepareAndListenToTimelineUpdates(mediaSource);
|
||||
waitForTimelineUpdate();
|
||||
HandlerThread handlerThread = new HandlerThread("TestCustomCallbackExecutionThread");
|
||||
handlerThread.start();
|
||||
Handler handler = new Handler(handlerThread.getLooper());
|
||||
return new DynamicConcatenatingMediaSourceAndHandler(mediaSource, handler);
|
||||
}
|
||||
|
||||
private void prepareAndListenToTimelineUpdates(MediaSource mediaSource) {
|
||||
mediaSource.prepareSource(new MessageHandlingExoPlayer(), true, new Listener() {
|
||||
@Override
|
||||
public void onSourceInfoRefreshed(MediaSource source, Timeline newTimeline, Object manifest) {
|
||||
timeline = newTimeline;
|
||||
synchronized (DynamicConcatenatingMediaSourceTest.this) {
|
||||
timelineUpdated = true;
|
||||
DynamicConcatenatingMediaSourceTest.this.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void waitForTimelineUpdate() throws InterruptedException {
|
||||
long deadlineMs = System.currentTimeMillis() + TIMEOUT_MS;
|
||||
while (!timelineUpdated) {
|
||||
wait(TIMEOUT_MS);
|
||||
if (System.currentTimeMillis() >= deadlineMs) {
|
||||
fail("No timeline update occurred within timeout.");
|
||||
}
|
||||
}
|
||||
timelineUpdated = false;
|
||||
}
|
||||
|
||||
private Runnable createCustomRunnable() {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (DynamicConcatenatingMediaSourceTest.this) {
|
||||
assertTrue(timelineUpdated);
|
||||
timelineUpdated = false;
|
||||
customRunnableCalled = true;
|
||||
DynamicConcatenatingMediaSourceTest.this.notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private synchronized void waitForCustomRunnable() throws InterruptedException {
|
||||
long deadlineMs = System.currentTimeMillis() + TIMEOUT_MS;
|
||||
while (!customRunnableCalled) {
|
||||
wait(TIMEOUT_MS);
|
||||
if (System.currentTimeMillis() >= deadlineMs) {
|
||||
fail("No custom runnable call occurred within timeout.");
|
||||
}
|
||||
}
|
||||
customRunnableCalled = false;
|
||||
}
|
||||
|
||||
private static FakeMediaSource[] createMediaSources(int count) {
|
||||
FakeMediaSource[] sources = new FakeMediaSource[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
|
@ -654,48 +598,69 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
|
|||
return new FakeTimeline(new TimelineWindowDefinition(index + 1, (index + 1) * 111));
|
||||
}
|
||||
|
||||
private static class DynamicConcatenatingMediaSourceAndHandler {
|
||||
private static final class DummyMainThread {
|
||||
|
||||
public final DynamicConcatenatingMediaSource mediaSource;
|
||||
public final Handler mainHandler;
|
||||
private final HandlerThread thread;
|
||||
private final Handler handler;
|
||||
|
||||
public DynamicConcatenatingMediaSourceAndHandler(DynamicConcatenatingMediaSource mediaSource,
|
||||
Handler mainHandler) {
|
||||
this.mediaSource = mediaSource;
|
||||
this.mainHandler = mainHandler;
|
||||
private DummyMainThread() {
|
||||
thread = new HandlerThread("DummyMainThread");
|
||||
thread.start();
|
||||
handler = new Handler(thread.getLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the provided {@link Runnable} on the main thread, blocking until execution completes.
|
||||
*
|
||||
* @param runnable The {@link Runnable} to run.
|
||||
*/
|
||||
public void runOnMainThread(final Runnable runnable) {
|
||||
final ConditionVariable finishedCondition = new ConditionVariable();
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runnable.run();
|
||||
finishedCondition.open();
|
||||
}
|
||||
});
|
||||
assertTrue(finishedCondition.block(TIMEOUT_MS));
|
||||
}
|
||||
|
||||
public void release() {
|
||||
thread.quit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* ExoPlayer that only accepts custom messages and runs them on a separate handler thread.
|
||||
*/
|
||||
private static class MessageHandlingExoPlayer extends StubExoPlayer implements Handler.Callback {
|
||||
private static final class TimelineGrabber implements Runnable {
|
||||
|
||||
private final Handler handler;
|
||||
private final MediaSourceTestRunner testRunner;
|
||||
private final ConditionVariable finishedCondition;
|
||||
|
||||
public MessageHandlingExoPlayer() {
|
||||
HandlerThread handlerThread = new HandlerThread("StubExoPlayerThread");
|
||||
handlerThread.start();
|
||||
handler = new Handler(handlerThread.getLooper(), this);
|
||||
private Timeline timeline;
|
||||
private AssertionError error;
|
||||
|
||||
public TimelineGrabber(MediaSourceTestRunner testRunner) {
|
||||
this.testRunner = testRunner;
|
||||
finishedCondition = new ConditionVariable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessages(ExoPlayerMessage... messages) {
|
||||
handler.obtainMessage(0, messages).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
ExoPlayerMessage[] messages = (ExoPlayerMessage[]) msg.obj;
|
||||
for (ExoPlayerMessage message : messages) {
|
||||
try {
|
||||
message.target.handleMessage(message.messageType, message.message);
|
||||
} catch (ExoPlaybackException e) {
|
||||
fail("Unexpected ExoPlaybackException.");
|
||||
}
|
||||
public void run() {
|
||||
try {
|
||||
timeline = testRunner.assertTimelineChange();
|
||||
} catch (AssertionError e) {
|
||||
error = e;
|
||||
}
|
||||
return true;
|
||||
finishedCondition.open();
|
||||
}
|
||||
|
||||
public Timeline assertTimelineChangeBlocking() {
|
||||
assertTrue(finishedCondition.block(TIMEOUT_MS));
|
||||
if (error != null) {
|
||||
throw error;
|
||||
}
|
||||
return timeline;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* Copyright (C) 2017 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.testutil;
|
||||
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A runner for {@link MediaSource} tests.
|
||||
*/
|
||||
public class MediaSourceTestRunner {
|
||||
|
||||
private final long timeoutMs;
|
||||
private final StubExoPlayer player;
|
||||
private final MediaSource mediaSource;
|
||||
private final MediaSourceListener mediaSourceListener;
|
||||
private final HandlerThread playbackThread;
|
||||
private final Handler playbackHandler;
|
||||
private final Allocator allocator;
|
||||
|
||||
private final LinkedBlockingDeque<Timeline> timelines;
|
||||
private Timeline timeline;
|
||||
|
||||
/**
|
||||
* @param mediaSource The source under test.
|
||||
* @param allocator The allocator to use during the test run.
|
||||
* @param timeoutMs The timeout for operations in milliseconds.
|
||||
*/
|
||||
public MediaSourceTestRunner(MediaSource mediaSource, Allocator allocator, long timeoutMs) {
|
||||
this.mediaSource = mediaSource;
|
||||
this.allocator = allocator;
|
||||
this.timeoutMs = timeoutMs;
|
||||
playbackThread = new HandlerThread("PlaybackThread");
|
||||
playbackThread.start();
|
||||
Looper playbackLooper = playbackThread.getLooper();
|
||||
playbackHandler = new Handler(playbackLooper);
|
||||
player = new EventHandlingExoPlayer(playbackLooper);
|
||||
mediaSourceListener = new MediaSourceListener();
|
||||
timelines = new LinkedBlockingDeque<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the provided {@link Runnable} on the playback thread, blocking until execution completes.
|
||||
*
|
||||
* @param runnable The {@link Runnable} to run.
|
||||
*/
|
||||
public void runOnPlaybackThread(final Runnable runnable) {
|
||||
final ConditionVariable finishedCondition = new ConditionVariable();
|
||||
playbackHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runnable.run();
|
||||
finishedCondition.open();
|
||||
}
|
||||
});
|
||||
assertTrue(finishedCondition.block(timeoutMs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the source on the playback thread, asserting that it provides an initial timeline.
|
||||
*
|
||||
* @return The initial {@link Timeline}.
|
||||
*/
|
||||
public Timeline prepareSource() {
|
||||
runOnPlaybackThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.prepareSource(player, true, mediaSourceListener);
|
||||
}
|
||||
});
|
||||
return assertTimelineChangeBlocking();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link MediaSource#createPeriod(MediaSource.MediaPeriodId, Allocator)} on the playback
|
||||
* thread, asserting that a non-null {@link MediaPeriod} is returned.
|
||||
*
|
||||
* @param periodId The id of the period to create.
|
||||
* @return The created {@link MediaPeriod}.
|
||||
*/
|
||||
public MediaPeriod createPeriod(final MediaPeriodId periodId) {
|
||||
final MediaPeriod[] holder = new MediaPeriod[1];
|
||||
runOnPlaybackThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
holder[0] = mediaSource.createPeriod(periodId, allocator);
|
||||
}
|
||||
});
|
||||
assertNotNull(holder[0]);
|
||||
return holder[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link MediaPeriod#prepare(MediaPeriod.Callback, long)} on the playback thread.
|
||||
*
|
||||
* @param mediaPeriod The {@link MediaPeriod} to prepare.
|
||||
* @param positionUs The position at which to prepare.
|
||||
* @return A {@link ConditionVariable} that will be opened when preparation completes.
|
||||
*/
|
||||
public ConditionVariable preparePeriod(final MediaPeriod mediaPeriod, final long positionUs) {
|
||||
final ConditionVariable preparedCondition = new ConditionVariable();
|
||||
runOnPlaybackThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaPeriod.prepare(new MediaPeriod.Callback() {
|
||||
@Override
|
||||
public void onPrepared(MediaPeriod mediaPeriod) {
|
||||
preparedCondition.open();
|
||||
}
|
||||
@Override
|
||||
public void onContinueLoadingRequested(MediaPeriod source) {
|
||||
// Do nothing.
|
||||
}
|
||||
}, positionUs);
|
||||
}
|
||||
});
|
||||
return preparedCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link MediaSource#releasePeriod(MediaPeriod)} on the playback thread.
|
||||
*
|
||||
* @param mediaPeriod The {@link MediaPeriod} to release.
|
||||
*/
|
||||
public void releasePeriod(final MediaPeriod mediaPeriod) {
|
||||
runOnPlaybackThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.releasePeriod(mediaPeriod);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link MediaSource#releaseSource()} on the playback thread.
|
||||
*/
|
||||
public void releaseSource() {
|
||||
runOnPlaybackThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.releaseSource();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the source has not notified its listener of a timeline change since the last call
|
||||
* to {@link #assertTimelineChangeBlocking()} or {@link #assertTimelineChange()} (or since the
|
||||
* runner was created if neither method has been called).
|
||||
*/
|
||||
public void assertNoTimelineChange() {
|
||||
assertTrue(timelines.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the source has notified its listener of a single timeline change.
|
||||
*
|
||||
* @return The new {@link Timeline}.
|
||||
*/
|
||||
public Timeline assertTimelineChange() {
|
||||
timeline = timelines.removeFirst();
|
||||
assertNoTimelineChange();
|
||||
return timeline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the source notifies its listener of a single timeline change. If the source has
|
||||
* not yet notified its listener, it has up to the timeout passed to the constructor to do so.
|
||||
*
|
||||
* @return The new {@link Timeline}.
|
||||
*/
|
||||
public Timeline assertTimelineChangeBlocking() {
|
||||
try {
|
||||
timeline = timelines.poll(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
assertNotNull(timeline); // Null indicates the poll timed out.
|
||||
assertNoTimelineChange();
|
||||
return timeline;
|
||||
} catch (InterruptedException e) {
|
||||
// Should never happen.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and releases all periods (including ad periods) defined in the last timeline to be
|
||||
* returned from {@link #prepareSource()}, {@link #assertTimelineChange()} or
|
||||
* {@link #assertTimelineChangeBlocking()}.
|
||||
*/
|
||||
public void assertPrepareAndReleaseAllPeriods() {
|
||||
Timeline.Period period = new Timeline.Period();
|
||||
for (int i = 0; i < timeline.getPeriodCount(); i++) {
|
||||
assertPrepareAndReleasePeriod(new MediaPeriodId(i));
|
||||
timeline.getPeriod(i, period);
|
||||
for (int adGroupIndex = 0; adGroupIndex < period.getAdGroupCount(); adGroupIndex++) {
|
||||
for (int adIndex = 0; adIndex < period.getAdCountInAdGroup(adGroupIndex); adIndex++) {
|
||||
assertPrepareAndReleasePeriod(new MediaPeriodId(i, adGroupIndex, adIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertPrepareAndReleasePeriod(MediaPeriodId mediaPeriodId) {
|
||||
MediaPeriod mediaPeriod = createPeriod(mediaPeriodId);
|
||||
ConditionVariable preparedCondition = preparePeriod(mediaPeriod, 0);
|
||||
assertTrue(preparedCondition.block(timeoutMs));
|
||||
// MediaSource is supposed to support multiple calls to createPeriod with the same id without an
|
||||
// intervening call to releasePeriod.
|
||||
MediaPeriod secondMediaPeriod = createPeriod(mediaPeriodId);
|
||||
ConditionVariable secondPreparedCondition = preparePeriod(secondMediaPeriod, 0);
|
||||
assertTrue(secondPreparedCondition.block(timeoutMs));
|
||||
// Release the periods.
|
||||
releasePeriod(mediaPeriod);
|
||||
releasePeriod(secondMediaPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the runner. Should be called when the runner is no longer required.
|
||||
*/
|
||||
public void release() {
|
||||
playbackThread.quit();
|
||||
}
|
||||
|
||||
private class MediaSourceListener implements MediaSource.Listener {
|
||||
|
||||
@Override
|
||||
public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, Object manifest) {
|
||||
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
|
||||
timelines.addLast(timeline);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class EventHandlingExoPlayer extends StubExoPlayer implements Handler.Callback {
|
||||
|
||||
private final Handler handler;
|
||||
|
||||
public EventHandlingExoPlayer(Looper looper) {
|
||||
this.handler = new Handler(looper, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessages(ExoPlayerMessage... messages) {
|
||||
handler.obtainMessage(0, messages).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
ExoPlayerMessage[] messages = (ExoPlayerMessage[]) msg.obj;
|
||||
for (ExoPlayerMessage message : messages) {
|
||||
try {
|
||||
message.target.handleMessage(message.messageType, message.message);
|
||||
} catch (ExoPlaybackException e) {
|
||||
fail("Unexpected ExoPlaybackException.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -146,6 +146,7 @@ public class TestUtil {
|
|||
/**
|
||||
* Extracts the timeline from a media source.
|
||||
*/
|
||||
// TODO: Remove this method and transition callers over to MediaSourceTestRunner.
|
||||
public static Timeline extractTimelineFromMediaSource(MediaSource mediaSource) {
|
||||
class TimelineListener implements Listener {
|
||||
private Timeline timeline;
|
||||
|
|
|
|||
|
|
@ -16,19 +16,11 @@
|
|||
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}.
|
||||
|
|
@ -157,46 +149,4 @@ public final class TimelineAsserts {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue