Assume renderer errors are thrown for reading period.

This fixes a bug that renderer errors are currently falsely associated
with the playing period.

PiperOrigin-RevId: 321381705
This commit is contained in:
tonihei 2020-07-15 18:00:57 +01:00 committed by Oliver Woodman
parent e682f53b3c
commit 8cc3cc4e14
6 changed files with 353 additions and 219 deletions

View file

@ -99,6 +99,8 @@
parameter ([#7582](https://github.com/google/ExoPlayer/issues/7582)).
* Distinguish between `offsetUs` and `startPositionUs` when passing new
`SampleStreams` to `Renderers`.
* Fix wrong `MediaPeriodId` for some renderer errors reported by
`AnalyticsListener.onPlayerError`.
* Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices.
* Track selection:
* Add `Player.getTrackSelector`.

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer2;
import android.os.SystemClock;
import android.text.TextUtils;
import androidx.annotation.CheckResult;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.RendererCapabilities.FormatSupport;
@ -93,6 +94,12 @@ public final class ExoPlaybackException extends Exception {
/** The value of {@link SystemClock#elapsedRealtime()} when this exception was created. */
public final long timestampMs;
/**
* The {@link MediaSource.MediaPeriodId} of the media associated with this error, or null if
* undetermined.
*/
@Nullable public final MediaSource.MediaPeriodId mediaPeriodId;
@Nullable private final Throwable cause;
/**
@ -192,7 +199,7 @@ public final class ExoPlaybackException extends Exception {
int rendererIndex,
@Nullable Format rendererFormat,
@FormatSupport int rendererFormatSupport) {
super(
this(
deriveMessage(
type,
customMessage,
@ -200,14 +207,35 @@ public final class ExoPlaybackException extends Exception {
rendererIndex,
rendererFormat,
rendererFormatSupport),
cause);
cause,
type,
rendererName,
rendererIndex,
rendererFormat,
rendererFormatSupport,
/* mediaPeriodId= */ null,
/* timestampMs= */ SystemClock.elapsedRealtime());
}
private ExoPlaybackException(
@Nullable String message,
@Nullable Throwable cause,
@Type int type,
@Nullable String rendererName,
int rendererIndex,
@Nullable Format rendererFormat,
@FormatSupport int rendererFormatSupport,
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
long timestampMs) {
super(message, cause);
this.type = type;
this.cause = cause;
this.rendererName = rendererName;
this.rendererIndex = rendererIndex;
this.rendererFormat = rendererFormat;
this.rendererFormatSupport = rendererFormatSupport;
timestampMs = SystemClock.elapsedRealtime();
this.mediaPeriodId = mediaPeriodId;
this.timestampMs = timestampMs;
}
/**
@ -250,6 +278,27 @@ public final class ExoPlaybackException extends Exception {
return (OutOfMemoryError) Assertions.checkNotNull(cause);
}
/**
* Returns a copy of this exception with the provided {@link MediaSource.MediaPeriodId}.
*
* @param mediaPeriodId The {@link MediaSource.MediaPeriodId}.
* @return The copied exception.
*/
@CheckResult
/* package= */ ExoPlaybackException copyWithMediaPeriodId(
@Nullable MediaSource.MediaPeriodId mediaPeriodId) {
return new ExoPlaybackException(
getMessage(),
cause,
type,
rendererName,
rendererIndex,
rendererFormat,
rendererFormatSupport,
mediaPeriodId,
timestampMs);
}
@Nullable
private static String deriveMessage(
@Type int type,

View file

@ -528,6 +528,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
maybeNotifyPlaybackInfoChanged();
} catch (ExoPlaybackException e) {
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
@Nullable MediaPeriodHolder readingPeriod = queue.getReadingPeriod();
if (readingPeriod != null) {
// We can assume that all renderer errors happen in the context of the reading period. See
// [internal: b/150584930#comment4] for exceptions that aren't covered by this assumption.
e = e.copyWithMediaPeriodId(readingPeriod.info.id);
}
}
Log.e(TAG, "Playback error", e);
stopInternal(
/* forceResetRenderers= */ true,
@ -537,6 +545,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
maybeNotifyPlaybackInfoChanged();
} catch (IOException e) {
ExoPlaybackException error = ExoPlaybackException.createForSource(e);
@Nullable MediaPeriodHolder playingPeriod = queue.getPlayingPeriod();
if (playingPeriod != null) {
// We ensure that all IOException throwing methods are only executed for the playing period.
error = error.copyWithMediaPeriodId(playingPeriod.info.id);
}
Log.e(TAG, "Playback error", error);
stopInternal(
/* forceResetRenderers= */ false,

View file

@ -543,7 +543,10 @@ public class AnalyticsCollector
@Override
public final void onPlayerError(ExoPlaybackException error) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
EventTime eventTime =
error.mediaPeriodId != null
? generateEventTime(error.mediaPeriodId)
: generateCurrentPlayerMediaPeriodEventTime();
for (AnalyticsListener listener : listeners) {
listener.onPlayerError(eventTime, error);
}

View file

@ -3066,87 +3066,6 @@ public final class ExoPlayerTest {
.isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT);
}
@Test
public void secondMediaSourceInPlaylistOnlyThrowsWhenPreviousPeriodIsFullyRead()
throws Exception {
Timeline fakeTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ 10 * C.MICROS_PER_SECOND));
MediaSource workingMediaSource =
new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
MediaSource failingMediaSource =
new FakeMediaSource(/* timeline= */ null, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
throw new IOException();
}
};
ConcatenatingMediaSource concatenatingMediaSource =
new ConcatenatingMediaSource(workingMediaSource, failingMediaSource);
FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatingMediaSource)
.setRenderers(renderer)
.build();
try {
testRunner.start().blockUntilEnded(TIMEOUT_MS);
fail();
} catch (ExoPlaybackException e) {
// Expected exception.
}
assertThat(renderer.sampleBufferReadCount).isAtLeast(1);
assertThat(renderer.hasReadStreamToEnd()).isTrue();
}
@Test
public void
testDynamicallyAddedSecondMediaSourceInPlaylistOnlyThrowsWhenPreviousPeriodIsFullyRead()
throws Exception {
Timeline fakeTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ 10 * C.MICROS_PER_SECOND));
MediaSource workingMediaSource =
new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT);
MediaSource failingMediaSource =
new FakeMediaSource(/* timeline= */ null, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
throw new IOException();
}
};
ConcatenatingMediaSource concatenatingMediaSource =
new ConcatenatingMediaSource(workingMediaSource);
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(() -> concatenatingMediaSource.addMediaSource(failingMediaSource))
.play()
.build();
FakeRenderer renderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context)
.setMediaSources(concatenatingMediaSource)
.setActionSchedule(actionSchedule)
.setRenderers(renderer)
.build();
try {
testRunner.start().blockUntilEnded(TIMEOUT_MS);
fail();
} catch (ExoPlaybackException e) {
// Expected exception.
}
assertThat(renderer.sampleBufferReadCount).isAtLeast(1);
assertThat(renderer.hasReadStreamToEnd()).isTrue();
}
@Test
public void removingLoopingLastPeriodFromPlaylistDoesNotThrow() throws Exception {
Timeline timeline =
@ -7172,140 +7091,6 @@ public final class ExoPlayerTest {
assertArrayEquals(new int[] {1, 0}, currentWindowIndices);
}
// TODO(b/150584930): Fix reporting of renderer errors.
@Ignore
@Test
public void errorThrownDuringRendererEnableAtPeriodTransition_isReportedForNewPeriod() {
FakeMediaSource source1 =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT);
FakeMediaSource source2 =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.AUDIO_FORMAT);
FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
FakeRenderer audioRenderer =
new FakeRenderer(C.TRACK_TYPE_AUDIO) {
@Override
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
throws ExoPlaybackException {
// Fail when enabling the renderer. This will happen during the period transition.
throw createRendererException(
new IllegalStateException(), ExoPlayerTestRunner.AUDIO_FORMAT);
}
};
AtomicReference<TrackGroupArray> trackGroupsAfterError = new AtomicReference<>();
AtomicReference<TrackSelectionArray> trackSelectionsAfterError = new AtomicReference<>();
AtomicInteger windowIndexAfterError = new AtomicInteger();
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.addAnalyticsListener(
new AnalyticsListener() {
@Override
public void onPlayerError(
EventTime eventTime, ExoPlaybackException error) {
trackGroupsAfterError.set(player.getCurrentTrackGroups());
trackSelectionsAfterError.set(player.getCurrentTrackSelections());
windowIndexAfterError.set(player.getCurrentWindowIndex());
}
});
}
})
.build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context)
.setMediaSources(source1, source2)
.setActionSchedule(actionSchedule)
.setRenderers(videoRenderer, audioRenderer)
.build();
assertThrows(
ExoPlaybackException.class,
() ->
testRunner
.start(/* doPrepare= */ true)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS));
assertThat(windowIndexAfterError.get()).isEqualTo(1);
assertThat(trackGroupsAfterError.get().length).isEqualTo(1);
assertThat(trackGroupsAfterError.get().get(0).getFormat(0))
.isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT);
assertThat(trackSelectionsAfterError.get().get(0)).isNull(); // Video renderer.
assertThat(trackSelectionsAfterError.get().get(1)).isNotNull(); // Audio renderer.
}
// TODO(b/150584930): Fix reporting of renderer errors.
@Ignore
@Test
public void errorThrownDuringRendererReplaceStreamAtPeriodTransition_isReportedForNewPeriod() {
FakeMediaSource source1 =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT);
FakeMediaSource source2 =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.AUDIO_FORMAT);
FakeRenderer videoRenderer = new FakeRenderer(C.TRACK_TYPE_VIDEO);
FakeRenderer audioRenderer =
new FakeRenderer(C.TRACK_TYPE_AUDIO) {
@Override
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs)
throws ExoPlaybackException {
// Fail when changing streams. This will happen during the period transition.
throw createRendererException(
new IllegalStateException(), ExoPlayerTestRunner.AUDIO_FORMAT);
}
};
AtomicReference<TrackGroupArray> trackGroupsAfterError = new AtomicReference<>();
AtomicReference<TrackSelectionArray> trackSelectionsAfterError = new AtomicReference<>();
AtomicInteger windowIndexAfterError = new AtomicInteger();
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.addAnalyticsListener(
new AnalyticsListener() {
@Override
public void onPlayerError(
EventTime eventTime, ExoPlaybackException error) {
trackGroupsAfterError.set(player.getCurrentTrackGroups());
trackSelectionsAfterError.set(player.getCurrentTrackSelections());
windowIndexAfterError.set(player.getCurrentWindowIndex());
}
});
}
})
.build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context)
.setMediaSources(source1, source2)
.setActionSchedule(actionSchedule)
.setRenderers(videoRenderer, audioRenderer)
.build();
assertThrows(
ExoPlaybackException.class,
() ->
testRunner
.start(/* doPrepare= */ true)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS));
assertThat(windowIndexAfterError.get()).isEqualTo(1);
assertThat(trackGroupsAfterError.get().length).isEqualTo(1);
assertThat(trackGroupsAfterError.get().get(0).getFormat(0))
.isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT);
assertThat(trackSelectionsAfterError.get().get(0)).isNull(); // Video renderer.
assertThat(trackSelectionsAfterError.get().get(1)).isNotNull(); // Audio renderer.
}
@Test
public void errorThrownDuringPlaylistUpdate_keepsConsistentPlayerState() {
FakeMediaSource source1 =
@ -8218,6 +8003,174 @@ public final class ExoPlayerTest {
exoPlayerTestRunner.assertMediaItemsTransitionedSame(initialMediaItem);
}
@Test
public void
mediaSourceMaybeThrowSourceInfoRefreshError_isNotThrownUntilPlaybackReachedFailingItem()
throws Exception {
ExoPlayer player = new TestExoPlayer.Builder(context).build();
player.addMediaSource(new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)));
player.addMediaSource(
new FakeMediaSource(/* timeline= */ null) {
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
throw new IOException();
}
});
player.prepare();
player.play();
ExoPlaybackException error = TestExoPlayer.runUntilError(player);
Object period1Uid =
player
.getCurrentTimeline()
.getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true)
.uid;
assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid);
assertThat(player.getCurrentWindowIndex()).isEqualTo(1);
}
@Test
public void mediaPeriodMaybeThrowPrepareError_isNotThrownUntilPlaybackReachedFailingItem()
throws Exception {
ExoPlayer player = new TestExoPlayer.Builder(context).build();
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
player.addMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
player.addMediaSource(
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
@Nullable TransferListener transferListener) {
return new FakeMediaPeriod(
trackGroupArray,
/* singleSampleTimeUs= */ 0,
mediaSourceEventDispatcher,
DrmSessionManager.DUMMY,
drmEventDispatcher,
/* deferOnPrepared= */ true) {
@Override
public void maybeThrowPrepareError() throws IOException {
throw new IOException();
}
};
}
});
player.prepare();
player.play();
ExoPlaybackException error = TestExoPlayer.runUntilError(player);
Object period1Uid =
player
.getCurrentTimeline()
.getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true)
.uid;
assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid);
assertThat(player.getCurrentWindowIndex()).isEqualTo(1);
}
@Test
public void sampleStreamMaybeThrowError_isNotThrownUntilPlaybackReachedFailingItem()
throws Exception {
ExoPlayer player = new TestExoPlayer.Builder(context).build();
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
player.addMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT));
player.addMediaSource(
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
@Nullable TransferListener transferListener) {
return new FakeMediaPeriod(
trackGroupArray, /* singleSampleTimeUs= */ 0, mediaSourceEventDispatcher) {
@Override
protected SampleStream createSampleStream(
long positionUs,
TrackSelection selection,
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher) {
return new FakeSampleStream(
mediaSourceEventDispatcher,
DrmSessionManager.DUMMY,
drmEventDispatcher,
selection.getSelectedFormat(),
/* fakeSampleStreamItems= */ ImmutableList.of()) {
@Override
public void maybeThrowError() throws IOException {
throw new IOException();
}
};
}
};
}
});
player.prepare();
player.play();
ExoPlaybackException error = TestExoPlayer.runUntilError(player);
Object period1Uid =
player
.getCurrentTimeline()
.getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true)
.uid;
assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid);
assertThat(player.getCurrentWindowIndex()).isEqualTo(1);
}
@Test
public void rendererError_isReportedWithReadingMediaPeriodId() throws Exception {
FakeMediaSource source0 =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT);
FakeMediaSource source1 =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.AUDIO_FORMAT);
RenderersFactory renderersFactory =
(eventHandler, videoListener, audioListener, textOutput, metadataOutput) ->
new Renderer[] {
new FakeRenderer(C.TRACK_TYPE_VIDEO),
new FakeRenderer(C.TRACK_TYPE_AUDIO) {
@Override
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
throws ExoPlaybackException {
// Fail when enabling the renderer. This will happen during the period
// transition while the reading and playing period are different.
throw createRendererException(
new IllegalStateException(), ExoPlayerTestRunner.AUDIO_FORMAT);
}
}
};
ExoPlayer player =
new TestExoPlayer.Builder(context).setRenderersFactory(renderersFactory).build();
player.setMediaSources(ImmutableList.of(source0, source1));
player.prepare();
player.play();
ExoPlaybackException error = TestExoPlayer.runUntilError(player);
Object period1Uid =
player
.getCurrentTimeline()
.getPeriod(/* periodIndex= */ 1, new Timeline.Period(), /* setIds= */ true)
.uid;
assertThat(error.mediaPeriodId.periodUid).isEqualTo(period1Uid);
// Verify test setup by checking that playing period was indeed different.
assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
}
// Internal methods.
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {

View file

@ -53,6 +53,7 @@ import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
import com.google.android.exoplayer2.testutil.FakeAudioRenderer;
import com.google.android.exoplayer2.testutil.FakeExoMediaDrm;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeRenderer;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.FakeVideoRenderer;
@ -1450,6 +1451,111 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period0);
}
@Test
public void onPlayerError_thrownDuringRendererEnableAtPeriodTransition_isReportedForNewPeriod()
throws Exception {
FakeMediaSource source0 =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT);
FakeMediaSource source1 =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.AUDIO_FORMAT);
RenderersFactory renderersFactory =
(eventHandler, videoListener, audioListener, textOutput, metadataOutput) ->
new Renderer[] {
new FakeRenderer(C.TRACK_TYPE_VIDEO),
new FakeRenderer(C.TRACK_TYPE_AUDIO) {
@Override
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
throws ExoPlaybackException {
// Fail when enabling the renderer. This will happen during the period transition.
throw createRendererException(
new IllegalStateException(), ExoPlayerTestRunner.AUDIO_FORMAT);
}
}
};
TestAnalyticsListener listener =
runAnalyticsTest(
new ConcatenatingMediaSource(source0, source1),
/* actionSchedule= */ null,
renderersFactory);
populateEventIds(listener.lastReportedTimeline);
assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period1);
}
@Test
public void onPlayerError_thrownDuringRenderAtPeriodTransition_isReportedForNewPeriod()
throws Exception {
FakeMediaSource source0 =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT);
FakeMediaSource source1 =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.AUDIO_FORMAT);
RenderersFactory renderersFactory =
(eventHandler, videoListener, audioListener, textOutput, metadataOutput) ->
new Renderer[] {
new FakeRenderer(C.TRACK_TYPE_VIDEO),
new FakeRenderer(C.TRACK_TYPE_AUDIO) {
@Override
public void render(long positionUs, long realtimeUs) throws ExoPlaybackException {
// Fail when rendering the audio stream. This will happen during the period
// transition.
throw createRendererException(
new IllegalStateException(), ExoPlayerTestRunner.AUDIO_FORMAT);
}
}
};
TestAnalyticsListener listener =
runAnalyticsTest(
new ConcatenatingMediaSource(source0, source1),
/* actionSchedule= */ null,
renderersFactory);
populateEventIds(listener.lastReportedTimeline);
assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period1);
}
@Test
public void
onPlayerError_thrownDuringRendererReplaceStreamAtPeriodTransition_isReportedForNewPeriod()
throws Exception {
FakeMediaSource source =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.AUDIO_FORMAT);
RenderersFactory renderersFactory =
(eventHandler, videoListener, audioListener, textOutput, metadataOutput) ->
new Renderer[] {
new FakeRenderer(C.TRACK_TYPE_AUDIO) {
private int streamChangeCount = 0;
@Override
protected void onStreamChanged(
Format[] formats, long startPositionUs, long offsetUs)
throws ExoPlaybackException {
// Fail when changing streams for the second time. This will happen during the
// period transition (as the first time is when enabling the stream initially).
if (++streamChangeCount == 2) {
throw createRendererException(
new IllegalStateException(), ExoPlayerTestRunner.AUDIO_FORMAT);
}
}
}
};
TestAnalyticsListener listener =
runAnalyticsTest(
new ConcatenatingMediaSource(source, source),
/* actionSchedule= */ null,
renderersFactory);
populateEventIds(listener.lastReportedTimeline);
assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period1);
}
private void populateEventIds(Timeline timeline) {
period0 =
new EventWindowAndPeriodId(
@ -1508,6 +1614,14 @@ public final class AnalyticsCollectorTest {
new FakeVideoRenderer(eventHandler, videoRendererEventListener),
new FakeAudioRenderer(eventHandler, audioRendererEventListener)
};
return runAnalyticsTest(mediaSource, actionSchedule, renderersFactory);
}
private static TestAnalyticsListener runAnalyticsTest(
MediaSource mediaSource,
@Nullable ActionSchedule actionSchedule,
RenderersFactory renderersFactory)
throws Exception {
TestAnalyticsListener listener = new TestAnalyticsListener();
try {
new ExoPlayerTestRunner.Builder(ApplicationProvider.getApplicationContext())