mirror of
https://github.com/androidx/media.git
synced 2025-05-07 15:40:37 +08:00
Reorder renderer enabling/disabling
We currently have the following logic to update renderers during period transitions: 1. Wait for the currently reading period to finish reading all its streams. a. Advance reading period. b. Set all streams that can't be replaced to final. c. If streams can be replaced, replace them now. 2. Wait until playback position reaches the transition point a. Disable all unneeded renderers (or those that need re-enabling). b. Advance playing period. c. Enable all new renderers (i.e. all except the ones where we replaced streams directly in step 1c. This logic causes delays because steps 2a and 2c can easily happen before 2b. Doing this allows a smooth transition for cases where renderers change or where they need to be re-enabled. The new order after this change is: 1. Wait for currently reading period to finish reading. a. Advance reading period. b. Set all streams that can't be replaced to final. 2. Update reading renderers iteratively. a. If streams can be replaced, replace them asap. b. If renderes need to be disabled, do so as soon as the respective renderer ended. c. Once step b is fully finished, enable or re-enable all new renderers. 3. Wait unril playback position reaches the transition point AND all tasks in step 2 are done (i.e. all renderers are set up for the playing period). a. Advance playing period. As a nice side effect, decoder enabled and disabled events are now always reported for the reading period, which is more consistent with other renderer callbacks. PiperOrigin-RevId: 300526983
This commit is contained in:
parent
1f202f0aee
commit
4d4e2cdd2a
@ -871,9 +871,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
|
||||
if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||
for (Renderer renderer : renderers) {
|
||||
if (isRendererEnabled(renderer)) {
|
||||
renderer.maybeThrowStreamError();
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
if (isRendererEnabled(renderers[i])
|
||||
&& renderers[i].getStream() == playingPeriodHolder.sampleStreams[i]) {
|
||||
renderers[i].maybeThrowStreamError();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1042,8 +1043,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
while (queue.getPlayingPeriod() != newPlayingPeriodHolder) {
|
||||
queue.advancePlayingPeriod();
|
||||
}
|
||||
queue.removeAfter(newPlayingPeriodHolder);
|
||||
newPlayingPeriodHolder.setRendererOffset(/* rendererPositionOffsetUs= */ 0);
|
||||
enablePlayingPeriodRenderers();
|
||||
enableRenderers();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1669,6 +1671,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
maybeUpdateLoadingPeriod();
|
||||
maybeUpdateReadingPeriod();
|
||||
maybeUpdateReadingRenderers();
|
||||
maybeUpdatePlayingPeriod();
|
||||
}
|
||||
|
||||
@ -1704,7 +1707,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdateReadingPeriod() throws ExoPlaybackException {
|
||||
private void maybeUpdateReadingPeriod() {
|
||||
@Nullable MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
|
||||
if (readingPeriodHolder == null) {
|
||||
return;
|
||||
@ -1733,8 +1736,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!readingPeriodHolder.getNext().prepared) {
|
||||
// The successor is not prepared yet.
|
||||
if (!readingPeriodHolder.getNext().prepared
|
||||
&& rendererPositionUs < readingPeriodHolder.getNext().getStartPositionRendererTime()) {
|
||||
// The successor is not prepared yet and playback hasn't reached the transition point.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1742,47 +1746,77 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
readingPeriodHolder = queue.advanceReadingPeriod();
|
||||
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
|
||||
|
||||
if (readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET) {
|
||||
if (readingPeriodHolder.prepared
|
||||
&& readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET) {
|
||||
// The new period starts with a discontinuity, so the renderers will play out all data, then
|
||||
// be disabled and re-enabled when they start playing the next period.
|
||||
setAllRendererStreamsFinal();
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
Renderer renderer = renderers[i];
|
||||
boolean rendererWasEnabled = oldTrackSelectorResult.isRendererEnabled(i);
|
||||
if (rendererWasEnabled && !renderer.isCurrentStreamFinal()) {
|
||||
// The renderer is enabled and its stream is not final, so we still have a chance to replace
|
||||
// the sample streams.
|
||||
TrackSelection newSelection = newTrackSelectorResult.selections.get(i);
|
||||
boolean newRendererEnabled = newTrackSelectorResult.isRendererEnabled(i);
|
||||
boolean oldRendererEnabled = oldTrackSelectorResult.isRendererEnabled(i);
|
||||
boolean newRendererEnabled = newTrackSelectorResult.isRendererEnabled(i);
|
||||
if (oldRendererEnabled && !renderers[i].isCurrentStreamFinal()) {
|
||||
boolean isNoSampleRenderer = rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE;
|
||||
RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[i];
|
||||
RendererConfiguration newConfig = newTrackSelectorResult.rendererConfigurations[i];
|
||||
if (newRendererEnabled && newConfig.equals(oldConfig) && !isNoSampleRenderer) {
|
||||
// Replace the renderer's SampleStream so the transition to playing the next period can
|
||||
// be seamless.
|
||||
// This should be avoided for no-sample renderer, because skipping ahead for such
|
||||
// renderer doesn't have any benefit (the renderer does not consume the sample stream),
|
||||
// and it will change the provided rendererOffsetUs while the renderer is still
|
||||
// rendering from the playing media period.
|
||||
Format[] formats = getFormats(newSelection);
|
||||
renderer.replaceStream(
|
||||
formats,
|
||||
readingPeriodHolder.sampleStreams[i],
|
||||
readingPeriodHolder.getRendererOffset());
|
||||
} else {
|
||||
if (!newRendererEnabled || !newConfig.equals(oldConfig) || isNoSampleRenderer) {
|
||||
// The renderer will be disabled when transitioning to playing the next period, because
|
||||
// there's no new selection, or because a configuration change is required, or because
|
||||
// it's a no-sample renderer for which rendererOffsetUs should be updated only when
|
||||
// starting to play the next period. Mark the SampleStream as final to play out any
|
||||
// remaining data.
|
||||
renderer.setCurrentStreamFinal();
|
||||
renderers[i].setCurrentStreamFinal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdateReadingRenderers() throws ExoPlaybackException {
|
||||
@Nullable MediaPeriodHolder readingPeriod = queue.getReadingPeriod();
|
||||
if (readingPeriod == null
|
||||
|| queue.getPlayingPeriod() == readingPeriod
|
||||
|| readingPeriod.allRenderersEnabled) {
|
||||
// Not reading ahead or all renderers updated.
|
||||
return;
|
||||
}
|
||||
if (replaceStreamsOrDisableRendererForTransition()) {
|
||||
enableRenderers();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean replaceStreamsOrDisableRendererForTransition() throws ExoPlaybackException {
|
||||
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
|
||||
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
|
||||
boolean needsToWaitForRendererToEnd = false;
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
Renderer renderer = renderers[i];
|
||||
if (!isRendererEnabled(renderer)) {
|
||||
continue;
|
||||
}
|
||||
boolean rendererIsReadingOldStream =
|
||||
renderer.getStream() != readingPeriodHolder.sampleStreams[i];
|
||||
boolean rendererShouldBeEnabled = newTrackSelectorResult.isRendererEnabled(i);
|
||||
if (rendererShouldBeEnabled && !rendererIsReadingOldStream) {
|
||||
// All done.
|
||||
continue;
|
||||
}
|
||||
if (!renderer.isCurrentStreamFinal()) {
|
||||
// The renderer stream is not final, so we can replace the sample streams immediately.
|
||||
Format[] formats = getFormats(newTrackSelectorResult.selections.get(i));
|
||||
renderer.replaceStream(
|
||||
formats, readingPeriodHolder.sampleStreams[i], readingPeriodHolder.getRendererOffset());
|
||||
} else if (renderer.isEnded()) {
|
||||
// The renderer has finished playback, so we can disable it now.
|
||||
disableRenderer(renderer);
|
||||
} else {
|
||||
// We need to wait until rendering finished before disabling the renderer.
|
||||
needsToWaitForRendererToEnd = true;
|
||||
}
|
||||
}
|
||||
return !needsToWaitForRendererToEnd;
|
||||
}
|
||||
|
||||
private void maybeUpdatePlayingPeriod() throws ExoPlaybackException {
|
||||
boolean advancedPlayingPeriod = false;
|
||||
while (shouldAdvancePlayingPeriod()) {
|
||||
@ -1791,13 +1825,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
maybeNotifyPlaybackInfoChanged();
|
||||
}
|
||||
MediaPeriodHolder oldPlayingPeriodHolder = queue.getPlayingPeriod();
|
||||
if (oldPlayingPeriodHolder == queue.getReadingPeriod()) {
|
||||
// The reading period hasn't advanced yet, so we can't seamlessly replace the SampleStreams
|
||||
// anymore and need to re-enable the renderers. Set all current streams final to do that.
|
||||
setAllRendererStreamsFinal();
|
||||
}
|
||||
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
||||
disablePlayingPeriodRenderersForTransition(rendererWasEnabledFlags);
|
||||
MediaPeriodHolder newPlayingPeriodHolder = queue.advancePlayingPeriod();
|
||||
playbackInfo =
|
||||
handlePositionDiscontinuity(
|
||||
@ -1810,7 +1837,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
: Player.DISCONTINUITY_REASON_AD_INSERTION;
|
||||
playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason);
|
||||
resetPendingPauseAtEndOfPeriod();
|
||||
enableRenderers(rendererWasEnabledFlags);
|
||||
updatePlaybackPositions();
|
||||
advancedPlayingPeriod = true;
|
||||
}
|
||||
@ -1834,14 +1860,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
return false;
|
||||
}
|
||||
MediaPeriodHolder nextPlayingPeriodHolder = playingPeriodHolder.getNext();
|
||||
if (nextPlayingPeriodHolder == null) {
|
||||
return false;
|
||||
}
|
||||
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
|
||||
if (playingPeriodHolder == readingPeriodHolder && !hasReadingPeriodFinishedReading()) {
|
||||
return false;
|
||||
}
|
||||
return rendererPositionUs >= nextPlayingPeriodHolder.getStartPositionRendererTime();
|
||||
return nextPlayingPeriodHolder != null
|
||||
&& rendererPositionUs >= nextPlayingPeriodHolder.getStartPositionRendererTime()
|
||||
&& nextPlayingPeriodHolder.allRenderersEnabled;
|
||||
}
|
||||
|
||||
private boolean hasReadingPeriodFinishedReading() {
|
||||
@ -1881,7 +1902,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
if (loadingPeriodHolder == queue.getPlayingPeriod()) {
|
||||
// This is the first prepared period, so update the position and the renderers.
|
||||
resetRendererPosition(loadingPeriodHolder.info.startPositionUs);
|
||||
enablePlayingPeriodRenderers();
|
||||
enableRenderers();
|
||||
playbackInfo =
|
||||
handlePositionDiscontinuity(
|
||||
playbackInfo.periodId,
|
||||
@ -1992,32 +2013,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
trackSelectorResult);
|
||||
}
|
||||
|
||||
private void disablePlayingPeriodRenderersForTransition(boolean[] outRendererWasEnabledFlags)
|
||||
throws ExoPlaybackException {
|
||||
MediaPeriodHolder oldPlayingPeriodHolder = Assertions.checkNotNull(queue.getPlayingPeriod());
|
||||
MediaPeriodHolder newPlayingPeriodHolder =
|
||||
Assertions.checkNotNull(oldPlayingPeriodHolder.getNext());
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
Renderer renderer = renderers[i];
|
||||
outRendererWasEnabledFlags[i] = isRendererEnabled(renderer);
|
||||
if (outRendererWasEnabledFlags[i]
|
||||
&& (!newPlayingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)
|
||||
|| (renderer.isCurrentStreamFinal()
|
||||
&& renderer.getStream() == oldPlayingPeriodHolder.sampleStreams[i]))) {
|
||||
// The renderer should be disabled before playing the next period, either because it's not
|
||||
// needed to play the next period, or because we need to re-enable it as its current stream
|
||||
// is final and it's not reading ahead.
|
||||
disableRenderer(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void enablePlayingPeriodRenderers() throws ExoPlaybackException {
|
||||
private void enableRenderers() throws ExoPlaybackException {
|
||||
enableRenderers(/* rendererWasEnabledFlags= */ new boolean[renderers.length]);
|
||||
}
|
||||
|
||||
private void enableRenderers(boolean[] rendererWasEnabledFlags) throws ExoPlaybackException {
|
||||
TrackSelectorResult trackSelectorResult = queue.getPlayingPeriod().getTrackSelectorResult();
|
||||
MediaPeriodHolder readingMediaPeriod = queue.getReadingPeriod();
|
||||
TrackSelectorResult trackSelectorResult = readingMediaPeriod.getTrackSelectorResult();
|
||||
// Reset all disabled renderers before enabling any new ones. This makes sure resources released
|
||||
// by the disabled renderers will be available to renderers that are being enabled.
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
@ -2031,6 +2033,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
enableRenderer(i, rendererWasEnabledFlags[i]);
|
||||
}
|
||||
}
|
||||
readingMediaPeriod.allRenderersEnabled = true;
|
||||
}
|
||||
|
||||
private void enableRenderer(int rendererIndex, boolean wasRendererEnabled)
|
||||
@ -2039,8 +2042,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
if (isRendererEnabled(renderer)) {
|
||||
return;
|
||||
}
|
||||
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
|
||||
TrackSelectorResult trackSelectorResult = playingPeriodHolder.getTrackSelectorResult();
|
||||
MediaPeriodHolder periodHolder = queue.getReadingPeriod();
|
||||
boolean mayRenderStartOfStream = periodHolder == queue.getPlayingPeriod();
|
||||
TrackSelectorResult trackSelectorResult = periodHolder.getTrackSelectorResult();
|
||||
RendererConfiguration rendererConfiguration =
|
||||
trackSelectorResult.rendererConfigurations[rendererIndex];
|
||||
TrackSelection newSelection = trackSelectorResult.selections.get(rendererIndex);
|
||||
@ -2054,11 +2058,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
renderer.enable(
|
||||
rendererConfiguration,
|
||||
formats,
|
||||
playingPeriodHolder.sampleStreams[rendererIndex],
|
||||
periodHolder.sampleStreams[rendererIndex],
|
||||
rendererPositionUs,
|
||||
joining,
|
||||
/* mayRenderStartOfStream= */ true,
|
||||
playingPeriodHolder.getRendererOffset());
|
||||
mayRenderStartOfStream,
|
||||
periodHolder.getRendererOffset());
|
||||
mediaClock.onRendererEnabled(renderer);
|
||||
// Start the renderer if playing.
|
||||
if (playing) {
|
||||
|
@ -51,6 +51,13 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
public boolean hasEnabledTracks;
|
||||
/** {@link MediaPeriodInfo} about this media period. */
|
||||
public MediaPeriodInfo info;
|
||||
/**
|
||||
* Whether all required renderers have been enabled with the {@link #sampleStreams} for this
|
||||
* {@link #mediaPeriod}. This means either {@link Renderer#enable(RendererConfiguration, Format[],
|
||||
* SampleStream, long, boolean, boolean, long)} or {@link Renderer#replaceStream(Format[],
|
||||
* SampleStream, long)} has been called.
|
||||
*/
|
||||
public boolean allRenderersEnabled;
|
||||
|
||||
private final boolean[] mayRetainStreamFlags;
|
||||
private final RendererCapabilities[] rendererCapabilities;
|
||||
|
@ -160,8 +160,7 @@ public class AnalyticsCollector
|
||||
|
||||
@Override
|
||||
public final void onAudioEnabled(DecoderCounters counters) {
|
||||
// The renderers are only enabled after we changed the playing media period.
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDecoderEnabled(eventTime, C.TRACK_TYPE_AUDIO, counters);
|
||||
}
|
||||
@ -240,8 +239,7 @@ public class AnalyticsCollector
|
||||
|
||||
@Override
|
||||
public final void onVideoEnabled(DecoderCounters counters) {
|
||||
// The renderers are only enabled after we changed the playing media period.
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDecoderEnabled(eventTime, C.TRACK_TYPE_VIDEO, counters);
|
||||
}
|
||||
@ -725,7 +723,7 @@ public class AnalyticsCollector
|
||||
|
||||
@Nullable private MediaPeriodInfo currentPlayerMediaPeriod;
|
||||
private @MonotonicNonNull MediaPeriodInfo playingMediaPeriod;
|
||||
@Nullable private MediaPeriodInfo readingMediaPeriod;
|
||||
private @MonotonicNonNull MediaPeriodInfo readingMediaPeriod;
|
||||
private Timeline timeline;
|
||||
|
||||
public MediaPeriodQueueTracker() {
|
||||
@ -760,7 +758,7 @@ public class AnalyticsCollector
|
||||
/**
|
||||
* Returns the {@link MediaPeriodInfo} of the media period currently being read by the player.
|
||||
*
|
||||
* <p>May be null, if the player is not reading a media period.
|
||||
* <p>May be null, if the player has not started reading any media period.
|
||||
*/
|
||||
@Nullable
|
||||
public MediaPeriodInfo getReadingMediaPeriod() {
|
||||
@ -799,14 +797,16 @@ public class AnalyticsCollector
|
||||
mediaPeriodInfoQueue.set(i, newMediaPeriodInfo);
|
||||
mediaPeriodIdToInfo.put(newMediaPeriodInfo.mediaPeriodId, newMediaPeriodInfo);
|
||||
}
|
||||
if (readingMediaPeriod != null) {
|
||||
readingMediaPeriod = updateMediaPeriodInfoToNewTimeline(readingMediaPeriod, timeline);
|
||||
}
|
||||
if (!mediaPeriodInfoQueue.isEmpty()) {
|
||||
playingMediaPeriod = mediaPeriodInfoQueue.get(0);
|
||||
} else if (playingMediaPeriod != null) {
|
||||
playingMediaPeriod = updateMediaPeriodInfoToNewTimeline(playingMediaPeriod, timeline);
|
||||
}
|
||||
if (readingMediaPeriod != null) {
|
||||
readingMediaPeriod = updateMediaPeriodInfoToNewTimeline(readingMediaPeriod, timeline);
|
||||
} else if (playingMediaPeriod != null) {
|
||||
readingMediaPeriod = playingMediaPeriod;
|
||||
}
|
||||
this.timeline = timeline;
|
||||
currentPlayerMediaPeriod = findMatchingMediaPeriodInQueue(player);
|
||||
}
|
||||
@ -826,6 +826,9 @@ public class AnalyticsCollector
|
||||
if (currentPlayerMediaPeriod == null && isMatchingPlayingMediaPeriod(player)) {
|
||||
currentPlayerMediaPeriod = playingMediaPeriod;
|
||||
}
|
||||
if (mediaPeriodInfoQueue.size() == 1) {
|
||||
readingMediaPeriod = playingMediaPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -840,7 +843,10 @@ public class AnalyticsCollector
|
||||
}
|
||||
mediaPeriodInfoQueue.remove(mediaPeriodInfo);
|
||||
if (readingMediaPeriod != null && mediaPeriodId.equals(readingMediaPeriod.mediaPeriodId)) {
|
||||
readingMediaPeriod = mediaPeriodInfoQueue.isEmpty() ? null : mediaPeriodInfoQueue.get(0);
|
||||
readingMediaPeriod =
|
||||
mediaPeriodInfoQueue.isEmpty()
|
||||
? Assertions.checkNotNull(playingMediaPeriod)
|
||||
: mediaPeriodInfoQueue.get(0);
|
||||
}
|
||||
if (!mediaPeriodInfoQueue.isEmpty()) {
|
||||
playingMediaPeriod = mediaPeriodInfoQueue.get(0);
|
||||
@ -853,7 +859,12 @@ public class AnalyticsCollector
|
||||
|
||||
/** Update the queue with a change in the reading media period. */
|
||||
public void onReadingStarted(MediaPeriodId mediaPeriodId) {
|
||||
readingMediaPeriod = mediaPeriodIdToInfo.get(mediaPeriodId);
|
||||
@Nullable MediaPeriodInfo mediaPeriodInfo = mediaPeriodIdToInfo.get(mediaPeriodId);
|
||||
if (mediaPeriodInfo == null) {
|
||||
// The media period has already been removed from the queue in resetForNewPlaylist().
|
||||
return;
|
||||
}
|
||||
readingMediaPeriod = mediaPeriodInfo;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -5701,6 +5701,8 @@ 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 =
|
||||
@ -5886,17 +5888,21 @@ public final class ExoPlayerTest {
|
||||
@Test
|
||||
public void errorThrownDuringPlaylistUpdate_keepsConsistentPlayerState() {
|
||||
FakeMediaSource source1 =
|
||||
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), Builder.VIDEO_FORMAT);
|
||||
new FakeMediaSource(
|
||||
new FakeTimeline(/* windowCount= */ 1), Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT);
|
||||
FakeMediaSource source2 =
|
||||
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1), Builder.AUDIO_FORMAT);
|
||||
AtomicInteger audioRendererEnableCount = new AtomicInteger(0);
|
||||
FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
|
||||
FakeRenderer audioRenderer =
|
||||
new FakeRenderer(Builder.AUDIO_FORMAT) {
|
||||
@Override
|
||||
protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
|
||||
throws ExoPlaybackException {
|
||||
// Fail when enabling the renderer. This will happen during the playlist update.
|
||||
throw createRendererException(new IllegalStateException(), Builder.AUDIO_FORMAT);
|
||||
if (audioRendererEnableCount.incrementAndGet() == 2) {
|
||||
// Fail when enabling the renderer for the second time during the playlist update.
|
||||
throw createRendererException(new IllegalStateException(), Builder.AUDIO_FORMAT);
|
||||
}
|
||||
}
|
||||
};
|
||||
AtomicReference<Timeline> timelineAfterError = new AtomicReference<>();
|
||||
|
@ -262,8 +262,6 @@ public final class AnalyticsCollectorTest {
|
||||
WINDOW_0 /* setPlayWhenReady */,
|
||||
WINDOW_0 /* BUFFERING */,
|
||||
period0 /* READY */,
|
||||
period1 /* BUFFERING */,
|
||||
period1 /* READY */,
|
||||
period1 /* ENDED */);
|
||||
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
|
||||
.containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */);
|
||||
@ -312,6 +310,11 @@ public final class AnalyticsCollectorTest {
|
||||
ActionSchedule actionSchedule =
|
||||
new ActionSchedule.Builder(TAG)
|
||||
.pause()
|
||||
// Wait until second period has fully loaded to assert loading events without flakiness.
|
||||
.waitForIsLoading(true)
|
||||
.waitForIsLoading(false)
|
||||
.waitForIsLoading(true)
|
||||
.waitForIsLoading(false)
|
||||
.waitForPlaybackState(Player.STATE_READY)
|
||||
.seek(/* windowIndex= */ 1, /* positionMs= */ 0)
|
||||
.waitForSeekProcessed()
|
||||
@ -357,12 +360,13 @@ public final class AnalyticsCollectorTest {
|
||||
assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0, period1);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_ENABLED))
|
||||
.containsExactly(period0 /* video */, period1 /* audio */);
|
||||
.containsExactly(period0 /* video */, period1 /* audio */, period1 /* audio */);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_INIT))
|
||||
.containsExactly(period0 /* video */, period1 /* audio */);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED))
|
||||
.containsExactly(period0 /* video */, period1 /* audio */);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_DISABLED))
|
||||
.containsExactly(period0 /* video */, period0 /* audio */);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0);
|
||||
@ -402,8 +406,6 @@ public final class AnalyticsCollectorTest {
|
||||
period0 /* BUFFERING */,
|
||||
period0 /* READY */,
|
||||
period0 /* setPlayWhenReady=true */,
|
||||
period1Seq2 /* BUFFERING */,
|
||||
period1Seq2 /* READY */,
|
||||
period1Seq2 /* ENDED */);
|
||||
assertThat(listener.getEvents(EVENT_TIMELINE_CHANGED))
|
||||
.containsExactly(WINDOW_0 /* PLAYLIST_CHANGED */, period0 /* SOURCE_UPDATE */);
|
||||
@ -429,7 +431,7 @@ public final class AnalyticsCollectorTest {
|
||||
period1Seq1 /* media */,
|
||||
period1Seq2 /* media */);
|
||||
assertThat(listener.getEvents(EVENT_DOWNSTREAM_FORMAT_CHANGED))
|
||||
.containsExactly(period0, period1Seq1, period1Seq2, period1Seq2);
|
||||
.containsExactly(period0, period1Seq1, period1Seq1, period1Seq2, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_CREATED))
|
||||
.containsExactly(period0, period1Seq1, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED))
|
||||
@ -437,21 +439,22 @@ public final class AnalyticsCollectorTest {
|
||||
assertThat(listener.getEvents(EVENT_READING_STARTED))
|
||||
.containsExactly(period0, period1Seq1, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_ENABLED))
|
||||
.containsExactly(period0, period0, period1Seq2);
|
||||
.containsExactly(period0, period1, period0, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_INIT))
|
||||
.containsExactly(period0, period1Seq1, period1Seq2, period1Seq2);
|
||||
.containsExactly(period0, period1Seq1, period1Seq1, period1Seq2, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED))
|
||||
.containsExactly(period0, period1Seq1, period1Seq2, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1Seq2);
|
||||
.containsExactly(period0, period1Seq1, period1Seq1, period1Seq2, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0, period0);
|
||||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID))
|
||||
.containsExactly(period1Seq1, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES))
|
||||
.containsExactly(period0, period1Seq2, period1Seq2);
|
||||
.containsExactly(period0, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||
.containsExactly(period0, period1Seq1, period0, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
|
||||
.containsExactly(period0, period1Seq1, period0, period1Seq2);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
|
||||
.containsExactly(period0, period1Seq2, period1Seq2);
|
||||
.containsExactly(period0, period1Seq2);
|
||||
listener.assertNoMoreEvents();
|
||||
}
|
||||
|
||||
@ -749,11 +752,13 @@ public final class AnalyticsCollectorTest {
|
||||
.containsExactly(period0Seq0, period1Seq1);
|
||||
assertThat(listener.getEvents(EVENT_MEDIA_PERIOD_RELEASED)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_READING_STARTED)).containsExactly(period0Seq0, period0Seq1);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_ENABLED)).containsExactly(period0Seq0, period0Seq1);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_ENABLED))
|
||||
.containsExactly(period0Seq0, period0Seq1, period0Seq1);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_INIT)).containsExactly(period0Seq0, period0Seq1);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_FORMAT_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq1);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_DECODER_DISABLED))
|
||||
.containsExactly(period0Seq0, period0Seq0);
|
||||
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period0Seq1);
|
||||
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
|
||||
.containsExactly(period0Seq0, period0Seq1);
|
||||
|
Loading…
x
Reference in New Issue
Block a user