mirror of
https://github.com/samsonjs/media.git
synced 2026-04-06 11:25:46 +00:00
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:
parent
e682f53b3c
commit
8cc3cc4e14
6 changed files with 353 additions and 219 deletions
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Reference in a new issue