Commit 676f3bfc by olly Committed by Oliver Woodman

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
parent 8455d103
...@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.testutil.FakeMediaSource; ...@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeShuffleOrder; import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; 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.TestUtil;
import com.google.android.exoplayer2.testutil.TimelineAsserts; import com.google.android.exoplayer2.testutil.TimelineAsserts;
import junit.framework.TestCase; import junit.framework.TestCase;
...@@ -32,6 +33,8 @@ import junit.framework.TestCase; ...@@ -32,6 +33,8 @@ import junit.framework.TestCase;
*/ */
public final class ConcatenatingMediaSourceTest extends TestCase { public final class ConcatenatingMediaSourceTest extends TestCase {
private static final int TIMEOUT_MS = 10000;
public void testEmptyConcatenation() { public void testEmptyConcatenation() {
for (boolean atomic : new boolean[] {false, true}) { for (boolean atomic : new boolean[] {false, true}) {
Timeline timeline = getConcatenatedTimeline(atomic); Timeline timeline = getConcatenatedTimeline(atomic);
...@@ -208,18 +211,22 @@ public final class ConcatenatingMediaSourceTest extends TestCase { ...@@ -208,18 +211,22 @@ public final class ConcatenatingMediaSourceTest extends TestCase {
ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(mediaSourceContentOnly, ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(mediaSourceContentOnly,
mediaSourceWithAds); mediaSourceWithAds);
// Prepare and assert timeline contains ad groups. MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null, TIMEOUT_MS);
Timeline timeline = TestUtil.extractTimelineFromMediaSource(mediaSource); try {
TimelineAsserts.assertAdGroupCounts(timeline, 0, 0, 1, 1); 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); // Create all periods and assert period creation of child media sources has been called.
mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(0)); testRunner.assertPrepareAndReleaseAllPeriods();
mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(1)); mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(0));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0)); mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(1));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1)); mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0, 0, 0)); mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0)); mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0, 0, 0));
mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0));
} finally {
testRunner.release();
}
} }
/** /**
......
/*
* 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 { ...@@ -146,6 +146,7 @@ public class TestUtil {
/** /**
* Extracts the timeline from a media source. * Extracts the timeline from a media source.
*/ */
// TODO: Remove this method and transition callers over to MediaSourceTestRunner.
public static Timeline extractTimelineFromMediaSource(MediaSource mediaSource) { public static Timeline extractTimelineFromMediaSource(MediaSource mediaSource) {
class TimelineListener implements Listener { class TimelineListener implements Listener {
private Timeline timeline; private Timeline timeline;
......
...@@ -16,19 +16,11 @@ ...@@ -16,19 +16,11 @@
package com.google.android.exoplayer2.testutil; package com.google.android.exoplayer2.testutil;
import static junit.framework.Assert.assertEquals; 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.C;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.Timeline.Window; 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}. * Unit test for {@link Timeline}.
...@@ -157,46 +149,4 @@ public final class TimelineAsserts { ...@@ -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);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment