diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index c72188ad2c..6b17bf1e40 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -21,11 +21,13 @@ 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.ClippingMediaSource.IllegalClippingException; import com.google.android.exoplayer2.testutil.FakeMediaSource; 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.TimelineAsserts; +import java.io.IOException; /** * Unit tests for {@link ClippingMediaSource}. @@ -40,11 +42,12 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { @Override protected void setUp() throws Exception { + super.setUp(); window = new Timeline.Window(); period = new Timeline.Period(); } - public void testNoClipping() { + public void testNoClipping() throws IOException { Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false); Timeline clippedTimeline = getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US); @@ -55,7 +58,7 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { assertEquals(TEST_PERIOD_DURATION_US, clippedTimeline.getPeriod(0, period).getDurationUs()); } - public void testClippingUnseekableWindowThrows() { + public void testClippingUnseekableWindowThrows() throws IOException { Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), false, false); // If the unseekable window isn't clipped, clipping succeeds. @@ -64,12 +67,12 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { // If the unseekable window is clipped, clipping fails. getClippedTimeline(timeline, 1, TEST_PERIOD_DURATION_US); fail("Expected clipping to fail."); - } catch (IllegalArgumentException e) { - // Expected. + } catch (IllegalClippingException e) { + assertEquals(IllegalClippingException.REASON_NOT_SEEKABLE_TO_START, e.reason); } } - public void testClippingStart() { + public void testClippingStart() throws IOException { Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false); Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, @@ -80,7 +83,7 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { clippedTimeline.getPeriod(0, period).getDurationUs()); } - public void testClippingEnd() { + public void testClippingEnd() throws IOException { Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false); Timeline clippedTimeline = getClippedTimeline(timeline, 0, @@ -91,7 +94,7 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { clippedTimeline.getPeriod(0, period).getDurationUs()); } - public void testClippingStartAndEnd() { + public void testClippingStartAndEnd() throws IOException { Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false); Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, @@ -102,7 +105,7 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { clippedTimeline.getPeriod(0, period).getDurationUs()); } - public void testWindowAndPeriodIndices() { + public void testWindowAndPeriodIndices() throws IOException { Timeline timeline = new FakeTimeline( new TimelineWindowDefinition(1, 111, true, false, TEST_PERIOD_DURATION_US)); Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, @@ -122,7 +125,8 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { /** * Wraps the specified timeline in a {@link ClippingMediaSource} and returns the clipped timeline. */ - private static Timeline getClippedTimeline(Timeline timeline, long startMs, long endMs) { + private static Timeline getClippedTimeline(Timeline timeline, long startMs, long endMs) + throws IOException { FakeMediaSource fakeMediaSource = new FakeMediaSource(timeline, null); ClippingMediaSource mediaSource = new ClippingMediaSource(fakeMediaSource, startMs, endMs); MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null); diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index 1ca32be46d..71c4b71023 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -25,6 +25,7 @@ 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.TimelineAsserts; +import java.io.IOException; import junit.framework.TestCase; /** @@ -32,7 +33,7 @@ import junit.framework.TestCase; */ public final class ConcatenatingMediaSourceTest extends TestCase { - public void testEmptyConcatenation() { + public void testEmptyConcatenation() throws IOException { for (boolean atomic : new boolean[] {false, true}) { Timeline timeline = getConcatenatedTimeline(atomic); TimelineAsserts.assertEmpty(timeline); @@ -45,7 +46,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase { } } - public void testSingleMediaSource() { + public void testSingleMediaSource() throws IOException { Timeline timeline = getConcatenatedTimeline(false, createFakeTimeline(3, 111)); TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertPeriodCounts(timeline, 3); @@ -75,7 +76,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase { } } - public void testMultipleMediaSources() { + public void testMultipleMediaSources() throws IOException { Timeline[] timelines = { createFakeTimeline(3, 111), createFakeTimeline(1, 222), createFakeTimeline(3, 333) }; Timeline timeline = getConcatenatedTimeline(false, timelines); @@ -121,7 +122,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase { } } - public void testNestedMediaSources() { + public void testNestedMediaSources() throws IOException { Timeline timeline = getConcatenatedTimeline(false, getConcatenatedTimeline(false, createFakeTimeline(1, 111), createFakeTimeline(1, 222)), getConcatenatedTimeline(true, createFakeTimeline(1, 333), createFakeTimeline(1, 444))); @@ -149,7 +150,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase { TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 3, 1); } - public void testEmptyTimelineMediaSources() { + public void testEmptyTimelineMediaSources() throws IOException { // Empty timelines in the front, back, and the middle (single and multiple in a row). Timeline[] timelines = { Timeline.EMPTY, createFakeTimeline(1, 111), Timeline.EMPTY, Timeline.EMPTY, createFakeTimeline(2, 222), Timeline.EMPTY, createFakeTimeline(3, 333), @@ -197,7 +198,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase { } } - public void testPeriodCreationWithAds() throws InterruptedException { + public void testPeriodCreationWithAds() throws IOException, InterruptedException { // Create media source with ad child source. Timeline timelineContentOnly = new FakeTimeline( new TimelineWindowDefinition(2, 111, true, false, 10 * C.MICROS_PER_SECOND)); @@ -231,7 +232,7 @@ public final class ConcatenatingMediaSourceTest extends TestCase { * the concatenated timeline. */ private static Timeline getConcatenatedTimeline(boolean isRepeatOneAtomic, - Timeline... timelines) { + Timeline... timelines) throws IOException { MediaSource[] mediaSources = new MediaSource[timelines.length]; for (int i = 0; i < timelines.length; i++) { mediaSources[i] = new FakeMediaSource(timelines[i], null); diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java index 16c9e1a17c..5fa158725d 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java @@ -30,6 +30,7 @@ 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.TimelineAsserts; +import java.io.IOException; import java.util.Arrays; import junit.framework.TestCase; import org.mockito.Mockito; @@ -55,7 +56,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { testRunner.release(); } - public void testPlaylistChangesAfterPreparation() { + public void testPlaylistChangesAfterPreparation() throws IOException { Timeline timeline = testRunner.prepareSource(); TimelineAsserts.assertEmpty(timeline); @@ -171,7 +172,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { childSources[3].assertReleased(); } - public void testPlaylistChangesBeforePreparation() { + public void testPlaylistChangesBeforePreparation() throws IOException { FakeMediaSource[] childSources = createMediaSources(4); mediaSource.addMediaSource(childSources[0]); mediaSource.addMediaSource(childSources[1]); @@ -201,7 +202,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } - public void testPlaylistWithLazyMediaSource() { + public void testPlaylistWithLazyMediaSource() throws IOException { // Create some normal (immediately preparing) sources and some lazy sources whose timeline // updates need to be triggered. FakeMediaSource[] fastSources = createMediaSources(2); @@ -290,7 +291,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } - public void testEmptyTimelineMediaSource() { + public void testEmptyTimelineMediaSource() throws IOException { Timeline timeline = testRunner.prepareSource(); TimelineAsserts.assertEmpty(timeline); @@ -426,7 +427,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { verify(runnable).run(); } - public void testCustomCallbackAfterPreparationAddSingle() { + public void testCustomCallbackAfterPreparationAddSingle() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { testRunner.prepareSource(); @@ -444,7 +445,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } - public void testCustomCallbackAfterPreparationAddMultiple() { + public void testCustomCallbackAfterPreparationAddMultiple() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { testRunner.prepareSource(); @@ -464,7 +465,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } - public void testCustomCallbackAfterPreparationAddSingleWithIndex() { + public void testCustomCallbackAfterPreparationAddSingleWithIndex() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { testRunner.prepareSource(); @@ -482,7 +483,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } - public void testCustomCallbackAfterPreparationAddMultipleWithIndex() { + public void testCustomCallbackAfterPreparationAddMultipleWithIndex() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { testRunner.prepareSource(); @@ -502,7 +503,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } - public void testCustomCallbackAfterPreparationRemove() { + public void testCustomCallbackAfterPreparationRemove() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { testRunner.prepareSource(); @@ -528,7 +529,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } - public void testCustomCallbackAfterPreparationMove() { + public void testCustomCallbackAfterPreparationMove() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { testRunner.prepareSource(); @@ -556,7 +557,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } - public void testPeriodCreationWithAds() throws InterruptedException { + public void testPeriodCreationWithAds() throws IOException, InterruptedException { // Create dynamic media source with ad child source. Timeline timelineContentOnly = new FakeTimeline( new TimelineWindowDefinition(2, 111, true, false, 10 * C.MICROS_PER_SECOND)); diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java index 6f69923ea2..7648af195c 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java @@ -23,6 +23,7 @@ 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.TimelineAsserts; +import java.io.IOException; import junit.framework.TestCase; /** @@ -39,7 +40,7 @@ public class LoopingMediaSourceTest extends TestCase { new TimelineWindowDefinition(1, 222), new TimelineWindowDefinition(1, 333)); } - public void testSingleLoop() { + public void testSingleLoop() throws IOException { Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); @@ -57,7 +58,7 @@ public class LoopingMediaSourceTest extends TestCase { } } - public void testMultiLoop() { + public void testMultiLoop() throws IOException { Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1, 1); @@ -77,7 +78,7 @@ public class LoopingMediaSourceTest extends TestCase { } } - public void testInfiniteLoop() { + public void testInfiniteLoop() throws IOException { Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); @@ -94,7 +95,7 @@ public class LoopingMediaSourceTest extends TestCase { } } - public void testEmptyTimelineLoop() { + public void testEmptyTimelineLoop() throws IOException { Timeline timeline = getLoopingTimeline(Timeline.EMPTY, 1); TimelineAsserts.assertEmpty(timeline); @@ -109,7 +110,7 @@ public class LoopingMediaSourceTest extends TestCase { * Wraps the specified timeline in a {@link LoopingMediaSource} and returns * the looping timeline. */ - private static Timeline getLoopingTimeline(Timeline timeline, int loopCount) { + private static Timeline getLoopingTimeline(Timeline timeline, int loopCount) throws IOException { FakeMediaSource fakeMediaSource = new FakeMediaSource(timeline, null); LoopingMediaSource mediaSource = new LoopingMediaSource(fakeMediaSource, loopCount); MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index 0b2ff33f30..721950f6b9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -15,20 +15,68 @@ */ package com.google.android.exoplayer2.source; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; /** * {@link MediaSource} that wraps a source and clips its timeline based on specified start/end - * positions. The wrapped source may only have a single period/window. + * positions. The wrapped source must consist of a single period that starts at the beginning of the + * corresponding window. */ public final class ClippingMediaSource implements MediaSource, MediaSource.Listener { + /** + * Thrown when a {@link ClippingMediaSource} cannot clip its wrapped source. + */ + public static final class IllegalClippingException extends IOException { + + /** + * The reason the clipping failed. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({REASON_INVALID_PERIOD_COUNT, REASON_PERIOD_OFFSET_IN_WINDOW, + REASON_NOT_SEEKABLE_TO_START, REASON_START_EXCEEDS_END}) + public @interface Reason {} + /** + * The wrapped source doesn't consist of a single period. + */ + public static final int REASON_INVALID_PERIOD_COUNT = 0; + /** + * The wrapped source period doesn't start at the beginning of the corresponding window. + */ + public static final int REASON_PERIOD_OFFSET_IN_WINDOW = 1; + /** + * The wrapped source is not seekable and a non-zero clipping start position was specified. + */ + public static final int REASON_NOT_SEEKABLE_TO_START = 2; + /** + * The wrapped source ends before the specified clipping start position. + */ + public static final int REASON_START_EXCEEDS_END = 3; + + /** + * The reason clipping failed. + */ + @Reason + public final int reason; + + /** + * @param reason The reason clipping failed. + */ + public IllegalClippingException(@Reason int reason) { + this.reason = reason; + } + + } + private final MediaSource mediaSource; private final long startUs; private final long endUs; @@ -36,6 +84,7 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste private final ArrayList mediaPeriods; private MediaSource.Listener sourceListener; + private IllegalClippingException clippingError; /** * Creates a new clipping source that wraps the specified source. @@ -88,6 +137,9 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste @Override public void maybeThrowSourceInfoRefreshError() throws IOException { + if (clippingError != null) { + throw clippingError; + } mediaSource.maybeThrowSourceInfoRefreshError(); } @@ -115,8 +167,17 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste @Override public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, Object manifest) { - sourceListener.onSourceInfoRefreshed(this, new ClippingTimeline(timeline, startUs, endUs), - manifest); + if (clippingError != null) { + return; + } + ClippingTimeline clippingTimeline; + try { + clippingTimeline = new ClippingTimeline(timeline, startUs, endUs); + } catch (IllegalClippingException e) { + clippingError = e; + return; + } + sourceListener.onSourceInfoRefreshed(this, clippingTimeline, manifest); int count = mediaPeriods.size(); for (int i = 0; i < count; i++) { mediaPeriods.get(i).setClipping(startUs, endUs); @@ -138,22 +199,30 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste * @param startUs The number of microseconds to clip from the start of {@code timeline}. * @param endUs The end position in microseconds for the clipped timeline relative to the start * of {@code timeline}, or {@link C#TIME_END_OF_SOURCE} to clip no samples from the end. + * @throws IllegalClippingException If the timeline could not be clipped. */ - public ClippingTimeline(Timeline timeline, long startUs, long endUs) { + public ClippingTimeline(Timeline timeline, long startUs, long endUs) + throws IllegalClippingException { super(timeline); - Assertions.checkArgument(timeline.getWindowCount() == 1); - Assertions.checkArgument(timeline.getPeriodCount() == 1); + if (timeline.getPeriodCount() != 1) { + throw new IllegalClippingException(IllegalClippingException.REASON_INVALID_PERIOD_COUNT); + } + if (timeline.getPeriod(0, new Period()).getPositionInWindowUs() != 0) { + throw new IllegalClippingException(IllegalClippingException.REASON_PERIOD_OFFSET_IN_WINDOW); + } Window window = timeline.getWindow(0, new Window(), false); long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : endUs; if (window.durationUs != C.TIME_UNSET) { if (resolvedEndUs > window.durationUs) { resolvedEndUs = window.durationUs; } - Assertions.checkArgument(startUs == 0 || window.isSeekable); - Assertions.checkArgument(startUs <= resolvedEndUs); + if (startUs != 0 && !window.isSeekable) { + throw new IllegalClippingException(IllegalClippingException.REASON_NOT_SEEKABLE_TO_START); + } + if (startUs > resolvedEndUs) { + throw new IllegalClippingException(IllegalClippingException.REASON_START_EXCEEDS_END); + } } - Period period = timeline.getPeriod(0, new Period()); - Assertions.checkArgument(period.getPositionInWindowUs() == 0); this.startUs = startUs; this.endUs = resolvedEndUs; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index ea0274796f..3b468d8709 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -45,11 +45,11 @@ public final class MergingMediaSource implements MediaSource { @IntDef({REASON_WINDOWS_ARE_DYNAMIC, REASON_PERIOD_COUNT_MISMATCH}) public @interface Reason {} /** - * The merge failed because one of the sources being merged has a dynamic window. + * One of the sources being merged has a dynamic window. */ public static final int REASON_WINDOWS_ARE_DYNAMIC = 0; /** - * The merge failed because the sources have different period counts. + * The sources have different period counts. */ public static final int REASON_PERIOD_COUNT_MISMATCH = 1; diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java index 235c04bef5..4f31a8b027 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java @@ -32,7 +32,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; - +import java.io.IOException; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; @@ -100,13 +100,25 @@ public class MediaSourceTestRunner { * * @return The initial {@link Timeline}. */ - public Timeline prepareSource() { + public Timeline prepareSource() throws IOException { + final IOException[] prepareError = new IOException[1]; runOnPlaybackThread(new Runnable() { @Override public void run() { mediaSource.prepareSource(player, true, mediaSourceListener); + try { + // TODO: This only catches errors that are set synchronously in prepareSource. To capture + // async errors we'll need to poll maybeThrowSourceInfoRefreshError until the first call + // to onSourceInfoRefreshed. + mediaSource.maybeThrowSourceInfoRefreshError(); + } catch (IOException e) { + prepareError[0] = e; + } } }); + if (prepareError[0] != null) { + throw prepareError[0]; + } return assertTimelineChangeBlocking(); }