mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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)).
|
parameter ([#7582](https://github.com/google/ExoPlayer/issues/7582)).
|
||||||
* Distinguish between `offsetUs` and `startPositionUs` when passing new
|
* Distinguish between `offsetUs` and `startPositionUs` when passing new
|
||||||
`SampleStreams` to `Renderers`.
|
`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.
|
* Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices.
|
||||||
* Track selection:
|
* Track selection:
|
||||||
* Add `Player.getTrackSelector`.
|
* Add `Player.getTrackSelector`.
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2;
|
||||||
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import androidx.annotation.CheckResult;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities.FormatSupport;
|
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. */
|
/** The value of {@link SystemClock#elapsedRealtime()} when this exception was created. */
|
||||||
public final long timestampMs;
|
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;
|
@Nullable private final Throwable cause;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -192,7 +199,7 @@ public final class ExoPlaybackException extends Exception {
|
||||||
int rendererIndex,
|
int rendererIndex,
|
||||||
@Nullable Format rendererFormat,
|
@Nullable Format rendererFormat,
|
||||||
@FormatSupport int rendererFormatSupport) {
|
@FormatSupport int rendererFormatSupport) {
|
||||||
super(
|
this(
|
||||||
deriveMessage(
|
deriveMessage(
|
||||||
type,
|
type,
|
||||||
customMessage,
|
customMessage,
|
||||||
|
|
@ -200,14 +207,35 @@ public final class ExoPlaybackException extends Exception {
|
||||||
rendererIndex,
|
rendererIndex,
|
||||||
rendererFormat,
|
rendererFormat,
|
||||||
rendererFormatSupport),
|
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.type = type;
|
||||||
this.cause = cause;
|
this.cause = cause;
|
||||||
this.rendererName = rendererName;
|
this.rendererName = rendererName;
|
||||||
this.rendererIndex = rendererIndex;
|
this.rendererIndex = rendererIndex;
|
||||||
this.rendererFormat = rendererFormat;
|
this.rendererFormat = rendererFormat;
|
||||||
this.rendererFormatSupport = rendererFormatSupport;
|
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);
|
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
|
@Nullable
|
||||||
private static String deriveMessage(
|
private static String deriveMessage(
|
||||||
@Type int type,
|
@Type int type,
|
||||||
|
|
|
||||||
|
|
@ -528,6 +528,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
}
|
}
|
||||||
maybeNotifyPlaybackInfoChanged();
|
maybeNotifyPlaybackInfoChanged();
|
||||||
} catch (ExoPlaybackException e) {
|
} 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);
|
Log.e(TAG, "Playback error", e);
|
||||||
stopInternal(
|
stopInternal(
|
||||||
/* forceResetRenderers= */ true,
|
/* forceResetRenderers= */ true,
|
||||||
|
|
@ -537,6 +545,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
maybeNotifyPlaybackInfoChanged();
|
maybeNotifyPlaybackInfoChanged();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
ExoPlaybackException error = ExoPlaybackException.createForSource(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);
|
Log.e(TAG, "Playback error", error);
|
||||||
stopInternal(
|
stopInternal(
|
||||||
/* forceResetRenderers= */ false,
|
/* forceResetRenderers= */ false,
|
||||||
|
|
|
||||||
|
|
@ -543,7 +543,10 @@ public class AnalyticsCollector
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void onPlayerError(ExoPlaybackException error) {
|
public final void onPlayerError(ExoPlaybackException error) {
|
||||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
EventTime eventTime =
|
||||||
|
error.mediaPeriodId != null
|
||||||
|
? generateEventTime(error.mediaPeriodId)
|
||||||
|
: generateCurrentPlayerMediaPeriodEventTime();
|
||||||
for (AnalyticsListener listener : listeners) {
|
for (AnalyticsListener listener : listeners) {
|
||||||
listener.onPlayerError(eventTime, error);
|
listener.onPlayerError(eventTime, error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3066,87 +3066,6 @@ public final class ExoPlayerTest {
|
||||||
.isEqualTo(ExoPlayerTestRunner.AUDIO_FORMAT);
|
.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
|
@Test
|
||||||
public void removingLoopingLastPeriodFromPlaylistDoesNotThrow() throws Exception {
|
public void removingLoopingLastPeriodFromPlaylistDoesNotThrow() throws Exception {
|
||||||
Timeline timeline =
|
Timeline timeline =
|
||||||
|
|
@ -7172,140 +7091,6 @@ public final class ExoPlayerTest {
|
||||||
assertArrayEquals(new int[] {1, 0}, currentWindowIndices);
|
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
|
@Test
|
||||||
public void errorThrownDuringPlaylistUpdate_keepsConsistentPlayerState() {
|
public void errorThrownDuringPlaylistUpdate_keepsConsistentPlayerState() {
|
||||||
FakeMediaSource source1 =
|
FakeMediaSource source1 =
|
||||||
|
|
@ -8218,6 +8003,174 @@ public final class ExoPlayerTest {
|
||||||
exoPlayerTestRunner.assertMediaItemsTransitionedSame(initialMediaItem);
|
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.
|
// Internal methods.
|
||||||
|
|
||||||
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
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.FakeAudioRenderer;
|
||||||
import com.google.android.exoplayer2.testutil.FakeExoMediaDrm;
|
import com.google.android.exoplayer2.testutil.FakeExoMediaDrm;
|
||||||
import com.google.android.exoplayer2.testutil.FakeMediaSource;
|
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;
|
||||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||||
import com.google.android.exoplayer2.testutil.FakeVideoRenderer;
|
import com.google.android.exoplayer2.testutil.FakeVideoRenderer;
|
||||||
|
|
@ -1450,6 +1451,111 @@ public final class AnalyticsCollectorTest {
|
||||||
assertThat(listener.getEvents(EVENT_PLAYER_ERROR)).containsExactly(period0);
|
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) {
|
private void populateEventIds(Timeline timeline) {
|
||||||
period0 =
|
period0 =
|
||||||
new EventWindowAndPeriodId(
|
new EventWindowAndPeriodId(
|
||||||
|
|
@ -1508,6 +1614,14 @@ public final class AnalyticsCollectorTest {
|
||||||
new FakeVideoRenderer(eventHandler, videoRendererEventListener),
|
new FakeVideoRenderer(eventHandler, videoRendererEventListener),
|
||||||
new FakeAudioRenderer(eventHandler, audioRendererEventListener)
|
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();
|
TestAnalyticsListener listener = new TestAnalyticsListener();
|
||||||
try {
|
try {
|
||||||
new ExoPlayerTestRunner.Builder(ApplicationProvider.getApplicationContext())
|
new ExoPlayerTestRunner.Builder(ApplicationProvider.getApplicationContext())
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue