diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b1fa2d50fb..93cb82b3b8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,6 +24,9 @@ functionality to platform `MediaExtractor`. * Move `BasePreloadManager.Listener` to a top level `PreloadManagerListener`. + * `RenderersFactory.createSecondaryRenderer` can be implemented to provide + secondary renderers for pre-warming. Pre-warming enables quicker media + item transitions during playback. * Transformer: * Update parameters of `VideoFrameProcessor.registerInputStream` and `VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`. diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java index cc1f196eab..8441e0cd55 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java @@ -1481,6 +1481,19 @@ public interface ExoPlayer extends Player { @UnstableApi Renderer getRenderer(int index); + /** + * Returns the secondary renderer at the given index. + * + * @param index The index of the secondary renderer. + * @return The secondary renderer at this index, or null if there is no secondary renderer at this + * index. + */ + @UnstableApi + @Nullable + default Renderer getSecondaryRenderer(int index) { + return null; + } + /** * Returns the track selector that this player uses, or null if track selection is not supported. */ diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java index 558777d075..e070c47484 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -87,6 +87,7 @@ import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.Log; +import androidx.media3.common.util.NullableType; import androidx.media3.common.util.Size; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.PlayerMessage.Target; @@ -153,6 +154,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private final Context applicationContext; private final Player wrappingPlayer; private final Renderer[] renderers; + private final @NullableType Renderer[] secondaryRenderers; private final TrackSelector trackSelector; private final HandlerWrapper playbackInfoUpdateHandler; private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener; @@ -263,17 +265,27 @@ import java.util.concurrent.CopyOnWriteArraySet; componentListener = new ComponentListener(); frameMetadataListener = new FrameMetadataListener(); Handler eventHandler = new Handler(builder.looper); + RenderersFactory renderersFactory = builder.renderersFactorySupplier.get(); renderers = - builder - .renderersFactorySupplier - .get() - .createRenderers( - eventHandler, - componentListener, - componentListener, - componentListener, - componentListener); + renderersFactory.createRenderers( + eventHandler, + componentListener, + componentListener, + componentListener, + componentListener); checkState(renderers.length > 0); + secondaryRenderers = new Renderer[renderers.length]; + for (int i = 0; i < secondaryRenderers.length; i++) { + // TODO(b/377671489): Fix DefaultAnalyticsCollector logic to still work with pre-warming. + secondaryRenderers[i] = + renderersFactory.createSecondaryRenderer( + renderers[i], + eventHandler, + componentListener, + componentListener, + componentListener, + componentListener); + } this.trackSelector = builder.trackSelectorSupplier.get(); this.mediaSourceFactory = builder.mediaSourceFactorySupplier.get(); this.bandwidthMeter = builder.bandwidthMeterSupplier.get(); @@ -357,6 +369,7 @@ import java.util.concurrent.CopyOnWriteArraySet; internalPlayer = new ExoPlayerImplInternal( renderers, + secondaryRenderers, trackSelector, emptyTrackSelectorResult, builder.loadControlSupplier.get(), @@ -1225,6 +1238,13 @@ import java.util.concurrent.CopyOnWriteArraySet; return renderers[index]; } + @Override + @Nullable + public Renderer getSecondaryRenderer(int index) { + verifyApplicationThread(); + return secondaryRenderers[index]; + } + @Override public TrackSelector getTrackSelector() { verifyApplicationThread(); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java index 24d5bd8745..5b53e54602 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -19,6 +19,9 @@ import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.common.util.Util.msToUs; +import static androidx.media3.exoplayer.MediaPeriodQueue.REMOVE_AFTER_REMOVED_READING_PERIOD; +import static androidx.media3.exoplayer.RendererHolder.REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED; +import static androidx.media3.exoplayer.RendererHolder.REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING; import static androidx.media3.exoplayer.audio.AudioSink.OFFLOAD_MODE_DISABLED; import static java.lang.Math.max; import static java.lang.Math.min; @@ -35,6 +38,7 @@ import androidx.media3.common.Format; import androidx.media3.common.IllegalSeekPositionException; import androidx.media3.common.MediaItem; import androidx.media3.common.Metadata; +import androidx.media3.common.MimeTypes; import androidx.media3.common.ParserException; import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException.ErrorCode; @@ -200,6 +204,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private final boolean dynamicSchedulingEnabled; private final AnalyticsCollector analyticsCollector; private final HandlerWrapper applicationLooperHandler; + private final boolean hasSecondaryRenderers; @SuppressWarnings("unused") private SeekParameters seekParameters; @@ -228,9 +233,11 @@ import java.util.concurrent.atomic.AtomicBoolean; private long playbackMaybeBecameStuckAtMs; private PreloadConfiguration preloadConfiguration; private Timeline lastPreloadPoolInvalidationTimeline; + private long prewarmingMediaPeriodDiscontinuity = C.TIME_UNSET; public ExoPlayerImplInternal( Renderer[] renderers, + Renderer[] secondaryRenderers, TrackSelector trackSelector, TrackSelectorResult emptyTrackSelectorResult, LoadControl loadControl, @@ -281,6 +288,7 @@ import java.util.concurrent.atomic.AtomicBoolean; RendererCapabilities.Listener rendererCapabilitiesListener = trackSelector.getRendererCapabilitiesListener(); + boolean hasSecondaryRenderers = false; this.renderers = new RendererHolder[renderers.length]; for (int i = 0; i < renderers.length; i++) { renderers[i].init(/* index= */ i, playerId, clock); @@ -288,8 +296,14 @@ import java.util.concurrent.atomic.AtomicBoolean; if (rendererCapabilitiesListener != null) { rendererCapabilities[i].setListener(rendererCapabilitiesListener); } - this.renderers[i] = new RendererHolder(renderers[i], /* index= */ i); + if (secondaryRenderers[i] != null) { + secondaryRenderers[i].init(/* index= */ i, playerId, clock); + hasSecondaryRenderers = true; + } + this.renderers[i] = new RendererHolder(renderers[i], secondaryRenderers[i], /* index= */ i); } + this.hasSecondaryRenderers = hasSecondaryRenderers; + mediaClock = new DefaultMediaClock(this, clock); pendingMessages = new ArrayList<>(); window = new Timeline.Window(); @@ -1477,6 +1491,7 @@ import java.util.concurrent.atomic.AtomicBoolean; newPlayingPeriodHolder.setRendererOffset( MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US); enableRenderers(); + newPlayingPeriodHolder.allRenderersInCorrectState = true; } } @@ -1512,7 +1527,7 @@ import java.util.concurrent.atomic.AtomicBoolean; : playingMediaPeriod.toRendererTime(periodPositionUs); mediaClock.resetPosition(rendererPositionUs); for (RendererHolder rendererHolder : renderers) { - rendererHolder.resetPosition(rendererPositionUs); + rendererHolder.resetPosition(playingMediaPeriod, rendererPositionUs); } notifyTrackSelectionDiscontinuity(); } @@ -1533,9 +1548,7 @@ import java.util.concurrent.atomic.AtomicBoolean; this.foregroundMode = foregroundMode; if (!foregroundMode) { for (RendererHolder rendererHolder : renderers) { - if (rendererHolder.getEnabledRendererCount() == 0) { - rendererHolder.reset(); - } + rendererHolder.reset(); } } } @@ -1873,15 +1886,16 @@ import java.util.concurrent.atomic.AtomicBoolean; private void disableRenderers() { for (int i = 0; i < renderers.length; i++) { - disableRenderer(i); + disableRenderer(/* rendererIndex= */ i); } + prewarmingMediaPeriodDiscontinuity = C.TIME_UNSET; } private void disableRenderer(int rendererIndex) { - int holderEnabledRendererCount = renderers[rendererIndex].getEnabledRendererCount(); + int enabledRendererCountBeforeDisabling = renderers[rendererIndex].getEnabledRendererCount(); renderers[rendererIndex].disable(mediaClock); maybeTriggerOnRendererReadyChanged(rendererIndex, /* allowsPlayback= */ false); - enabledRendererCount -= holderEnabledRendererCount; + enabledRendererCount -= enabledRendererCountBeforeDisabling; } private void reselectTracksInternalAndSeek() throws ExoPlaybackException { @@ -1923,7 +1937,8 @@ import java.util.concurrent.atomic.AtomicBoolean; if (selectionsChangedForReadPeriod) { // Update streams and rebuffer for the new selection, recreating all streams if reading ahead. MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); - boolean recreateStreams = queue.removeAfter(playingPeriodHolder); + int removeAfterResult = queue.removeAfter(playingPeriodHolder); + boolean recreateStreams = (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) != 0; boolean[] streamResetFlags = new boolean[renderers.length]; long periodPositionUs = @@ -1954,7 +1969,7 @@ import java.util.concurrent.atomic.AtomicBoolean; if (!renderers[i].isReadingFromPeriod(playingPeriodHolder)) { disableRenderer(i); } else if (streamResetFlags[i]) { - renderers[i].resetPosition(rendererPositionUs); + renderers[i].resetPosition(playingPeriodHolder, rendererPositionUs); } } } @@ -2020,7 +2035,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ? livePlaybackSpeedControl.getTargetLiveOffsetUs() : C.TIME_UNSET; MediaPeriodHolder loadingHolder = queue.getLoadingPeriod(); - boolean isBufferedToEnd = (loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal); + boolean isBufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal; // Ad loader implementations may only load ad media once playback has nearly reached the ad, but // it is possible for playback to be stuck buffering waiting for this. Therefore, we start // playback regardless of buffered duration if we are waiting for an ad media period to prepare. @@ -2221,6 +2236,7 @@ import java.util.concurrent.atomic.AtomicBoolean; return; } boolean loadingPeriodChanged = maybeUpdateLoadingPeriod(); + maybeUpdatePrewarmingPeriod(); maybeUpdateReadingPeriod(); maybeUpdateReadingRenderers(); maybeUpdatePlayingPeriod(); @@ -2258,6 +2274,54 @@ import java.util.concurrent.atomic.AtomicBoolean; return loadingPeriodChanged; } + private void maybeUpdatePrewarmingPeriod() throws ExoPlaybackException { + // TODO: Add limit as to not enable waiting renderer too early + if (pendingPauseAtEndOfPeriod || !hasSecondaryRenderers || areRenderersPrewarming()) { + return; + } + @Nullable MediaPeriodHolder prewarmingPeriodHolder = queue.getPrewarmingPeriod(); + if (prewarmingPeriodHolder == null + || prewarmingPeriodHolder != queue.getReadingPeriod() + || prewarmingPeriodHolder.getNext() == null + || !prewarmingPeriodHolder.getNext().prepared) { + return; + } + + queue.advancePrewarmingPeriod(); + maybePrewarmRenderers(); + } + + private void maybePrewarmRenderers() throws ExoPlaybackException { + @Nullable MediaPeriodHolder prewarmingPeriod = queue.getPrewarmingPeriod(); + if (prewarmingPeriod == null) { + return; + } + TrackSelectorResult trackSelectorResult = prewarmingPeriod.getTrackSelectorResult(); + for (int i = 0; i < renderers.length; i++) { + if (trackSelectorResult.isRendererEnabled(i) + && renderers[i].hasSecondary() + && !renderers[i].isPrewarming()) { + renderers[i].startPrewarming(); + enableRenderer( + prewarmingPeriod, + /* rendererIndex= */ i, + /* wasRendererEnabled= */ false, + prewarmingPeriod.getStartPositionRendererTime()); + } + } + // Handle any media period discontinuities. + if (areRenderersPrewarming()) { + prewarmingMediaPeriodDiscontinuity = prewarmingPeriod.mediaPeriod.readDiscontinuity(); + if (!prewarmingPeriod.isFullyBuffered()) { + // The discontinuity caused the period to not be fully buffered. Continue loading from + // this period again and discard all other periods we already started loading. + queue.removeAfter(prewarmingPeriod); + handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); + maybeContinueLoading(); + } + } + } + private void maybeUpdateReadingPeriod() throws ExoPlaybackException { @Nullable MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); if (readingPeriodHolder == null) { @@ -2268,20 +2332,19 @@ import java.util.concurrent.atomic.AtomicBoolean; // We don't have a successor to advance the reading period to or we want to let them end // intentionally to pause at the end of the period. if (readingPeriodHolder.info.isFinal || pendingPauseAtEndOfPeriod) { - for (int i = 0; i < renderers.length; i++) { - RendererHolder renderer = renderers[i]; + for (RendererHolder renderer : renderers) { if (!renderer.isReadingFromPeriod(readingPeriodHolder)) { continue; } // Defer setting the stream as final until the renderer has actually consumed the whole // stream in case of playlist changes that cause the stream to be no longer final. - if (renderer.hasReadStreamToEnd()) { + if (renderer.hasReadPeriodToEnd(readingPeriodHolder)) { long streamEndPositionUs = readingPeriodHolder.info.durationUs != C.TIME_UNSET && readingPeriodHolder.info.durationUs != C.TIME_END_OF_SOURCE ? readingPeriodHolder.getRendererOffset() + readingPeriodHolder.info.durationUs : C.TIME_UNSET; - renderer.setCurrentStreamFinal(streamEndPositionUs); + renderer.setCurrentStreamFinal(readingPeriodHolder, streamEndPositionUs); } } } @@ -2292,6 +2355,11 @@ import java.util.concurrent.atomic.AtomicBoolean; return; } + if (areRenderersPrewarming() && queue.getPrewarmingPeriod() == queue.getReadingPeriod()) { + // Reading period has already advanced to pre-warming period. + return; + } + if (!readingPeriodHolder.getNext().prepared && rendererPositionUs < readingPeriodHolder.getNext().getStartPositionRendererTime()) { // The successor is not prepared yet and playback hasn't reached the transition point. @@ -2312,37 +2380,48 @@ import java.util.concurrent.atomic.AtomicBoolean; /* forceSetTargetOffsetOverride= */ false); 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 + && ((hasSecondaryRenderers && prewarmingMediaPeriodDiscontinuity != C.TIME_UNSET) + || readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET)) { + prewarmingMediaPeriodDiscontinuity = C.TIME_UNSET; + // The new period starts with a discontinuity, so unless a pre-warming renderer is handling + // the discontinuity, the renderers will play out all data, then // be disabled and re-enabled when they start playing the next period. - setAllRendererStreamsFinal( - /* streamEndPositionUs= */ readingPeriodHolder.getStartPositionRendererTime()); - if (!readingPeriodHolder.isFullyBuffered()) { - // The discontinuity caused the period to not be fully buffered. Continue loading from this - // period again and discard all other periods we already started loading. - queue.removeAfter(readingPeriodHolder); - handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); - maybeContinueLoading(); - } - return; - } - for (int i = 0; i < renderers.length; 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) { - // 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. - renderers[i].setCurrentStreamFinal( - /* streamEndPositionUs= */ readingPeriodHolder.getStartPositionRendererTime()); + boolean arePrewarmingRenderersHandlingDiscontinuity = hasSecondaryRenderers; + if (arePrewarmingRenderersHandlingDiscontinuity) { + for (int i = 0; i < renderers.length; i++) { + if (!newTrackSelectorResult.isRendererEnabled(i)) { + continue; + } + // TODO: This check should ideally be replaced by a per-stream discontinuity check + // done by the MediaPeriod itself. + if (!MimeTypes.allSamplesAreSyncSamples( + newTrackSelectorResult.selections[i].getSelectedFormat().sampleMimeType, + newTrackSelectorResult.selections[i].getSelectedFormat().codecs) + && !renderers[i].isPrewarming()) { + arePrewarmingRenderersHandlingDiscontinuity = false; + break; + } } } + if (!arePrewarmingRenderersHandlingDiscontinuity) { + setAllNonPrewarmingRendererStreamsFinal( + /* streamEndPositionUs= */ readingPeriodHolder.getStartPositionRendererTime()); + if (!readingPeriodHolder.isFullyBuffered()) { + // The discontinuity caused the period to not be fully buffered. Continue loading from + // this period again and discard all other periods we already started loading. + queue.removeAfter(readingPeriodHolder); + handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); + maybeContinueLoading(); + } + return; + } + } + + for (RendererHolder renderer : renderers) { + renderer.maybeSetOldStreamToFinal( + oldTrackSelectorResult, + newTrackSelectorResult, + readingPeriodHolder.getStartPositionRendererTime()); } } @@ -2354,11 +2433,48 @@ import java.util.concurrent.atomic.AtomicBoolean; // Not reading ahead or all renderers updated. return; } - if (replaceStreamsOrDisableRendererForTransition()) { - enableRenderers(); + boolean allUpdated = updateRenderersForTransition(); + if (allUpdated) { + queue.getReadingPeriod().allRenderersInCorrectState = true; } } + private boolean updateRenderersForTransition() throws ExoPlaybackException { + MediaPeriodHolder readingMediaPeriod = queue.getReadingPeriod(); + TrackSelectorResult newTrackSelectorResult = readingMediaPeriod.getTrackSelectorResult(); + boolean allUpdated = true; + for (int i = 0; i < renderers.length; i++) { + int enabledRendererCountPreTransition = renderers[i].getEnabledRendererCount(); + int result = + renderers[i].replaceStreamsOrDisableRendererForTransition( + readingMediaPeriod, newTrackSelectorResult, mediaClock); + if ((result & REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING) != 0 + && offloadSchedulingEnabled) { + // Prevent sleeping across offload track transition else position won't get updated. + // TODO: (b/183635183) Optimize Offload End-Of-Stream: Sleep to just before end of track + setOffloadSchedulingEnabled(false); + } + enabledRendererCount -= + enabledRendererCountPreTransition - renderers[i].getEnabledRendererCount(); + + boolean completedUpdate = (result & REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED) != 0; + allUpdated &= completedUpdate; + } + if (allUpdated) { + for (int i = 0; i < renderers.length; i++) { + if (newTrackSelectorResult.isRendererEnabled(i) + && !renderers[i].isReadingFromPeriod(readingMediaPeriod)) { + enableRenderer( + readingMediaPeriod, + /* rendererIndex= */ i, + /* wasRendererEnabled= */ false, + readingMediaPeriod.getStartPositionRendererTime()); + } + } + } + return allUpdated; + } + private void maybeUpdatePreloadPeriods(boolean loadingPeriodChanged) { if (preloadConfiguration.targetPreloadDurationUs == C.TIME_UNSET) { // Do nothing if preloading disabled. @@ -2397,46 +2513,6 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - private boolean replaceStreamsOrDisableRendererForTransition() throws ExoPlaybackException { - MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod(); - TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult(); - boolean needsToWaitForRendererToEnd = false; - for (int i = 0; i < renderers.length; i++) { - RendererHolder renderer = renderers[i]; - if (renderer.getEnabledRendererCount() == 0) { - continue; - } - boolean rendererIsReadingOldStream = !renderer.isReadingFromPeriod(readingPeriodHolder); - 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[i]); - renderer.replaceStream( - formats, - readingPeriodHolder.sampleStreams[i], - readingPeriodHolder.getStartPositionRendererTime(), - readingPeriodHolder.getRendererOffset(), - readingPeriodHolder.info.id); - if (offloadSchedulingEnabled) { - // Prevent sleeping across offload track transition else position won't get updated. - // TODO: (b/183635183) Optimize Offload End-Of-Stream: Sleep to just before end of track - setOffloadSchedulingEnabled(false); - } - } else if (renderer.isEnded()) { - // The renderer has finished playback, so we can disable it now. - disableRenderer(i); - } 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()) { @@ -2461,6 +2537,9 @@ import java.util.concurrent.atomic.AtomicBoolean; Player.DISCONTINUITY_REASON_AUTO_TRANSITION); resetPendingPauseAtEndOfPeriod(); updatePlaybackPositions(); + if (areRenderersPrewarming() && newPlayingPeriodHolder == queue.getPrewarmingPeriod()) { + maybeHandlePrewarmingTransition(); + } if (playbackInfo.playbackState == Player.STATE_READY) { startRenderers(); } @@ -2469,6 +2548,12 @@ import java.util.concurrent.atomic.AtomicBoolean; } } + private void maybeHandlePrewarmingTransition() { + for (RendererHolder renderer : renderers) { + renderer.maybeHandlePrewarmingTransition(); + } + } + private void maybeUpdateOffloadScheduling() { // If playing period is audio-only with offload mode preference to enable, then offload // scheduling should be enabled. @@ -2539,9 +2624,9 @@ import java.util.concurrent.atomic.AtomicBoolean; return true; } - private void setAllRendererStreamsFinal(long streamEndPositionUs) { + private void setAllNonPrewarmingRendererStreamsFinal(long streamEndPositionUs) { for (RendererHolder renderer : renderers) { - renderer.setCurrentStreamFinal(streamEndPositionUs); + renderer.setAllNonPrewarmingRendererStreamsFinal(streamEndPositionUs); } } @@ -2579,6 +2664,7 @@ import java.util.concurrent.atomic.AtomicBoolean; // This is the first prepared period, so update the position and the renderers. resetRendererPosition(loadingPeriodHolder.info.startPositionUs); enableRenderers(); + loadingPeriodHolder.allRenderersInCorrectState = true; playbackInfo = handlePositionDiscontinuity( playbackInfo.periodId, @@ -2789,20 +2875,26 @@ import java.util.concurrent.atomic.AtomicBoolean; renderers[i].reset(); } } - // Enable the renderers. for (int i = 0; i < renderers.length; i++) { - if (trackSelectorResult.isRendererEnabled(i)) { - enableRenderer(i, rendererWasEnabledFlags[i], startPositionUs); + if (trackSelectorResult.isRendererEnabled(i) + && !renderers[i].isReadingFromPeriod(readingMediaPeriod)) { + enableRenderer( + readingMediaPeriod, + /* rendererIndex= */ i, + rendererWasEnabledFlags[i], + startPositionUs); } } - readingMediaPeriod.allRenderersInCorrectState = true; } - private void enableRenderer(int rendererIndex, boolean wasRendererEnabled, long startPositionUs) + private void enableRenderer( + MediaPeriodHolder periodHolder, + int rendererIndex, + boolean wasRendererEnabled, + long startPositionUs) throws ExoPlaybackException { - MediaPeriodHolder periodHolder = queue.getReadingPeriod(); RendererHolder renderer = renderers[rendererIndex]; - if (renderer.getEnabledRendererCount() > 0) { + if (renderer.isRendererEnabled()) { return; } boolean arePlayingAndReadingTheSamePeriod = periodHolder == queue.getPlayingPeriod(); @@ -2810,16 +2902,16 @@ import java.util.concurrent.atomic.AtomicBoolean; RendererConfiguration rendererConfiguration = trackSelectorResult.rendererConfigurations[rendererIndex]; ExoTrackSelection newSelection = trackSelectorResult.selections[rendererIndex]; - Format[] formats = getFormats(newSelection); // The renderer needs enabling with its new track selection. boolean playing = shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY; - // Consider as joining only if the renderer was previously disabled. + // Consider as joining only if the renderer was previously disabled and being enabled on the + // playing period. boolean joining = !wasRendererEnabled && playing; // Enable the renderer. enabledRendererCount++; renderer.enable( rendererConfiguration, - formats, + newSelection, periodHolder.sampleStreams[rendererIndex], rendererPositionUs, joining, @@ -2842,7 +2934,8 @@ import java.util.concurrent.atomic.AtomicBoolean; handler.sendEmptyMessage(MSG_DO_SOME_WORK); } } - }); + }, + /* mediaPeriod= */ periodHolder); // Start the renderer if playing and the Playing and Reading periods are the same. if (playing && arePlayingAndReadingTheSamePeriod) { renderer.start(); @@ -2934,7 +3027,7 @@ import java.util.concurrent.atomic.AtomicBoolean; throws IOException, ExoPlaybackException { RendererHolder renderer = renderers[rendererIndex]; try { - renderer.maybeThrowStreamError(); + renderer.maybeThrowStreamError(checkNotNull(queue.getPlayingPeriod())); } catch (IOException | RuntimeException e) { switch (renderer.getTrackType()) { case C.TRACK_TYPE_TEXT: @@ -2970,6 +3063,18 @@ import java.util.concurrent.atomic.AtomicBoolean; } } + private boolean areRenderersPrewarming() { + if (!hasSecondaryRenderers) { + return false; + } + for (RendererHolder renderer : renderers) { + if (renderer.isPrewarming()) { + return true; + } + } + return false; + } + private static PositionUpdateForPlaylistChange resolvePositionForPlaylistChange( Timeline timeline, PlaybackInfo playbackInfo, @@ -3416,16 +3521,6 @@ import java.util.concurrent.atomic.AtomicBoolean; : newTimeline.getPeriod(newPeriodIndex, period).windowIndex; } - private static Format[] getFormats(ExoTrackSelection newSelection) { - // Build an array of formats contained by the selection. - int length = newSelection != null ? newSelection.length() : 0; - Format[] formats = new Format[length]; - for (int i = 0; i < length; i++) { - formats[i] = newSelection.getFormat(i); - } - return formats; - } - private static final class SeekPosition { public final Timeline timeline; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodQueue.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodQueue.java index b68d2c4877..96421bb209 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodQueue.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodQueue.java @@ -18,9 +18,11 @@ package androidx.media3.exoplayer; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull; import static java.lang.Math.max; +import static java.lang.annotation.ElementType.TYPE_USE; import android.os.Handler; import android.util.Pair; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.media3.common.AdPlaybackState; import androidx.media3.common.C; @@ -33,6 +35,10 @@ import androidx.media3.exoplayer.analytics.AnalyticsCollector; import androidx.media3.exoplayer.source.MediaPeriod; import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId; import com.google.common.collect.ImmutableList; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; @@ -82,6 +88,7 @@ import java.util.List; private PreloadConfiguration preloadConfiguration; @Nullable private MediaPeriodHolder playing; @Nullable private MediaPeriodHolder reading; + @Nullable private MediaPeriodHolder prewarming; @Nullable private MediaPeriodHolder loading; @Nullable private MediaPeriodHolder preloading; private int length; @@ -218,6 +225,7 @@ import java.util.List; } else { playing = newPeriodHolder; reading = newPeriodHolder; + prewarming = newPeriodHolder; } oldFrontPeriodUid = null; loading = newPeriodHolder; @@ -362,17 +370,37 @@ import java.util.List; return reading; } + /** Returns the prewarming period holder, or null if the queue is empty. */ + @Nullable + public MediaPeriodHolder getPrewarmingPeriod() { + return prewarming; + } + /** * Continues reading from the next period holder in the queue. * * @return The updated reading period holder. */ public MediaPeriodHolder advanceReadingPeriod() { + if (prewarming == reading) { + prewarming = checkStateNotNull(reading).getNext(); + } reading = checkStateNotNull(reading).getNext(); notifyQueueUpdate(); return checkStateNotNull(reading); } + /** + * Continues pre-warming from the next period holder in the queue. + * + * @return The updated pre-warming period holder. + */ + public MediaPeriodHolder advancePrewarmingPeriod() { + prewarming = checkStateNotNull(prewarming).getNext(); + notifyQueueUpdate(); + return checkStateNotNull(prewarming); + } + /** * Dequeues the playing period holder from the front of the queue and advances the playing period * holder to be the next item in the queue. @@ -387,6 +415,9 @@ import java.util.List; if (playing == reading) { reading = playing.getNext(); } + if (playing == prewarming) { + prewarming = playing.getNext(); + } playing.release(); length--; if (length == 0) { @@ -405,27 +436,33 @@ import java.util.List; * the same as the playing period holder at the front of the queue. * * @param mediaPeriodHolder The media period holder that shall be the new end of the queue. - * @return Whether the reading period has been removed. + * @return {@link RemoveAfterResult} with flags denoting if the reading or pre-warming periods + * were removed. */ - public boolean removeAfter(MediaPeriodHolder mediaPeriodHolder) { + public int removeAfter(MediaPeriodHolder mediaPeriodHolder) { checkStateNotNull(mediaPeriodHolder); if (mediaPeriodHolder.equals(loading)) { - return false; + return 0; } - boolean removedReading = false; + int removedResult = 0; loading = mediaPeriodHolder; while (mediaPeriodHolder.getNext() != null) { mediaPeriodHolder = checkNotNull(mediaPeriodHolder.getNext()); if (mediaPeriodHolder == reading) { reading = playing; - removedReading = true; + prewarming = playing; + removedResult |= REMOVE_AFTER_REMOVED_READING_PERIOD; + } + if (mediaPeriodHolder == prewarming) { + prewarming = reading; + removedResult |= REMOVE_AFTER_REMOVED_PREWARMING_PERIOD; } mediaPeriodHolder.release(); length--; } checkNotNull(loading).setNext(null); notifyQueueUpdate(); - return removedReading; + return removedResult; } /** @@ -472,6 +509,7 @@ import java.util.List; playing = null; loading = null; reading = null; + prewarming = null; length = 0; notifyQueueUpdate(); } @@ -511,11 +549,13 @@ import java.util.List; getFollowingMediaPeriodInfo(timeline, previousPeriodHolder, rendererPositionUs); if (newPeriodInfo == null) { // We've loaded a next media period that is not in the new timeline. - return !removeAfter(previousPeriodHolder); + int removeAfterResult = removeAfter(previousPeriodHolder); + return (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) == 0; } if (!canKeepMediaPeriodHolder(oldPeriodInfo, newPeriodInfo)) { // The new media period has a different id or start position. - return !removeAfter(previousPeriodHolder); + int removeAfterResult = removeAfter(previousPeriodHolder); + return (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) == 0; } } @@ -538,7 +578,9 @@ import java.util.List; && !periodHolder.info.isFollowedByTransitionToSameStream && (maxRendererReadPositionUs == C.TIME_END_OF_SOURCE || maxRendererReadPositionUs >= newDurationInRendererTime); - boolean readingPeriodRemoved = removeAfter(periodHolder); + int removeAfterResult = removeAfter(periodHolder); + boolean readingPeriodRemoved = + (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) != 0; return !readingPeriodRemoved && !isReadingAndReadBeyondNewDuration; } @@ -834,7 +876,8 @@ import java.util.List; } // Release any period holders that don't match the new period order. - boolean readingPeriodRemoved = removeAfter(lastValidPeriodHolder); + int removeAfterResult = removeAfter(lastValidPeriodHolder); + boolean readingPeriodRemoved = (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) != 0; // Update the period info for the last holder, as it may now be the last period in the timeline. lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(timeline, lastValidPeriodHolder.info); @@ -1211,4 +1254,22 @@ import java.util.List; } return startPositionUs + period.getContentResumeOffsetUs(adGroupIndex); } + + /** + * Result for {@link #removeAfter} that signifies whether the reading or pre-warming periods were + * removed during the process. + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef( + flag = true, + value = {REMOVE_AFTER_REMOVED_READING_PERIOD, REMOVE_AFTER_REMOVED_PREWARMING_PERIOD}) + /* package */ @interface RemoveAfterResult {} + + /** The call to {@link #removeAfter} removed the reading period. */ + /* package */ static final int REMOVE_AFTER_REMOVED_READING_PERIOD = 1; + + /** The call to {@link #removeAfter} removed the pre-warming period. */ + /* package */ static final int REMOVE_AFTER_REMOVED_PREWARMING_PERIOD = 1 << 1; } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererHolder.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererHolder.java index 4efe3b2e3f..30edb47c18 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererHolder.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererHolder.java @@ -16,37 +16,84 @@ package androidx.media3.exoplayer; import static androidx.media3.common.C.TRACK_TYPE_VIDEO; +import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.exoplayer.Renderer.STATE_DISABLED; import static androidx.media3.exoplayer.Renderer.STATE_ENABLED; import static androidx.media3.exoplayer.Renderer.STATE_STARTED; +import static java.lang.Math.min; +import static java.lang.annotation.ElementType.TYPE_USE; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.Timeline; -import androidx.media3.common.util.Assertions; import androidx.media3.exoplayer.metadata.MetadataRenderer; import androidx.media3.exoplayer.source.MediaPeriod; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.SampleStream; import androidx.media3.exoplayer.text.TextRenderer; +import androidx.media3.exoplayer.trackselection.ExoTrackSelection; +import androidx.media3.exoplayer.trackselection.TrackSelectorResult; import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Objects; /** Holds a {@link Renderer renderer}. */ /* package */ class RendererHolder { - private final Renderer renderer; + private final Renderer primaryRenderer; // Index of renderer in renderer list held by the {@link Player}. private final int index; - private boolean requiresReset; + @Nullable private final Renderer secondaryRenderer; + private @RendererPrewarmingState int prewarmingState; + private boolean primaryRequiresReset; + private boolean secondaryRequiresReset; - public RendererHolder(Renderer renderer, int index) { - this.renderer = renderer; + public RendererHolder(Renderer renderer, @Nullable Renderer secondaryRenderer, int index) { + this.primaryRenderer = renderer; this.index = index; - requiresReset = false; + this.secondaryRenderer = secondaryRenderer; + prewarmingState = RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY; + primaryRequiresReset = false; + secondaryRequiresReset = false; + } + + public boolean hasSecondary() { + return secondaryRenderer != null; + } + + public void startPrewarming() { + checkState(!isPrewarming()); + prewarmingState = + isRendererEnabled(primaryRenderer) + ? RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY + : secondaryRenderer != null && isRendererEnabled(secondaryRenderer) + ? RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY + : RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY; + } + + public boolean isPrewarming() { + return isPrimaryRendererPrewarming() || isSecondaryRendererPrewarming(); + } + + private boolean isPrimaryRendererPrewarming() { + return prewarmingState == RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY + || prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY; + } + + private boolean isSecondaryRendererPrewarming() { + return prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY; } public int getEnabledRendererCount() { - return isRendererEnabled(renderer) ? 1 : 0; + int result = 0; + result += isRendererEnabled(primaryRenderer) ? 1 : 0; + result += secondaryRenderer != null && isRendererEnabled(secondaryRenderer) ? 1 : 0; + return result; } /** @@ -55,7 +102,7 @@ import java.io.IOException; * @see Renderer#getTrackType() */ public @C.TrackType int getTrackType() { - return renderer.getTrackType(); + return primaryRenderer.getTrackType(); } /** @@ -70,8 +117,7 @@ import java.io.IOException; * {@link MediaPeriodHolder media period}. */ public long getReadingPositionUs(@Nullable MediaPeriodHolder period) { - Assertions.checkState(isReadingFromPeriod(period)); - return renderer.getReadingPositionUs(); + return Objects.requireNonNull(getRendererReadingFromPeriod(period)).getReadingPositionUs(); } /** @@ -79,7 +125,8 @@ import java.io.IOException; * * @see Renderer#hasReadStreamToEnd() */ - public boolean hasReadStreamToEnd() { + public boolean hasReadPeriodToEnd(MediaPeriodHolder mediaPeriodHolder) { + Renderer renderer = checkNotNull(getRendererReadingFromPeriod(mediaPeriodHolder)); return renderer.hasReadStreamToEnd(); } @@ -88,47 +135,84 @@ import java.io.IOException; * before it is next disabled or reset. * * @see Renderer#setCurrentStreamFinal() + * @param mediaPeriodHolder The {@link MediaPeriodHolder media period} containing the current + * stream. * @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to * render until the end of the current stream. */ - public void setCurrentStreamFinal(long streamEndPositionUs) { - if (renderer.getStream() != null) { - setCurrentStreamFinal(renderer, streamEndPositionUs); + public void setCurrentStreamFinal(MediaPeriodHolder mediaPeriodHolder, long streamEndPositionUs) { + Renderer renderer = checkNotNull(getRendererReadingFromPeriod(mediaPeriodHolder)); + setCurrentStreamFinalInternal(renderer, streamEndPositionUs); + } + + /** + * Maybe signal to the renderer that the old {@link SampleStream} will be the final one supplied + * before it is next disabled or reset. + * + * @param oldTrackSelectorResult {@link TrackSelectorResult} containing the previous {@link + * SampleStream}. + * @param newTrackSelectorResult {@link TrackSelectorResult} containing the next {@link + * SampleStream}. + * @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to + * render until the end of the current stream. + */ + public void maybeSetOldStreamToFinal( + TrackSelectorResult oldTrackSelectorResult, + TrackSelectorResult newTrackSelectorResult, + long streamEndPositionUs) { + boolean oldRendererEnabled = oldTrackSelectorResult.isRendererEnabled(index); + boolean newRendererEnabled = newTrackSelectorResult.isRendererEnabled(index); + boolean isPrimaryOldRenderer = + secondaryRenderer == null + || prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY + || (prewarmingState == RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY + && isRendererEnabled(primaryRenderer)); + Renderer oldRenderer = isPrimaryOldRenderer ? primaryRenderer : checkNotNull(secondaryRenderer); + if (oldRendererEnabled && !oldRenderer.isCurrentStreamFinal()) { + boolean isNoSampleRenderer = getTrackType() == C.TRACK_TYPE_NONE; + RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[index]; + RendererConfiguration newConfig = newTrackSelectorResult.rendererConfigurations[index]; + if (!newRendererEnabled + || !Objects.equals(newConfig, oldConfig) + || isNoSampleRenderer + || isPrewarming()) { + // 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, or there is a backup renderer that has already been + // enabled for the following media item. Mark the SampleStream as final to play out any + // remaining data. + setCurrentStreamFinalInternal(oldRenderer, streamEndPositionUs); + } } } - private void setCurrentStreamFinal(Renderer renderer, long streamEndPositionUs) { + /** + * Calls {@link Renderer#setCurrentStreamFinal} on enabled {@link Renderer renderers} that are not + * pre-warming. + * + * @see Renderer#setCurrentStreamFinal + */ + public void setAllNonPrewarmingRendererStreamsFinal(long streamEndPositionUs) { + if (isRendererEnabled(primaryRenderer) + && prewarmingState != RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY + && prewarmingState != RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY) { + setCurrentStreamFinalInternal(primaryRenderer, streamEndPositionUs); + } + if (secondaryRenderer != null + && isRendererEnabled(secondaryRenderer) + && prewarmingState != RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY) { + setCurrentStreamFinalInternal(secondaryRenderer, streamEndPositionUs); + } + } + + private void setCurrentStreamFinalInternal(Renderer renderer, long streamEndPositionUs) { renderer.setCurrentStreamFinal(); if (renderer instanceof TextRenderer) { ((TextRenderer) renderer).setFinalStreamEndPositionUs(streamEndPositionUs); } } - /** - * Returns whether the current {@link SampleStream} will be the final one supplied before the - * renderer is next disabled or reset. - * - * @see Renderer#isCurrentStreamFinal() - */ - public boolean isCurrentStreamFinal() { - return renderer.isCurrentStreamFinal(); - } - - /** - * Invokes {@link Renderer#replaceStream}. - * - * @see Renderer#replaceStream - */ - public void replaceStream( - Format[] formats, - SampleStream stream, - long startPositionUs, - long offsetUs, - MediaSource.MediaPeriodId mediaPeriodId) - throws ExoPlaybackException { - renderer.replaceStream(formats, stream, startPositionUs, offsetUs, mediaPeriodId); - } - /** * Returns minimum amount of playback clock time that must pass in order for the {@link #render} * call to make progress. @@ -145,9 +229,19 @@ import java.io.IOException; */ public long getMinDurationToProgressUs( long rendererPositionUs, long rendererPositionElapsedRealtimeUs) { - return isRendererEnabled(renderer) - ? renderer.getDurationToProgressUs(rendererPositionUs, rendererPositionElapsedRealtimeUs) - : Long.MAX_VALUE; + long minDurationToProgress = + isRendererEnabled(primaryRenderer) + ? primaryRenderer.getDurationToProgressUs( + rendererPositionUs, rendererPositionElapsedRealtimeUs) + : Long.MAX_VALUE; + if (secondaryRenderer != null && isRendererEnabled(secondaryRenderer)) { + minDurationToProgress = + min( + minDurationToProgress, + secondaryRenderer.getDurationToProgressUs( + rendererPositionUs, rendererPositionElapsedRealtimeUs)); + } + return minDurationToProgress; } /** @@ -156,8 +250,10 @@ import java.io.IOException; * @see Renderer#enableMayRenderStartOfStream */ public void enableMayRenderStartOfStream() { - if (isRendererEnabled(renderer)) { - renderer.enableMayRenderStartOfStream(); + if (isRendererEnabled(primaryRenderer)) { + primaryRenderer.enableMayRenderStartOfStream(); + } else if (secondaryRenderer != null && isRendererEnabled(secondaryRenderer)) { + secondaryRenderer.enableMayRenderStartOfStream(); } } @@ -168,7 +264,10 @@ import java.io.IOException; */ public void setPlaybackSpeed(float currentPlaybackSpeed, float targetPlaybackSpeed) throws ExoPlaybackException { - renderer.setPlaybackSpeed(currentPlaybackSpeed, targetPlaybackSpeed); + primaryRenderer.setPlaybackSpeed(currentPlaybackSpeed, targetPlaybackSpeed); + if (secondaryRenderer != null) { + secondaryRenderer.setPlaybackSpeed(currentPlaybackSpeed, targetPlaybackSpeed); + } } /** @@ -177,7 +276,10 @@ import java.io.IOException; * @see Renderer#setTimeline */ public void setTimeline(Timeline timeline) { - renderer.setTimeline(timeline); + primaryRenderer.setTimeline(timeline); + if (secondaryRenderer != null) { + secondaryRenderer.setTimeline(timeline); + } } /** @@ -187,7 +289,14 @@ import java.io.IOException; * @return if all renderers have {@link Renderer#isEnded() ended}. */ public boolean isEnded() { - return renderer.isEnded(); + boolean renderersEnded = true; + if (isRendererEnabled(primaryRenderer)) { + renderersEnded &= primaryRenderer.isEnded(); + } + if (secondaryRenderer != null && isRendererEnabled(secondaryRenderer)) { + renderersEnded &= secondaryRenderer.isEnded(); + } + return renderersEnded; } /** @@ -200,28 +309,6 @@ import java.io.IOException; return getRendererReadingFromPeriod(period) != null; } - /** - * Returns the {@link Renderer} that is enabled on the provided media {@link MediaPeriodHolder - * period}. - * - *
Returns null if the renderer is not enabled on the requested period. - * - * @param period The {@link MediaPeriodHolder period} with which to retrieve the linked {@link - * Renderer} - * @return {@link Renderer} enabled on the {@link MediaPeriodHolder period} or {@code null} if the - * renderer is not enabled on the provided period. - */ - @Nullable - private Renderer getRendererReadingFromPeriod(@Nullable MediaPeriodHolder period) { - if (period == null || period.sampleStreams[index] == null) { - return null; - } - if (renderer.getStream() == period.sampleStreams[index]) { - return renderer; - } - return null; - } - /** * Returns whether the {@link Renderer renderers} are still reading a {@link MediaPeriodHolder * media period}. @@ -230,17 +317,27 @@ import java.io.IOException; * @return true if {@link Renderer renderers} are reading the current reading period. */ public boolean hasFinishedReadingFromPeriod(MediaPeriodHolder periodHolder) { - return hasFinishedReadingFromPeriodInternal(periodHolder); + return hasFinishedReadingFromPeriodInternal(periodHolder, primaryRenderer) + && hasFinishedReadingFromPeriodInternal(periodHolder, secondaryRenderer); } - private boolean hasFinishedReadingFromPeriodInternal(MediaPeriodHolder readingPeriodHolder) { + private boolean hasFinishedReadingFromPeriodInternal( + MediaPeriodHolder readingPeriodHolder, @Nullable Renderer renderer) { + if (renderer == null) { + return true; + } SampleStream sampleStream = readingPeriodHolder.sampleStreams[index]; - if (renderer.getStream() != sampleStream - || (sampleStream != null - && !renderer.hasReadStreamToEnd() - && !hasReachedServerSideInsertedAdsTransition(renderer, readingPeriodHolder))) { + if (renderer.getStream() != null + && (renderer.getStream() != sampleStream + || (sampleStream != null + && !renderer.hasReadStreamToEnd() + && !hasReachedServerSideInsertedAdsTransition(renderer, readingPeriodHolder)))) { // The current reading period is still being read by at least one renderer. - return false; + MediaPeriodHolder followingPeriod = readingPeriodHolder.getNext(); + // If renderer is reading ahead as it was enabled early, then it is not 'reading' the + // current reading period. + return followingPeriod != null + && followingPeriod.sampleStreams[index] == renderer.getStream(); } return true; } @@ -272,8 +369,11 @@ import java.io.IOException; */ public void render(long rendererPositionUs, long rendererPositionElapsedRealtimeUs) throws ExoPlaybackException { - if (isRendererEnabled(renderer)) { - renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs); + if (isRendererEnabled(primaryRenderer)) { + primaryRenderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs); + } + if (secondaryRenderer != null && isRendererEnabled(secondaryRenderer)) { + secondaryRenderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs); } } @@ -289,23 +389,22 @@ import java.io.IOException; * @param playingPeriodHolder The currently playing media {@link MediaPeriodHolder period}. * @return whether renderer allows playback. */ - public boolean allowsPlayback(MediaPeriodHolder playingPeriodHolder) throws IOException { - return allowsPlayback(renderer, playingPeriodHolder); - } - - private boolean allowsPlayback(Renderer renderer, MediaPeriodHolder playingPeriodHolder) { - boolean isReadingAhead = playingPeriodHolder.sampleStreams[index] != renderer.getStream(); - boolean isWaitingForNextStream = !isReadingAhead && renderer.hasReadStreamToEnd(); - return isReadingAhead || isWaitingForNextStream || renderer.isReady() || renderer.isEnded(); + public boolean allowsPlayback(MediaPeriodHolder playingPeriodHolder) { + Renderer renderer = getRendererReadingFromPeriod(playingPeriodHolder); + return renderer == null + || renderer.hasReadStreamToEnd() + || renderer.isReady() + || renderer.isEnded(); } /** - * Invokes {Renderer#maybeThrowStreamError}. + * Invokes {@link Renderer#maybeThrowStreamError()} for {@link Renderer} enabled on {@link + * MediaPeriodHolder media period}. * * @see Renderer#maybeThrowStreamError() */ - public void maybeThrowStreamError() throws IOException { - renderer.maybeThrowStreamError(); + public void maybeThrowStreamError(MediaPeriodHolder mediaPeriodHolder) throws IOException { + checkNotNull(getRendererReadingFromPeriod(mediaPeriodHolder)).maybeThrowStreamError(); } /** @@ -314,15 +413,23 @@ import java.io.IOException; * @throws ExoPlaybackException If an error occurs. */ public void start() throws ExoPlaybackException { - if (renderer.getState() == STATE_ENABLED) { - renderer.start(); + if (primaryRenderer.getState() == STATE_ENABLED + && (prewarmingState != RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY)) { + primaryRenderer.start(); + } else if (secondaryRenderer != null + && secondaryRenderer.getState() == STATE_ENABLED + && prewarmingState != RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY) { + secondaryRenderer.start(); } } /** Calls {@link Renderer#stop()} on all enabled {@link Renderer renderers}. */ public void stop() { - if (isRendererEnabled(renderer)) { - ensureStopped(renderer); + if (isRendererEnabled(primaryRenderer)) { + ensureStopped(primaryRenderer); + } + if (secondaryRenderer != null && isRendererEnabled(secondaryRenderer)) { + ensureStopped(secondaryRenderer); } } @@ -337,7 +444,7 @@ import java.io.IOException; * * @see Renderer#enable * @param configuration The renderer configuration. - * @param formats The enabled formats. + * @param trackSelection The track selection for the {@link Renderer}. * @param stream The {@link SampleStream} from which the renderer should consume. * @param positionUs The player's current position. * @param joining Whether this renderer is being enabled to join an ongoing playback. @@ -354,7 +461,7 @@ import java.io.IOException; */ public void enable( RendererConfiguration configuration, - Format[] formats, + ExoTrackSelection trackSelection, SampleStream stream, long positionUs, boolean joining, @@ -364,27 +471,53 @@ import java.io.IOException; MediaSource.MediaPeriodId mediaPeriodId, DefaultMediaClock mediaClock) throws ExoPlaybackException { - requiresReset = true; - renderer.enable( - configuration, - formats, - stream, - positionUs, - joining, - mayRenderStartOfStream, - startPositionUs, - offsetUs, - mediaPeriodId); - mediaClock.onRendererEnabled(renderer); + Format[] formats = getFormats(trackSelection); + boolean enablePrimary = + prewarmingState == RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY + || prewarmingState == RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY + || prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY; + if (enablePrimary) { + primaryRequiresReset = true; + primaryRenderer.enable( + configuration, + formats, + stream, + positionUs, + joining, + mayRenderStartOfStream, + startPositionUs, + offsetUs, + mediaPeriodId); + mediaClock.onRendererEnabled(primaryRenderer); + } else { + secondaryRequiresReset = true; + checkNotNull(secondaryRenderer) + .enable( + configuration, + formats, + stream, + positionUs, + joining, + mayRenderStartOfStream, + startPositionUs, + offsetUs, + mediaPeriodId); + mediaClock.onRendererEnabled(secondaryRenderer); + } } /** - * Invokes {@link Renderer#handleMessage} on the {@link Renderer}. + * Invokes {@link Renderer#handleMessage} on the {@link Renderer} enabled on the {@link + * MediaPeriodHolder media period}. * * @see Renderer#handleMessage(int, Object) */ - public void handleMessage(@Renderer.MessageType int messageType, @Nullable Object message) + public void handleMessage( + @Renderer.MessageType int messageType, + @Nullable Object message, + MediaPeriodHolder mediaPeriod) throws ExoPlaybackException { + Renderer renderer = checkNotNull(getRendererReadingFromPeriod(mediaPeriod)); renderer.handleMessage(messageType, message); } @@ -395,7 +528,22 @@ import java.io.IOException; * Renderer}. */ public void disable(DefaultMediaClock mediaClock) { - disableRenderer(renderer, mediaClock); + disableRenderer(primaryRenderer, mediaClock); + if (secondaryRenderer != null) { + disableRenderer(secondaryRenderer, mediaClock); + // Release resources for other renderer + maybeResetRenderer(/* resetPrimary= */ false); + } + prewarmingState = RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY; + } + + public void maybeHandlePrewarmingTransition() { + if (prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY + || prewarmingState == RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY) { + prewarmingState = RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY; + } else if (prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY) { + prewarmingState = RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_SECONDARY; + } } /** @@ -410,6 +558,7 @@ import java.io.IOException; * renderer}. */ private void disableRenderer(Renderer renderer, DefaultMediaClock mediaClock) { + checkState(primaryRenderer == renderer || secondaryRenderer == renderer); if (!isRendererEnabled(renderer)) { return; } @@ -419,37 +568,241 @@ import java.io.IOException; } /** - * Calls {@link Renderer#resetPosition} on the {@link Renderer} if its enabled. + * Invokes {@link Renderer#resetPosition} on the {@link Renderer} that is enabled on the provided + * {@link MediaPeriodHolder media period}. * * @see Renderer#resetPosition */ - public void resetPosition(long positionUs) throws ExoPlaybackException { - if (isRendererEnabled(renderer)) { + public void resetPosition(MediaPeriodHolder playingPeriod, long positionUs) + throws ExoPlaybackException { + Renderer renderer = getRendererReadingFromPeriod(playingPeriod); + if (renderer != null) { renderer.resetPosition(positionUs); } } - /** Calls {@link Renderer#reset()} on all renderers that must be reset. */ + /** + * Calls {@link Renderer#reset()} on all disabled {@link Renderer renderers} that must be reset. + */ public void reset() { - if (requiresReset) { - renderer.reset(); - requiresReset = false; + if (!isRendererEnabled(primaryRenderer)) { + maybeResetRenderer(/* resetPrimary= */ true); } + if (secondaryRenderer != null && !isRendererEnabled(secondaryRenderer)) { + maybeResetRenderer(/* resetPrimary= */ false); + } + } + + private void maybeResetRenderer(boolean resetPrimary) { + if (resetPrimary) { + if (primaryRequiresReset) { + primaryRenderer.reset(); + primaryRequiresReset = false; + } + } else if (secondaryRequiresReset) { + checkNotNull(secondaryRenderer).reset(); + secondaryRequiresReset = false; + } + } + + public int replaceStreamsOrDisableRendererForTransition( + MediaPeriodHolder readingPeriodHolder, + TrackSelectorResult newTrackSelectorResult, + DefaultMediaClock mediaClock) + throws ExoPlaybackException { + int primaryRendererResult = + replaceStreamsOrDisableRendererForTransitionInternal( + primaryRenderer, readingPeriodHolder, newTrackSelectorResult, mediaClock); + int secondaryRendererResult = + replaceStreamsOrDisableRendererForTransitionInternal( + secondaryRenderer, readingPeriodHolder, newTrackSelectorResult, mediaClock); + return primaryRendererResult == REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED + ? secondaryRendererResult + : primaryRendererResult; + } + + private int replaceStreamsOrDisableRendererForTransitionInternal( + @Nullable Renderer renderer, + MediaPeriodHolder readingPeriodHolder, + TrackSelectorResult newTrackSelectorResult, + DefaultMediaClock mediaClock) + throws ExoPlaybackException { + if (renderer == null + || !isRendererEnabled(renderer) + || (renderer == primaryRenderer && isPrimaryRendererPrewarming()) + || (renderer == secondaryRenderer && isSecondaryRendererPrewarming())) { + return REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED; + } + boolean rendererIsReadingOldStream = + renderer.getStream() != readingPeriodHolder.sampleStreams[index]; + boolean rendererShouldBeEnabled = newTrackSelectorResult.isRendererEnabled(index); + if (rendererShouldBeEnabled && !rendererIsReadingOldStream) { + // All done. + return REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED; + } + if (!renderer.isCurrentStreamFinal()) { + // The renderer stream is not final, so we can replace the sample streams immediately. + Format[] formats = getFormats(newTrackSelectorResult.selections[index]); + renderer.replaceStream( + formats, + checkNotNull(readingPeriodHolder.sampleStreams[index]), + readingPeriodHolder.getStartPositionRendererTime(), + readingPeriodHolder.getRendererOffset(), + readingPeriodHolder.info.id); + // Prevent sleeping across offload track transition else position won't get updated. + return REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED + | REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING; + } else if (renderer.isEnded()) { + // The renderer has finished playback, so we can disable it now. + disableRenderer(renderer, mediaClock); + if (!rendererShouldBeEnabled || isPrewarming()) { + maybeResetRenderer(/* resetPrimary= */ renderer == primaryRenderer); + } + return REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED; + } else { + // Need to wait until rendering of current item is finished. + return 0; + } + } + + private static Format[] getFormats(@Nullable ExoTrackSelection newSelection) { + // Build an array of formats contained by the selection. + int length = newSelection != null ? newSelection.length() : 0; + Format[] formats = new Format[length]; + for (int i = 0; i < length; i++) { + formats[i] = checkNotNull(newSelection).getFormat(i); + } + return formats; } /** Calls {@link Renderer#release()} on all {@link Renderer renderers}. */ public void release() { - renderer.release(); - requiresReset = false; + primaryRenderer.release(); + primaryRequiresReset = false; + if (secondaryRenderer != null) { + secondaryRenderer.release(); + secondaryRequiresReset = false; + } } public void setVideoOutput(@Nullable Object videoOutput) throws ExoPlaybackException { - if (renderer.getTrackType() == TRACK_TYPE_VIDEO) { - renderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, videoOutput); + if (getTrackType() != TRACK_TYPE_VIDEO) { + return; } + if (prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY + || prewarmingState == RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_SECONDARY) { + checkNotNull(secondaryRenderer).handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, videoOutput); + } else { + primaryRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, videoOutput); + } + } + + public boolean isRendererEnabled() { + boolean checkPrimary = + prewarmingState == RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY + || prewarmingState == RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY + || prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY; + return checkPrimary + ? isRendererEnabled(primaryRenderer) + : isRendererEnabled(checkNotNull(secondaryRenderer)); } private static boolean isRendererEnabled(Renderer renderer) { return renderer.getState() != STATE_DISABLED; } + + /** + * Returns the {@link Renderer} that is enabled on the provided media {@link MediaPeriodHolder + * period}. + * + *
Returns null if the renderer is not enabled on the requested period. + * + * @param period The {@link MediaPeriodHolder period} with which to retrieve the linked {@link + * Renderer} + * @return {@link Renderer} enabled on the {@link MediaPeriodHolder period} or {@code null} if the + * renderer is not enabled on the provided period. + */ + @Nullable + private Renderer getRendererReadingFromPeriod(@Nullable MediaPeriodHolder period) { + if (period == null || period.sampleStreams[index] == null) { + return null; + } + if (primaryRenderer.getStream() == period.sampleStreams[index]) { + return primaryRenderer; + } else if (secondaryRenderer != null + && secondaryRenderer.getStream() == period.sampleStreams[index]) { + return secondaryRenderer; + } + return null; + } + + /** Possible pre-warming states for primary and secondary renderers. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef({ + RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY, + RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_SECONDARY, + RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY, + RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY, + RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY + }) + @interface RendererPrewarmingState {} + + /** + * ExoPlayer is not currently transitioning between two enabled renderers for subsequent media + * items and is using the primary renderer. + */ + /* package */ static final int RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY = 0; + + /** + * ExoPlayer is not currently transitioning between two enabled renderers for subsequent media + * items and is using the secondary renderer. + */ + /* package */ static final int RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_SECONDARY = 1; + + /** + * ExoPlayer is currently pre-warming the primary renderer that is not being used for the current + * media item for a subsequent media item. + */ + /* package */ static final int RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY = 2; + + /** + * Both a primary and secondary renderer are enabled and ExoPlayer is transitioning to a media + * item using the secondary renderer. + */ + /* package */ static final int RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY = 3; + + /** + * Both a primary and secondary renderer are enabled and ExoPlayer is transitioning to a media + * item using the primary renderer. + */ + /* package */ static final int RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY = 4; + + /** Results for calls to {@link #replaceStreamsOrDisableRendererForTransition}. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef( + flag = true, + value = { + REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED, + REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING + }) + /* package */ @interface ReplaceStreamsOrDisableRendererResult {} + + /** + * The call to {@link #replaceStreamsOrDisableRendererForTransition} has completed processing + * {@link Renderer#replaceStream} or {@link Renderer#disable()} on all renderers enabled on the + * current playing period. + */ + /* package */ static final int REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED = 1; + + /** + * The call to {@link #replaceStreamsOrDisableRendererForTransition} invoked {@link + * Renderer#replaceStream} and so therefore offload should be disabled until after the media + * transition. + */ + /* package */ static final int REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING = + 1 << 1; } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RenderersFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RenderersFactory.java index 2d7592168e..ecaec1141e 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RenderersFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RenderersFactory.java @@ -16,6 +16,7 @@ package androidx.media3.exoplayer; import android.os.Handler; +import androidx.annotation.Nullable; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.audio.AudioRendererEventListener; import androidx.media3.exoplayer.metadata.MetadataOutput; @@ -42,4 +43,29 @@ public interface RenderersFactory { AudioRendererEventListener audioRendererEventListener, TextOutput textRendererOutput, MetadataOutput metadataRendererOutput); + + /** + * Provides a secondary {@link Renderer} instance for an {@link ExoPlayer} to use for pre-warming. + * + *
The created secondary {@code Renderer} should match its primary in its reported track type
+ * support and {@link RendererCapabilities}.
+ *
+ * @param renderer The primary {@code Renderer} for which to create the backup.
+ * @param eventHandler A handler to use when invoking event listeners and outputs.
+ * @param videoRendererEventListener An event listener for video renderers.
+ * @param audioRendererEventListener An event listener for audio renderers.
+ * @param textRendererOutput An output for text renderers.
+ * @param metadataRendererOutput An output for metadata renderers.
+ * @return The {@link Renderer instances}.
+ */
+ @Nullable
+ default Renderer createSecondaryRenderer(
+ Renderer renderer,
+ Handler eventHandler,
+ VideoRendererEventListener videoRendererEventListener,
+ AudioRendererEventListener audioRendererEventListener,
+ TextOutput textRendererOutput,
+ MetadataOutput metadataRendererOutput) {
+ return null;
+ }
}
diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerWithPrewarmingRenderersTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerWithPrewarmingRenderersTest.java
new file mode 100644
index 0000000000..3d72975575
--- /dev/null
+++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerWithPrewarmingRenderersTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.media3.exoplayer;
+
+import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.run;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import androidx.media3.common.Player;
+import androidx.media3.common.util.Clock;
+import androidx.media3.common.util.HandlerWrapper;
+import androidx.media3.exoplayer.audio.AudioRendererEventListener;
+import androidx.media3.exoplayer.metadata.MetadataOutput;
+import androidx.media3.exoplayer.text.TextOutput;
+import androidx.media3.exoplayer.video.VideoRendererEventListener;
+import androidx.media3.test.utils.ExoPlayerTestRunner;
+import androidx.media3.test.utils.FakeAudioRenderer;
+import androidx.media3.test.utils.FakeClock;
+import androidx.media3.test.utils.FakeMediaSource;
+import androidx.media3.test.utils.FakeTimeline;
+import androidx.media3.test.utils.FakeVideoRenderer;
+import androidx.media3.test.utils.TestExoPlayerBuilder;
+import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.collect.ImmutableList;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link ExoPlayer} with the pre-warming render feature. */
+@RunWith(AndroidJUnit4.class)
+public class ExoPlayerWithPrewarmingRenderersTest {
+
+ private Context context;
+
+ @Rule
+ public ShadowMediaCodecConfig mediaCodecConfig =
+ ShadowMediaCodecConfig.forAllSupportedMimeTypes();
+
+ @Before
+ public void setUp() {
+ context = ApplicationProvider.getApplicationContext();
+ }
+
+ @Test
+ public void play_withTwoItemsAndPrewarming_secondaryRendererisEnabledButNotStarted()
+ throws Exception {
+ Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
+ ExoPlayer player =
+ new TestExoPlayerBuilder(context)
+ .setClock(fakeClock)
+ .setRenderersFactory(
+ new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
+ .build();
+ Renderer videoRenderer = player.getRenderer(/* index= */ 0);
+ Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
+ // Set a playlist that allows a new renderer to be enabled early.
+ player.setMediaSources(
+ ImmutableList.of(
+ new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(
+ new FakeTimeline(),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT)));
+ player.prepare();
+
+ // Play a bit until the second renderer is being pre-warmed.
+ player.play();
+ run(player)
+ .untilBackgroundThreadCondition(
+ () -> secondaryVideoRenderer.getState() == Renderer.STATE_ENABLED);
+ @Renderer.State int videoState1 = videoRenderer.getState();
+ @Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
+ // Play until second item is started.
+ run(player)
+ .untilBackgroundThreadCondition(
+ () -> secondaryVideoRenderer.getState() == Renderer.STATE_STARTED);
+ @Renderer.State int videoState2 = videoRenderer.getState();
+ @Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
+ player.release();
+
+ assertThat(videoState1).isEqualTo(Renderer.STATE_STARTED);
+ assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_ENABLED);
+ assertThat(videoState2).isEqualTo(Renderer.STATE_DISABLED);
+ assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_STARTED);
+ }
+
+ @Test
+ public void
+ play_withThreeItemsAndPrewarming_playerSuccessfullyPrewarmsAndSwapsBackToPrimaryRenderer()
+ throws Exception {
+ Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
+ ExoPlayer player =
+ new TestExoPlayerBuilder(context)
+ .setClock(fakeClock)
+ .setRenderersFactory(
+ new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
+ .build();
+ Renderer videoRenderer = player.getRenderer(/* index= */ 0);
+ Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
+ // Set a playlist that allows a new renderer to be enabled early.
+ player.setMediaSources(
+ ImmutableList.of(
+ new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(
+ new FakeTimeline(),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT),
+ new FakeMediaSource(
+ new FakeTimeline(),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT)));
+ player.prepare();
+
+ // Play a bit until the secondary renderer is being pre-warmed.
+ player.play();
+ run(player)
+ .untilBackgroundThreadCondition(
+ () -> secondaryVideoRenderer.getState() == Renderer.STATE_ENABLED);
+ @Renderer.State int videoState1 = videoRenderer.getState();
+ @Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
+ // Play until until the primary renderer is being pre-warmed.
+ run(player)
+ .untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_ENABLED);
+ @Renderer.State int videoState2 = videoRenderer.getState();
+ @Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
+ // Play until past transition back to primary renderer for third media item.
+ run(player)
+ .untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_STARTED);
+ @Renderer.State int videoState3 = videoRenderer.getState();
+ @Renderer.State int secondaryVideoState3 = secondaryVideoRenderer.getState();
+ player.release();
+
+ assertThat(videoState1).isEqualTo(Renderer.STATE_STARTED);
+ assertThat(videoState2).isEqualTo(Renderer.STATE_ENABLED);
+ assertThat(videoState3).isEqualTo(Renderer.STATE_STARTED);
+ assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_ENABLED);
+ assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_STARTED);
+ assertThat(secondaryVideoState3).isEqualTo(Renderer.STATE_DISABLED);
+ }
+
+ @Test
+ public void prepare_withPeriodBetweenPlayingAndPrewarmingPeriods_playerSuccessfullyPrewarms()
+ throws Exception {
+ Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
+ ExoPlayer player =
+ new TestExoPlayerBuilder(context)
+ .setClock(fakeClock)
+ .setRenderersFactory(
+ new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
+ .build();
+ Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
+ // Set a playlist that allows a new renderer to be enabled early.
+ player.setMediaSources(
+ ImmutableList.of(
+ new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
+ new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.AUDIO_FORMAT),
+ new FakeMediaSource(
+ new FakeTimeline(),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT)));
+ player.prepare();
+
+ // Advance media periods until secondary renderer is being pre-warmed.
+ run(player)
+ .untilBackgroundThreadCondition(
+ () -> secondaryVideoRenderer.getState() == Renderer.STATE_ENABLED);
+ @Renderer.State int secondaryVideoState = secondaryVideoRenderer.getState();
+ player.release();
+
+ assertThat(secondaryVideoState).isEqualTo(Renderer.STATE_ENABLED);
+ }
+
+ @Test
+ public void
+ setPlayWhenReady_playFromPauseWithPrewarmingPrimaryRenderer_primaryRendererIsEnabledButNotStarted()
+ throws Exception {
+ Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
+ ExoPlayer player =
+ new TestExoPlayerBuilder(context)
+ .setClock(fakeClock)
+ .setRenderersFactory(
+ new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
+ .build();
+ Renderer videoRenderer = player.getRenderer(/* index= */ 0);
+ Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
+ // Set a playlist that allows a new renderer to be enabled early.
+ player.setMediaSources(
+ ImmutableList.of(
+ new FakeMediaSource(
+ new FakeTimeline(),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT),
+ new FakeMediaSource(
+ new FakeTimeline(),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT),
+ new FakeMediaSource(
+ new FakeTimeline(),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT)));
+ player.prepare();
+
+ // Play a bit until the primary renderer has been enabled, but not yet started.
+ player.play();
+ run(player)
+ .untilBackgroundThreadCondition(
+ () -> secondaryVideoRenderer.getState() == Renderer.STATE_STARTED);
+ run(player)
+ .untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_ENABLED);
+ @Renderer.State int videoState1 = videoRenderer.getState();
+ @Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
+ player.pause();
+ run(player).untilPendingCommandsAreFullyHandled();
+ @Renderer.State int videoState2 = videoRenderer.getState();
+ @Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
+ player.play();
+ run(player)
+ .untilBackgroundThreadCondition(
+ () -> secondaryVideoRenderer.getState() == Renderer.STATE_STARTED);
+ @Renderer.State int videoState3 = videoRenderer.getState();
+ @Renderer.State int secondaryVideoState3 = secondaryVideoRenderer.getState();
+ player.release();
+
+ assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
+ assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_STARTED);
+ assertThat(videoState2).isEqualTo(Renderer.STATE_ENABLED);
+ assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_ENABLED);
+ assertThat(videoState3).isEqualTo(Renderer.STATE_ENABLED);
+ assertThat(secondaryVideoState3).isEqualTo(Renderer.STATE_STARTED);
+ }
+
+ @Test
+ public void
+ setPlayWhenReady_playFromPauseWithPrewarmingNonTransitioningRenderer_rendererIsEnabledButNotStarted()
+ throws Exception {
+ Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
+ ExoPlayer player =
+ new TestExoPlayerBuilder(context)
+ .setClock(fakeClock)
+ .setRenderersFactory(
+ new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
+ .build();
+ Renderer videoRenderer = player.getRenderer(/* index= */ 0);
+ Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
+ // Set a playlist that allows a new renderer to be enabled early.
+ player.setMediaSources(
+ ImmutableList.of(
+ new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.AUDIO_FORMAT),
+ new FakeMediaSource(
+ new FakeTimeline(),
+ ExoPlayerTestRunner.VIDEO_FORMAT,
+ ExoPlayerTestRunner.AUDIO_FORMAT)));
+ player.prepare();
+
+ // Play a bit until the primary renderer has been enabled, but not yet started.
+ run(player).untilState(Player.STATE_READY);
+ player.play();
+ run(player).untilPendingCommandsAreFullyHandled();
+ @Renderer.State int videoState1 = videoRenderer.getState();
+ @Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
+ player.pause();
+ run(player).untilPendingCommandsAreFullyHandled();
+ @Renderer.State int videoState2 = videoRenderer.getState();
+ player.play();
+ run(player).untilPendingCommandsAreFullyHandled();
+ @Renderer.State int videoState3 = videoRenderer.getState();
+ player.release();
+
+ assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
+ assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_DISABLED);
+ assertThat(videoState2).isEqualTo(Renderer.STATE_ENABLED);
+ assertThat(videoState3).isEqualTo(Renderer.STATE_ENABLED);
+ }
+
+ private static class FakeRenderersFactorySupportingSecondaryVideoRenderer
+ implements RenderersFactory {
+ protected final Clock clock;
+
+ public FakeRenderersFactorySupportingSecondaryVideoRenderer(Clock clock) {
+ this.clock = clock;
+ }
+
+ @Override
+ public Renderer[] createRenderers(
+ Handler eventHandler,
+ VideoRendererEventListener videoRendererEventListener,
+ AudioRendererEventListener audioRendererEventListener,
+ TextOutput textRendererOutput,
+ MetadataOutput metadataRendererOutput) {
+ HandlerWrapper clockAwareHandler =
+ clock.createHandler(eventHandler.getLooper(), /* callback= */ null);
+ return new Renderer[] {
+ new FakeVideoRenderer(clockAwareHandler, videoRendererEventListener),
+ new FakeAudioRenderer(clockAwareHandler, audioRendererEventListener)
+ };
+ }
+
+ @Override
+ public Renderer createSecondaryRenderer(
+ Renderer renderer,
+ Handler eventHandler,
+ VideoRendererEventListener videoRendererEventListener,
+ AudioRendererEventListener audioRendererEventListener,
+ TextOutput textRendererOutput,
+ MetadataOutput metadataRendererOutput) {
+ if (renderer instanceof FakeVideoRenderer) {
+ return new FakeVideoRenderer(
+ clock.createHandler(eventHandler.getLooper(), /* callback= */ null),
+ videoRendererEventListener);
+ }
+ return null;
+ }
+ }
+}
diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PrewarmingRendererPlaybackTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PrewarmingRendererPlaybackTest.java
new file mode 100644
index 0000000000..7a4e54ad3b
--- /dev/null
+++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PrewarmingRendererPlaybackTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.media3.exoplayer.e2etest;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.os.Handler;
+import android.view.Surface;
+import androidx.media3.common.C;
+import androidx.media3.common.MediaItem;
+import androidx.media3.common.Player;
+import androidx.media3.exoplayer.ExoPlayer;
+import androidx.media3.exoplayer.Renderer;
+import androidx.media3.exoplayer.audio.AudioRendererEventListener;
+import androidx.media3.exoplayer.image.ImageDecoder;
+import androidx.media3.exoplayer.image.ImageRenderer;
+import androidx.media3.exoplayer.metadata.MetadataOutput;
+import androidx.media3.exoplayer.text.TextOutput;
+import androidx.media3.exoplayer.video.MediaCodecVideoRenderer;
+import androidx.media3.exoplayer.video.VideoRendererEventListener;
+import androidx.media3.test.utils.CapturingRenderersFactory;
+import androidx.media3.test.utils.DumpFileAsserts;
+import androidx.media3.test.utils.FakeClock;
+import androidx.media3.test.utils.robolectric.PlaybackOutput;
+import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
+import androidx.media3.test.utils.robolectric.TestPlayerRunHelper;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.collect.ImmutableList;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** End-to-end playback tests using secondary renderers. */
+@RunWith(AndroidJUnit4.class)
+public class PrewarmingRendererPlaybackTest {
+
+ private static final String TEST_AUDIO_MP4_URI = "asset:///media/mp4/sample_ac3.mp4";
+ private static final String TEST_MP4_URI = "asset:///media/mp4/sample.mp4";
+ private static final String TEST_IMAGE_URI = "asset:///media/jpeg/tokyo.jpg";
+
+ @Rule
+ public ShadowMediaCodecConfig mediaCodecConfig =
+ ShadowMediaCodecConfig.forAllSupportedMimeTypes();
+
+ @Test
+ public void playback_withTwoMediaItemsAndSecondaryVideoRenderer_dumpsCorrectOutput()
+ throws Exception {
+ Context applicationContext = ApplicationProvider.getApplicationContext();
+ CapturingRenderersWithSecondaryVideoRendererFactory capturingRenderersFactory =
+ new CapturingRenderersWithSecondaryVideoRendererFactory(applicationContext);
+ ExoPlayer player =
+ new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
+ .setClock(new FakeClock(/* isAutoAdvancing= */ true))
+ .build();
+ Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
+ player.setVideoSurface(surface);
+ PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
+ player.addMediaItems(
+ ImmutableList.of(
+ new MediaItem.Builder().setUri(TEST_MP4_URI).build(),
+ new MediaItem.Builder().setUri(TEST_MP4_URI).build()));
+
+ player.prepare();
+ player.play();
+ TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
+ player.release();
+ surface.release();
+
+ DumpFileAsserts.assertOutput(
+ applicationContext,
+ playbackOutput,
+ /* dumpFile= */ "playbackdumps/prewarmingRenderer/"
+ + "twoItemsPlaylist-withSecondaryVideoRenderer"
+ + ".dump");
+ }
+
+ @Test
+ public void playback_withThreeItemsAndSecondaryVideoRenderer_dumpsCorrectOutput()
+ throws Exception {
+ Context applicationContext = ApplicationProvider.getApplicationContext();
+ CapturingRenderersWithSecondaryVideoRendererFactory capturingRenderersFactory =
+ new CapturingRenderersWithSecondaryVideoRendererFactory(applicationContext);
+ ExoPlayer player =
+ new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
+ .setClock(new FakeClock(/* isAutoAdvancing= */ true))
+ .build();
+ Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
+ player.setVideoSurface(surface);
+ PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
+ player.addMediaItems(
+ ImmutableList.of(
+ new MediaItem.Builder().setUri(TEST_MP4_URI).build(),
+ new MediaItem.Builder().setUri(TEST_MP4_URI).build(),
+ new MediaItem.Builder().setUri(TEST_MP4_URI).build()));
+ // Disable audio renderer for simpler dump file.
+ player.setTrackSelectionParameters(
+ player
+ .getTrackSelectionParameters()
+ .buildUpon()
+ .setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, true)
+ .build());
+ player.prepare();
+ player.play();
+
+ TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
+
+ player.release();
+ surface.release();
+
+ DumpFileAsserts.assertOutput(
+ applicationContext,
+ playbackOutput,
+ "playbackdumps/prewarmingRenderer/"
+ + "threeItemPlaylist-withSecondaryVideoRenderer"
+ + ".dump");
+ }
+
+ @Test
+ public void playback_withMultipleMediaItemsWithClippingConfigurations_dumpsCorrectOutput()
+ throws Exception {
+ Context applicationContext = ApplicationProvider.getApplicationContext();
+ CapturingRenderersWithSecondaryVideoRendererFactory capturingRenderersFactory =
+ new CapturingRenderersWithSecondaryVideoRendererFactory(applicationContext);
+ ExoPlayer player =
+ new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
+ .setClock(new FakeClock(/* isAutoAdvancing= */ true))
+ .build();
+ Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
+ player.setVideoSurface(surface);
+ PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
+ player.addMediaItem(
+ new MediaItem.Builder()
+ .setUri(TEST_MP4_URI)
+ .setClippingConfiguration(
+ new MediaItem.ClippingConfiguration.Builder()
+ .setStartPositionMs(0)
+ .setEndPositionMs(250)
+ .build())
+ .build());
+ player.addMediaItem(
+ new MediaItem.Builder()
+ .setUri(TEST_MP4_URI)
+ .setClippingConfiguration(
+ new MediaItem.ClippingConfiguration.Builder()
+ .setStartPositionMs(250)
+ .setEndPositionMs(600)
+ .build())
+ .build());
+ player.addMediaItem(
+ new MediaItem.Builder()
+ .setUri(TEST_MP4_URI)
+ .setClippingConfiguration(
+ new MediaItem.ClippingConfiguration.Builder()
+ .setStartPositionMs(600)
+ .setEndPositionMs(C.TIME_END_OF_SOURCE)
+ .build())
+ .build());
+
+ player.prepare();
+ player.play();
+ TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
+ player.release();
+ surface.release();
+
+ DumpFileAsserts.assertOutput(
+ applicationContext,
+ playbackOutput,
+ "playbackdumps/prewarmingRenderer/"
+ + "threeItemPlaylist-withClippingConfigurations"
+ + ".dump");
+ }
+
+ @Test
+ public void playback_withPrewarmingNonTransitioningRenderer_dumpsCorrectOutput()
+ throws Exception {
+ Context applicationContext = ApplicationProvider.getApplicationContext();
+ CapturingRenderersWithSecondaryVideoRendererFactory capturingRenderersFactory =
+ new CapturingRenderersWithSecondaryVideoRendererFactory(applicationContext);
+ ExoPlayer player =
+ new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
+ .setClock(new FakeClock(/* isAutoAdvancing= */ true))
+ .build();
+ Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
+ player.setVideoSurface(surface);
+ PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
+ player.addMediaItem(new MediaItem.Builder().setUri(TEST_AUDIO_MP4_URI).build());
+ player.addMediaItem(
+ new MediaItem.Builder()
+ .setUri(TEST_MP4_URI)
+ .setClippingConfiguration(
+ new MediaItem.ClippingConfiguration.Builder()
+ .setStartPositionMs(300)
+ .setEndPositionMs(600)
+ .build())
+ .build());
+
+ player.prepare();
+ player.play();
+ TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
+ player.release();
+ surface.release();
+
+ DumpFileAsserts.assertOutput(
+ applicationContext,
+ playbackOutput,
+ "playbackdumps/prewarmingRenderer/"
+ + "twoItemPlaylist-withPrewarmingNonTransitioningRenderer"
+ + ".dump");
+ }
+
+ @Test
+ public void playback_withImageVideoPlaylistAndSecondaryVideoRendererOnly_dumpsCorrectOutput()
+ throws Exception {
+ Context applicationContext = ApplicationProvider.getApplicationContext();
+ CapturingRenderersWithSecondaryVideoAndImageRenderersFactory capturingRenderersFactory =
+ new CapturingRenderersWithSecondaryVideoAndImageRenderersFactory(applicationContext);
+ ExoPlayer player =
+ new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
+ .setClock(new FakeClock(/* isAutoAdvancing= */ true))
+ .build();
+ Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
+ player.setVideoSurface(surface);
+ PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
+ player.addMediaItems(
+ ImmutableList.of(
+ new MediaItem.Builder().setUri(TEST_IMAGE_URI).setImageDurationMs(1000).build(),
+ new MediaItem.Builder().setUri(TEST_MP4_URI).build(),
+ new MediaItem.Builder().setUri(TEST_IMAGE_URI).setImageDurationMs(1000).build()));
+
+ player.prepare();
+ player.play();
+ TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
+ player.release();
+ surface.release();
+
+ DumpFileAsserts.assertOutput(
+ applicationContext,
+ playbackOutput,
+ /* dumpFile= */ "playbackdumps/prewarmingRenderer/"
+ + "imageVideoPlaylist-withSecondaryRenderers"
+ + ".dump");
+ }
+
+ /** This class extends {@link CapturingRenderersFactory} to provide a secondary video renderer. */
+ private static final class CapturingRenderersWithSecondaryVideoRendererFactory
+ extends CapturingRenderersFactory {
+
+ /**
+ * Creates an instance.
+ *
+ * @param context The {@link Context}.
+ */
+ public CapturingRenderersWithSecondaryVideoRendererFactory(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Renderer createSecondaryRenderer(
+ Renderer renderer,
+ Handler eventHandler,
+ VideoRendererEventListener videoRendererEventListener,
+ AudioRendererEventListener audioRendererEventListener,
+ TextOutput textRendererOutput,
+ MetadataOutput metadataRendererOutput) {
+ if (renderer.getClass().getSuperclass() == MediaCodecVideoRenderer.class) {
+ return createMediaCodecVideoRenderer(eventHandler, videoRendererEventListener);
+ }
+ return null;
+ }
+ }
+
+ /** This class extends {@link CapturingRenderersFactory} to provide a secondary video renderer. */
+ private static final class CapturingRenderersWithSecondaryVideoAndImageRenderersFactory
+ extends CapturingRenderersFactory {
+
+ /**
+ * Creates an instance.
+ *
+ * @param context The {@link Context}.
+ */
+ public CapturingRenderersWithSecondaryVideoAndImageRenderersFactory(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Renderer createSecondaryRenderer(
+ Renderer renderer,
+ Handler eventHandler,
+ VideoRendererEventListener videoRendererEventListener,
+ AudioRendererEventListener audioRendererEventListener,
+ TextOutput textRendererOutput,
+ MetadataOutput metadataRendererOutput) {
+ if (renderer.getClass().getSuperclass() == MediaCodecVideoRenderer.class) {
+ return createMediaCodecVideoRenderer(eventHandler, videoRendererEventListener);
+ } else if (renderer.getClass() == ImageRenderer.class) {
+ return new ImageRenderer(ImageDecoder.Factory.DEFAULT, /* imageOutput= */ null);
+ }
+ return null;
+ }
+ }
+}
diff --git a/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/imageVideoPlaylist-withSecondaryRenderers.dump b/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/imageVideoPlaylist-withSecondaryRenderers.dump
new file mode 100644
index 0000000000..53691b8559
--- /dev/null
+++ b/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/imageVideoPlaylist-withSecondaryRenderers.dump
@@ -0,0 +1,692 @@
+MediaCodecAdapter (exotest.audio.aac):
+ inputBuffers:
+ count = 46
+ input buffer #0:
+ timeUs = 1000001044000
+ contents = length 23, hash 47DE9131
+ input buffer #1:
+ timeUs = 1000001067219
+ contents = length 6, hash 31EC5206
+ input buffer #2:
+ timeUs = 1000001090439
+ contents = length 148, hash 894A176B
+ input buffer #3:
+ timeUs = 1000001113659
+ contents = length 189, hash CEF235A1
+ input buffer #4:
+ timeUs = 1000001136879
+ contents = length 205, hash BBF5F7B0
+ input buffer #5:
+ timeUs = 1000001160099
+ contents = length 210, hash F278B193
+ input buffer #6:
+ timeUs = 1000001183319
+ contents = length 210, hash 82DA1589
+ input buffer #7:
+ timeUs = 1000001206539
+ contents = length 207, hash 5BE231DF
+ input buffer #8:
+ timeUs = 1000001229759
+ contents = length 225, hash 18819EE1
+ input buffer #9:
+ timeUs = 1000001252979
+ contents = length 215, hash CA7FA67B
+ input buffer #10:
+ timeUs = 1000001276199
+ contents = length 211, hash 581A1C18
+ input buffer #11:
+ timeUs = 1000001299419
+ contents = length 216, hash ADB88187
+ input buffer #12:
+ timeUs = 1000001322639
+ contents = length 229, hash 2E8BA4DC
+ input buffer #13:
+ timeUs = 1000001345859
+ contents = length 232, hash 22F0C510
+ input buffer #14:
+ timeUs = 1000001369079
+ contents = length 235, hash 867AD0DC
+ input buffer #15:
+ timeUs = 1000001392299
+ contents = length 231, hash 84E823A8
+ input buffer #16:
+ timeUs = 1000001415519
+ contents = length 226, hash 1BEF3A95
+ input buffer #17:
+ timeUs = 1000001438739
+ contents = length 216, hash EAA345AE
+ input buffer #18:
+ timeUs = 1000001461959
+ contents = length 229, hash 6957411F
+ input buffer #19:
+ timeUs = 1000001485179
+ contents = length 219, hash 41275022
+ input buffer #20:
+ timeUs = 1000001508399
+ contents = length 241, hash 6495DF96
+ input buffer #21:
+ timeUs = 1000001531619
+ contents = length 228, hash 63D95906
+ input buffer #22:
+ timeUs = 1000001554839
+ contents = length 238, hash 34F676F9
+ input buffer #23:
+ timeUs = 1000001578058
+ contents = length 234, hash E5CBC045
+ input buffer #24:
+ timeUs = 1000001601278
+ contents = length 231, hash 5FC43661
+ input buffer #25:
+ timeUs = 1000001624498
+ contents = length 217, hash 682708ED
+ input buffer #26:
+ timeUs = 1000001647718
+ contents = length 239, hash D43780FC
+ input buffer #27:
+ timeUs = 1000001670938
+ contents = length 243, hash C5E17980
+ input buffer #28:
+ timeUs = 1000001694158
+ contents = length 231, hash AC5837BA
+ input buffer #29:
+ timeUs = 1000001717378
+ contents = length 230, hash 169EE895
+ input buffer #30:
+ timeUs = 1000001740598
+ contents = length 238, hash C48FF3F1
+ input buffer #31:
+ timeUs = 1000001763818
+ contents = length 225, hash 531E4599
+ input buffer #32:
+ timeUs = 1000001787038
+ contents = length 232, hash CB3C6B8D
+ input buffer #33:
+ timeUs = 1000001810258
+ contents = length 243, hash F8C94C7
+ input buffer #34:
+ timeUs = 1000001833478
+ contents = length 232, hash A646A7D0
+ input buffer #35:
+ timeUs = 1000001856698
+ contents = length 237, hash E8B787A5
+ input buffer #36:
+ timeUs = 1000001879918
+ contents = length 228, hash 3FA7A29F
+ input buffer #37:
+ timeUs = 1000001903138
+ contents = length 235, hash B9B33B0A
+ input buffer #38:
+ timeUs = 1000001926358
+ contents = length 264, hash 71A4869E
+ input buffer #39:
+ timeUs = 1000001949578
+ contents = length 257, hash D049B54C
+ input buffer #40:
+ timeUs = 1000001972798
+ contents = length 227, hash 66757231
+ input buffer #41:
+ timeUs = 1000001996018
+ contents = length 227, hash BD374F1B
+ input buffer #42:
+ timeUs = 1000002019238
+ contents = length 235, hash 999477F6
+ input buffer #43:
+ timeUs = 1000002042458
+ contents = length 229, hash FFF98DF0
+ input buffer #44:
+ timeUs = 1000002065678
+ contents = length 6, hash 31B22286
+ input buffer #45:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 45
+ output buffer #0:
+ timeUs = 1000001044000
+ size = 0
+ rendered = false
+ output buffer #1:
+ timeUs = 1000001067219
+ size = 0
+ rendered = false
+ output buffer #2:
+ timeUs = 1000001090439
+ size = 0
+ rendered = false
+ output buffer #3:
+ timeUs = 1000001113659
+ size = 0
+ rendered = false
+ output buffer #4:
+ timeUs = 1000001136879
+ size = 0
+ rendered = false
+ output buffer #5:
+ timeUs = 1000001160099
+ size = 0
+ rendered = false
+ output buffer #6:
+ timeUs = 1000001183319
+ size = 0
+ rendered = false
+ output buffer #7:
+ timeUs = 1000001206539
+ size = 0
+ rendered = false
+ output buffer #8:
+ timeUs = 1000001229759
+ size = 0
+ rendered = false
+ output buffer #9:
+ timeUs = 1000001252979
+ size = 0
+ rendered = false
+ output buffer #10:
+ timeUs = 1000001276199
+ size = 0
+ rendered = false
+ output buffer #11:
+ timeUs = 1000001299419
+ size = 0
+ rendered = false
+ output buffer #12:
+ timeUs = 1000001322639
+ size = 0
+ rendered = false
+ output buffer #13:
+ timeUs = 1000001345859
+ size = 0
+ rendered = false
+ output buffer #14:
+ timeUs = 1000001369079
+ size = 0
+ rendered = false
+ output buffer #15:
+ timeUs = 1000001392299
+ size = 0
+ rendered = false
+ output buffer #16:
+ timeUs = 1000001415519
+ size = 0
+ rendered = false
+ output buffer #17:
+ timeUs = 1000001438739
+ size = 0
+ rendered = false
+ output buffer #18:
+ timeUs = 1000001461959
+ size = 0
+ rendered = false
+ output buffer #19:
+ timeUs = 1000001485179
+ size = 0
+ rendered = false
+ output buffer #20:
+ timeUs = 1000001508399
+ size = 0
+ rendered = false
+ output buffer #21:
+ timeUs = 1000001531619
+ size = 0
+ rendered = false
+ output buffer #22:
+ timeUs = 1000001554839
+ size = 0
+ rendered = false
+ output buffer #23:
+ timeUs = 1000001578058
+ size = 0
+ rendered = false
+ output buffer #24:
+ timeUs = 1000001601278
+ size = 0
+ rendered = false
+ output buffer #25:
+ timeUs = 1000001624498
+ size = 0
+ rendered = false
+ output buffer #26:
+ timeUs = 1000001647718
+ size = 0
+ rendered = false
+ output buffer #27:
+ timeUs = 1000001670938
+ size = 0
+ rendered = false
+ output buffer #28:
+ timeUs = 1000001694158
+ size = 0
+ rendered = false
+ output buffer #29:
+ timeUs = 1000001717378
+ size = 0
+ rendered = false
+ output buffer #30:
+ timeUs = 1000001740598
+ size = 0
+ rendered = false
+ output buffer #31:
+ timeUs = 1000001763818
+ size = 0
+ rendered = false
+ output buffer #32:
+ timeUs = 1000001787038
+ size = 0
+ rendered = false
+ output buffer #33:
+ timeUs = 1000001810258
+ size = 0
+ rendered = false
+ output buffer #34:
+ timeUs = 1000001833478
+ size = 0
+ rendered = false
+ output buffer #35:
+ timeUs = 1000001856698
+ size = 0
+ rendered = false
+ output buffer #36:
+ timeUs = 1000001879918
+ size = 0
+ rendered = false
+ output buffer #37:
+ timeUs = 1000001903138
+ size = 0
+ rendered = false
+ output buffer #38:
+ timeUs = 1000001926358
+ size = 0
+ rendered = false
+ output buffer #39:
+ timeUs = 1000001949578
+ size = 0
+ rendered = false
+ output buffer #40:
+ timeUs = 1000001972798
+ size = 0
+ rendered = false
+ output buffer #41:
+ timeUs = 1000001996018
+ size = 0
+ rendered = false
+ output buffer #42:
+ timeUs = 1000002019238
+ size = 0
+ rendered = false
+ output buffer #43:
+ timeUs = 1000002042458
+ size = 0
+ rendered = false
+ output buffer #44:
+ timeUs = 1000002065678
+ size = 0
+ rendered = false
+MediaCodecAdapter (exotest.video.avc):
+ inputBuffers:
+ count = 31
+ input buffer #0:
+ timeUs = 1000001000000
+ contents = length 36692, hash D216076E
+ input buffer #1:
+ timeUs = 1000001066733
+ contents = length 5312, hash D45D3CA0
+ input buffer #2:
+ timeUs = 1000001033366
+ contents = length 599, hash 1BE7812D
+ input buffer #3:
+ timeUs = 1000001200200
+ contents = length 7735, hash 4490F110
+ input buffer #4:
+ timeUs = 1000001133466
+ contents = length 987, hash 560B5036
+ input buffer #5:
+ timeUs = 1000001100100
+ contents = length 673, hash ED7CD8C7
+ input buffer #6:
+ timeUs = 1000001166833
+ contents = length 523, hash 3020DF50
+ input buffer #7:
+ timeUs = 1000001333666
+ contents = length 6061, hash 736C72B2
+ input buffer #8:
+ timeUs = 1000001266933
+ contents = length 992, hash FE132F23
+ input buffer #9:
+ timeUs = 1000001233566
+ contents = length 623, hash 5B2C1816
+ input buffer #10:
+ timeUs = 1000001300300
+ contents = length 421, hash 742E69C1
+ input buffer #11:
+ timeUs = 1000001433766
+ contents = length 4899, hash F72F86A1
+ input buffer #12:
+ timeUs = 1000001400400
+ contents = length 568, hash 519A8E50
+ input buffer #13:
+ timeUs = 1000001367033
+ contents = length 620, hash 3990AA39
+ input buffer #14:
+ timeUs = 1000001567233
+ contents = length 5450, hash F06EC4AA
+ input buffer #15:
+ timeUs = 1000001500500
+ contents = length 1051, hash 92DFA63A
+ input buffer #16:
+ timeUs = 1000001467133
+ contents = length 874, hash 69587FB4
+ input buffer #17:
+ timeUs = 1000001533866
+ contents = length 781, hash 36BE495B
+ input buffer #18:
+ timeUs = 1000001700700
+ contents = length 4725, hash AC0C8CD3
+ input buffer #19:
+ timeUs = 1000001633966
+ contents = length 1022, hash 5D8BFF34
+ input buffer #20:
+ timeUs = 1000001600600
+ contents = length 790, hash 99413A99
+ input buffer #21:
+ timeUs = 1000001667333
+ contents = length 610, hash 5E129290
+ input buffer #22:
+ timeUs = 1000001834166
+ contents = length 2751, hash 769974CB
+ input buffer #23:
+ timeUs = 1000001767433
+ contents = length 745, hash B78A477A
+ input buffer #24:
+ timeUs = 1000001734066
+ contents = length 621, hash CF741E7A
+ input buffer #25:
+ timeUs = 1000001800800
+ contents = length 505, hash 1DB4894E
+ input buffer #26:
+ timeUs = 1000001967633
+ contents = length 1268, hash C15348DC
+ input buffer #27:
+ timeUs = 1000001900900
+ contents = length 880, hash C2DE85D0
+ input buffer #28:
+ timeUs = 1000001867533
+ contents = length 530, hash C98BC6A8
+ input buffer #29:
+ timeUs = 1000001934266
+ contents = length 568, hash 4FE5C8EA
+ input buffer #30:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 30
+ output buffer #0:
+ timeUs = 1000001000000
+ size = 36692
+ rendered = true
+ output buffer #1:
+ timeUs = 1000001066733
+ size = 5312
+ rendered = true
+ output buffer #2:
+ timeUs = 1000001033366
+ size = 599
+ rendered = true
+ output buffer #3:
+ timeUs = 1000001200200
+ size = 7735
+ rendered = true
+ output buffer #4:
+ timeUs = 1000001133466
+ size = 987
+ rendered = true
+ output buffer #5:
+ timeUs = 1000001100100
+ size = 673
+ rendered = true
+ output buffer #6:
+ timeUs = 1000001166833
+ size = 523
+ rendered = true
+ output buffer #7:
+ timeUs = 1000001333666
+ size = 6061
+ rendered = true
+ output buffer #8:
+ timeUs = 1000001266933
+ size = 992
+ rendered = true
+ output buffer #9:
+ timeUs = 1000001233566
+ size = 623
+ rendered = true
+ output buffer #10:
+ timeUs = 1000001300300
+ size = 421
+ rendered = true
+ output buffer #11:
+ timeUs = 1000001433766
+ size = 4899
+ rendered = true
+ output buffer #12:
+ timeUs = 1000001400400
+ size = 568
+ rendered = true
+ output buffer #13:
+ timeUs = 1000001367033
+ size = 620
+ rendered = true
+ output buffer #14:
+ timeUs = 1000001567233
+ size = 5450
+ rendered = true
+ output buffer #15:
+ timeUs = 1000001500500
+ size = 1051
+ rendered = true
+ output buffer #16:
+ timeUs = 1000001467133
+ size = 874
+ rendered = true
+ output buffer #17:
+ timeUs = 1000001533866
+ size = 781
+ rendered = true
+ output buffer #18:
+ timeUs = 1000001700700
+ size = 4725
+ rendered = true
+ output buffer #19:
+ timeUs = 1000001633966
+ size = 1022
+ rendered = true
+ output buffer #20:
+ timeUs = 1000001600600
+ size = 790
+ rendered = true
+ output buffer #21:
+ timeUs = 1000001667333
+ size = 610
+ rendered = true
+ output buffer #22:
+ timeUs = 1000001834166
+ size = 2751
+ rendered = true
+ output buffer #23:
+ timeUs = 1000001767433
+ size = 745
+ rendered = true
+ output buffer #24:
+ timeUs = 1000001734066
+ size = 621
+ rendered = true
+ output buffer #25:
+ timeUs = 1000001800800
+ size = 505
+ rendered = true
+ output buffer #26:
+ timeUs = 1000001967633
+ size = 1268
+ rendered = true
+ output buffer #27:
+ timeUs = 1000001900900
+ size = 880
+ rendered = true
+ output buffer #28:
+ timeUs = 1000001867533
+ size = 530
+ rendered = true
+ output buffer #29:
+ timeUs = 1000001934266
+ size = 568
+ rendered = true
+AudioSink:
+ buffer count = 45
+ config:
+ pcmEncoding = 2
+ channelCount = 1
+ sampleRate = 44100
+ buffer #0:
+ time = 1000001044000
+ data = 1
+ buffer #1:
+ time = 1000001067219
+ data = 1
+ buffer #2:
+ time = 1000001090439
+ data = 1
+ buffer #3:
+ time = 1000001113659
+ data = 1
+ buffer #4:
+ time = 1000001136879
+ data = 1
+ buffer #5:
+ time = 1000001160099
+ data = 1
+ buffer #6:
+ time = 1000001183319
+ data = 1
+ buffer #7:
+ time = 1000001206539
+ data = 1
+ buffer #8:
+ time = 1000001229759
+ data = 1
+ buffer #9:
+ time = 1000001252979
+ data = 1
+ buffer #10:
+ time = 1000001276199
+ data = 1
+ buffer #11:
+ time = 1000001299419
+ data = 1
+ buffer #12:
+ time = 1000001322639
+ data = 1
+ buffer #13:
+ time = 1000001345859
+ data = 1
+ buffer #14:
+ time = 1000001369079
+ data = 1
+ buffer #15:
+ time = 1000001392299
+ data = 1
+ buffer #16:
+ time = 1000001415519
+ data = 1
+ buffer #17:
+ time = 1000001438739
+ data = 1
+ buffer #18:
+ time = 1000001461959
+ data = 1
+ buffer #19:
+ time = 1000001485179
+ data = 1
+ buffer #20:
+ time = 1000001508399
+ data = 1
+ buffer #21:
+ time = 1000001531619
+ data = 1
+ buffer #22:
+ time = 1000001554839
+ data = 1
+ buffer #23:
+ time = 1000001578058
+ data = 1
+ buffer #24:
+ time = 1000001601278
+ data = 1
+ buffer #25:
+ time = 1000001624498
+ data = 1
+ buffer #26:
+ time = 1000001647718
+ data = 1
+ buffer #27:
+ time = 1000001670938
+ data = 1
+ buffer #28:
+ time = 1000001694158
+ data = 1
+ buffer #29:
+ time = 1000001717378
+ data = 1
+ buffer #30:
+ time = 1000001740598
+ data = 1
+ buffer #31:
+ time = 1000001763818
+ data = 1
+ buffer #32:
+ time = 1000001787038
+ data = 1
+ buffer #33:
+ time = 1000001810258
+ data = 1
+ buffer #34:
+ time = 1000001833478
+ data = 1
+ buffer #35:
+ time = 1000001856698
+ data = 1
+ buffer #36:
+ time = 1000001879918
+ data = 1
+ buffer #37:
+ time = 1000001903138
+ data = 1
+ buffer #38:
+ time = 1000001926358
+ data = 1
+ buffer #39:
+ time = 1000001949578
+ data = 1
+ buffer #40:
+ time = 1000001972798
+ data = 1
+ buffer #41:
+ time = 1000001996018
+ data = 1
+ buffer #42:
+ time = 1000002019238
+ data = 1
+ buffer #43:
+ time = 1000002042458
+ data = 1
+ buffer #44:
+ time = 1000002065678
+ data = 1
+ImageOutput:
+ rendered image count = 2
+ image output #1:
+ presentationTimeUs = 0
+ bitmap hash = -1041353260
+ image output #2:
+ presentationTimeUs = 0
+ bitmap hash = -1041353260
diff --git a/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/threeItemPlaylist-withClippingConfigurations.dump b/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/threeItemPlaylist-withClippingConfigurations.dump
new file mode 100644
index 0000000000..5468d6bb7d
--- /dev/null
+++ b/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/threeItemPlaylist-withClippingConfigurations.dump
@@ -0,0 +1,895 @@
+MediaCodecAdapter (exotest.audio.aac):
+ inputBuffers:
+ count = 48
+ input buffer #0:
+ timeUs = 1000000044000
+ contents = length 23, hash 47DE9131
+ input buffer #1:
+ timeUs = 1000000067219
+ contents = length 6, hash 31EC5206
+ input buffer #2:
+ timeUs = 1000000090439
+ contents = length 148, hash 894A176B
+ input buffer #3:
+ timeUs = 1000000113659
+ contents = length 189, hash CEF235A1
+ input buffer #4:
+ timeUs = 1000000136879
+ contents = length 205, hash BBF5F7B0
+ input buffer #5:
+ timeUs = 1000000160099
+ contents = length 210, hash F278B193
+ input buffer #6:
+ timeUs = 1000000183319
+ contents = length 210, hash 82DA1589
+ input buffer #7:
+ timeUs = 1000000206539
+ contents = length 207, hash 5BE231DF
+ input buffer #8:
+ timeUs = 1000000229759
+ contents = length 225, hash 18819EE1
+ input buffer #9:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ input buffer #10:
+ timeUs = 1000000252979
+ contents = length 215, hash CA7FA67B
+ input buffer #11:
+ timeUs = 1000000276199
+ contents = length 211, hash 581A1C18
+ input buffer #12:
+ timeUs = 1000000299419
+ contents = length 216, hash ADB88187
+ input buffer #13:
+ timeUs = 1000000322639
+ contents = length 229, hash 2E8BA4DC
+ input buffer #14:
+ timeUs = 1000000345859
+ contents = length 232, hash 22F0C510
+ input buffer #15:
+ timeUs = 1000000369079
+ contents = length 235, hash 867AD0DC
+ input buffer #16:
+ timeUs = 1000000392299
+ contents = length 231, hash 84E823A8
+ input buffer #17:
+ timeUs = 1000000415519
+ contents = length 226, hash 1BEF3A95
+ input buffer #18:
+ timeUs = 1000000438739
+ contents = length 216, hash EAA345AE
+ input buffer #19:
+ timeUs = 1000000461959
+ contents = length 229, hash 6957411F
+ input buffer #20:
+ timeUs = 1000000485179
+ contents = length 219, hash 41275022
+ input buffer #21:
+ timeUs = 1000000508399
+ contents = length 241, hash 6495DF96
+ input buffer #22:
+ timeUs = 1000000531619
+ contents = length 228, hash 63D95906
+ input buffer #23:
+ timeUs = 1000000554839
+ contents = length 238, hash 34F676F9
+ input buffer #24:
+ timeUs = 1000000578058
+ contents = length 234, hash E5CBC045
+ input buffer #25:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ input buffer #26:
+ timeUs = 1000000601278
+ contents = length 231, hash 5FC43661
+ input buffer #27:
+ timeUs = 1000000624498
+ contents = length 217, hash 682708ED
+ input buffer #28:
+ timeUs = 1000000647718
+ contents = length 239, hash D43780FC
+ input buffer #29:
+ timeUs = 1000000670938
+ contents = length 243, hash C5E17980
+ input buffer #30:
+ timeUs = 1000000694158
+ contents = length 231, hash AC5837BA
+ input buffer #31:
+ timeUs = 1000000717378
+ contents = length 230, hash 169EE895
+ input buffer #32:
+ timeUs = 1000000740598
+ contents = length 238, hash C48FF3F1
+ input buffer #33:
+ timeUs = 1000000763818
+ contents = length 225, hash 531E4599
+ input buffer #34:
+ timeUs = 1000000787038
+ contents = length 232, hash CB3C6B8D
+ input buffer #35:
+ timeUs = 1000000810258
+ contents = length 243, hash F8C94C7
+ input buffer #36:
+ timeUs = 1000000833478
+ contents = length 232, hash A646A7D0
+ input buffer #37:
+ timeUs = 1000000856698
+ contents = length 237, hash E8B787A5
+ input buffer #38:
+ timeUs = 1000000879918
+ contents = length 228, hash 3FA7A29F
+ input buffer #39:
+ timeUs = 1000000903138
+ contents = length 235, hash B9B33B0A
+ input buffer #40:
+ timeUs = 1000000926358
+ contents = length 264, hash 71A4869E
+ input buffer #41:
+ timeUs = 1000000949578
+ contents = length 257, hash D049B54C
+ input buffer #42:
+ timeUs = 1000000972798
+ contents = length 227, hash 66757231
+ input buffer #43:
+ timeUs = 1000000996018
+ contents = length 227, hash BD374F1B
+ input buffer #44:
+ timeUs = 1000001019238
+ contents = length 235, hash 999477F6
+ input buffer #45:
+ timeUs = 1000001042458
+ contents = length 229, hash FFF98DF0
+ input buffer #46:
+ timeUs = 1000001065678
+ contents = length 6, hash 31B22286
+ input buffer #47:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 45
+ output buffer #0:
+ timeUs = 1000000044000
+ size = 0
+ rendered = false
+ output buffer #1:
+ timeUs = 1000000067219
+ size = 0
+ rendered = false
+ output buffer #2:
+ timeUs = 1000000090439
+ size = 0
+ rendered = false
+ output buffer #3:
+ timeUs = 1000000113659
+ size = 0
+ rendered = false
+ output buffer #4:
+ timeUs = 1000000136879
+ size = 0
+ rendered = false
+ output buffer #5:
+ timeUs = 1000000160099
+ size = 0
+ rendered = false
+ output buffer #6:
+ timeUs = 1000000183319
+ size = 0
+ rendered = false
+ output buffer #7:
+ timeUs = 1000000206539
+ size = 0
+ rendered = false
+ output buffer #8:
+ timeUs = 1000000229759
+ size = 0
+ rendered = false
+ output buffer #9:
+ timeUs = 1000000252979
+ size = 0
+ rendered = false
+ output buffer #10:
+ timeUs = 1000000276199
+ size = 0
+ rendered = false
+ output buffer #11:
+ timeUs = 1000000299419
+ size = 0
+ rendered = false
+ output buffer #12:
+ timeUs = 1000000322639
+ size = 0
+ rendered = false
+ output buffer #13:
+ timeUs = 1000000345859
+ size = 0
+ rendered = false
+ output buffer #14:
+ timeUs = 1000000369079
+ size = 0
+ rendered = false
+ output buffer #15:
+ timeUs = 1000000392299
+ size = 0
+ rendered = false
+ output buffer #16:
+ timeUs = 1000000415519
+ size = 0
+ rendered = false
+ output buffer #17:
+ timeUs = 1000000438739
+ size = 0
+ rendered = false
+ output buffer #18:
+ timeUs = 1000000461959
+ size = 0
+ rendered = false
+ output buffer #19:
+ timeUs = 1000000485179
+ size = 0
+ rendered = false
+ output buffer #20:
+ timeUs = 1000000508399
+ size = 0
+ rendered = false
+ output buffer #21:
+ timeUs = 1000000531619
+ size = 0
+ rendered = false
+ output buffer #22:
+ timeUs = 1000000554839
+ size = 0
+ rendered = false
+ output buffer #23:
+ timeUs = 1000000578058
+ size = 0
+ rendered = false
+ output buffer #24:
+ timeUs = 1000000601278
+ size = 0
+ rendered = false
+ output buffer #25:
+ timeUs = 1000000624498
+ size = 0
+ rendered = false
+ output buffer #26:
+ timeUs = 1000000647718
+ size = 0
+ rendered = false
+ output buffer #27:
+ timeUs = 1000000670938
+ size = 0
+ rendered = false
+ output buffer #28:
+ timeUs = 1000000694158
+ size = 0
+ rendered = false
+ output buffer #29:
+ timeUs = 1000000717378
+ size = 0
+ rendered = false
+ output buffer #30:
+ timeUs = 1000000740598
+ size = 0
+ rendered = false
+ output buffer #31:
+ timeUs = 1000000763818
+ size = 0
+ rendered = false
+ output buffer #32:
+ timeUs = 1000000787038
+ size = 0
+ rendered = false
+ output buffer #33:
+ timeUs = 1000000810258
+ size = 0
+ rendered = false
+ output buffer #34:
+ timeUs = 1000000833478
+ size = 0
+ rendered = false
+ output buffer #35:
+ timeUs = 1000000856698
+ size = 0
+ rendered = false
+ output buffer #36:
+ timeUs = 1000000879918
+ size = 0
+ rendered = false
+ output buffer #37:
+ timeUs = 1000000903138
+ size = 0
+ rendered = false
+ output buffer #38:
+ timeUs = 1000000926358
+ size = 0
+ rendered = false
+ output buffer #39:
+ timeUs = 1000000949578
+ size = 0
+ rendered = false
+ output buffer #40:
+ timeUs = 1000000972798
+ size = 0
+ rendered = false
+ output buffer #41:
+ timeUs = 1000000996018
+ size = 0
+ rendered = false
+ output buffer #42:
+ timeUs = 1000001019238
+ size = 0
+ rendered = false
+ output buffer #43:
+ timeUs = 1000001042458
+ size = 0
+ rendered = false
+ output buffer #44:
+ timeUs = 1000001065678
+ size = 0
+ rendered = false
+MediaCodecAdapter (exotest.video.avc):
+ inputBuffers:
+ count = 8
+ input buffer #0:
+ timeUs = 1000000000000
+ contents = length 36692, hash D216076E
+ input buffer #1:
+ timeUs = 1000000066733
+ contents = length 5312, hash D45D3CA0
+ input buffer #2:
+ timeUs = 1000000033366
+ contents = length 599, hash 1BE7812D
+ input buffer #3:
+ timeUs = 1000000200200
+ contents = length 7735, hash 4490F110
+ input buffer #4:
+ timeUs = 1000000133466
+ contents = length 987, hash 560B5036
+ input buffer #5:
+ timeUs = 1000000100100
+ contents = length 673, hash ED7CD8C7
+ input buffer #6:
+ timeUs = 1000000166833
+ contents = length 523, hash 3020DF50
+ input buffer #7:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 7
+ output buffer #0:
+ timeUs = 1000000000000
+ size = 36692
+ rendered = true
+ output buffer #1:
+ timeUs = 1000000066733
+ size = 5312
+ rendered = true
+ output buffer #2:
+ timeUs = 1000000033366
+ size = 599
+ rendered = true
+ output buffer #3:
+ timeUs = 1000000200200
+ size = 7735
+ rendered = true
+ output buffer #4:
+ timeUs = 1000000133466
+ size = 987
+ rendered = true
+ output buffer #5:
+ timeUs = 1000000100100
+ size = 673
+ rendered = true
+ output buffer #6:
+ timeUs = 1000000166833
+ size = 523
+ rendered = true
+MediaCodecAdapter (exotest.video.avc):
+ inputBuffers:
+ count = 19
+ input buffer #0:
+ timeUs = 1000000000000
+ contents = length 36692, hash D216076E
+ input buffer #1:
+ timeUs = 1000000066733
+ contents = length 5312, hash D45D3CA0
+ input buffer #2:
+ timeUs = 1000000033366
+ contents = length 599, hash 1BE7812D
+ input buffer #3:
+ timeUs = 1000000200200
+ contents = length 7735, hash 4490F110
+ input buffer #4:
+ timeUs = 1000000133466
+ contents = length 987, hash 560B5036
+ input buffer #5:
+ timeUs = 1000000100100
+ contents = length 673, hash ED7CD8C7
+ input buffer #6:
+ timeUs = 1000000166833
+ contents = length 523, hash 3020DF50
+ input buffer #7:
+ timeUs = 1000000333666
+ contents = length 6061, hash 736C72B2
+ input buffer #8:
+ timeUs = 1000000266933
+ contents = length 992, hash FE132F23
+ input buffer #9:
+ timeUs = 1000000233566
+ contents = length 623, hash 5B2C1816
+ input buffer #10:
+ timeUs = 1000000300300
+ contents = length 421, hash 742E69C1
+ input buffer #11:
+ timeUs = 1000000433766
+ contents = length 4899, hash F72F86A1
+ input buffer #12:
+ timeUs = 1000000400400
+ contents = length 568, hash 519A8E50
+ input buffer #13:
+ timeUs = 1000000367033
+ contents = length 620, hash 3990AA39
+ input buffer #14:
+ timeUs = 1000000567233
+ contents = length 5450, hash F06EC4AA
+ input buffer #15:
+ timeUs = 1000000500500
+ contents = length 1051, hash 92DFA63A
+ input buffer #16:
+ timeUs = 1000000467133
+ contents = length 874, hash 69587FB4
+ input buffer #17:
+ timeUs = 1000000533866
+ contents = length 781, hash 36BE495B
+ input buffer #18:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 18
+ output buffer #0:
+ timeUs = 1000000000000
+ size = 36692
+ rendered = false
+ output buffer #1:
+ timeUs = 1000000066733
+ size = 5312
+ rendered = false
+ output buffer #2:
+ timeUs = 1000000033366
+ size = 599
+ rendered = false
+ output buffer #3:
+ timeUs = 1000000200200
+ size = 7735
+ rendered = false
+ output buffer #4:
+ timeUs = 1000000133466
+ size = 987
+ rendered = false
+ output buffer #5:
+ timeUs = 1000000100100
+ size = 673
+ rendered = false
+ output buffer #6:
+ timeUs = 1000000166833
+ size = 523
+ rendered = false
+ output buffer #7:
+ timeUs = 1000000333666
+ size = 6061
+ rendered = false
+ output buffer #8:
+ timeUs = 1000000266933
+ size = 992
+ rendered = false
+ output buffer #9:
+ timeUs = 1000000233566
+ size = 623
+ rendered = false
+ output buffer #10:
+ timeUs = 1000000300300
+ size = 421
+ rendered = false
+ output buffer #11:
+ timeUs = 1000000433766
+ size = 4899
+ rendered = false
+ output buffer #12:
+ timeUs = 1000000400400
+ size = 568
+ rendered = false
+ output buffer #13:
+ timeUs = 1000000367033
+ size = 620
+ rendered = false
+ output buffer #14:
+ timeUs = 1000000567233
+ size = 5450
+ rendered = false
+ output buffer #15:
+ timeUs = 1000000500500
+ size = 1051
+ rendered = false
+ output buffer #16:
+ timeUs = 1000000467133
+ size = 874
+ rendered = false
+ output buffer #17:
+ timeUs = 1000000533866
+ size = 781
+ rendered = false
+MediaCodecAdapter (exotest.video.avc):
+ inputBuffers:
+ count = 31
+ input buffer #0:
+ timeUs = 1000000000000
+ contents = length 36692, hash D216076E
+ input buffer #1:
+ timeUs = 1000000066733
+ contents = length 5312, hash D45D3CA0
+ input buffer #2:
+ timeUs = 1000000033366
+ contents = length 599, hash 1BE7812D
+ input buffer #3:
+ timeUs = 1000000200200
+ contents = length 7735, hash 4490F110
+ input buffer #4:
+ timeUs = 1000000133466
+ contents = length 987, hash 560B5036
+ input buffer #5:
+ timeUs = 1000000100100
+ contents = length 673, hash ED7CD8C7
+ input buffer #6:
+ timeUs = 1000000166833
+ contents = length 523, hash 3020DF50
+ input buffer #7:
+ timeUs = 1000000333666
+ contents = length 6061, hash 736C72B2
+ input buffer #8:
+ timeUs = 1000000266933
+ contents = length 992, hash FE132F23
+ input buffer #9:
+ timeUs = 1000000233566
+ contents = length 623, hash 5B2C1816
+ input buffer #10:
+ timeUs = 1000000300300
+ contents = length 421, hash 742E69C1
+ input buffer #11:
+ timeUs = 1000000433766
+ contents = length 4899, hash F72F86A1
+ input buffer #12:
+ timeUs = 1000000400400
+ contents = length 568, hash 519A8E50
+ input buffer #13:
+ timeUs = 1000000367033
+ contents = length 620, hash 3990AA39
+ input buffer #14:
+ timeUs = 1000000567233
+ contents = length 5450, hash F06EC4AA
+ input buffer #15:
+ timeUs = 1000000500500
+ contents = length 1051, hash 92DFA63A
+ input buffer #16:
+ timeUs = 1000000467133
+ contents = length 874, hash 69587FB4
+ input buffer #17:
+ timeUs = 1000000533866
+ contents = length 781, hash 36BE495B
+ input buffer #18:
+ timeUs = 1000000700700
+ contents = length 4725, hash AC0C8CD3
+ input buffer #19:
+ timeUs = 1000000633966
+ contents = length 1022, hash 5D8BFF34
+ input buffer #20:
+ timeUs = 1000000600600
+ contents = length 790, hash 99413A99
+ input buffer #21:
+ timeUs = 1000000667333
+ contents = length 610, hash 5E129290
+ input buffer #22:
+ timeUs = 1000000834166
+ contents = length 2751, hash 769974CB
+ input buffer #23:
+ timeUs = 1000000767433
+ contents = length 745, hash B78A477A
+ input buffer #24:
+ timeUs = 1000000734066
+ contents = length 621, hash CF741E7A
+ input buffer #25:
+ timeUs = 1000000800800
+ contents = length 505, hash 1DB4894E
+ input buffer #26:
+ timeUs = 1000000967633
+ contents = length 1268, hash C15348DC
+ input buffer #27:
+ timeUs = 1000000900900
+ contents = length 880, hash C2DE85D0
+ input buffer #28:
+ timeUs = 1000000867533
+ contents = length 530, hash C98BC6A8
+ input buffer #29:
+ timeUs = 1000000934266
+ contents = length 568, hash 4FE5C8EA
+ input buffer #30:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 30
+ output buffer #0:
+ timeUs = 1000000000000
+ size = 36692
+ rendered = false
+ output buffer #1:
+ timeUs = 1000000066733
+ size = 5312
+ rendered = false
+ output buffer #2:
+ timeUs = 1000000033366
+ size = 599
+ rendered = false
+ output buffer #3:
+ timeUs = 1000000200200
+ size = 7735
+ rendered = false
+ output buffer #4:
+ timeUs = 1000000133466
+ size = 987
+ rendered = false
+ output buffer #5:
+ timeUs = 1000000100100
+ size = 673
+ rendered = false
+ output buffer #6:
+ timeUs = 1000000166833
+ size = 523
+ rendered = false
+ output buffer #7:
+ timeUs = 1000000333666
+ size = 6061
+ rendered = false
+ output buffer #8:
+ timeUs = 1000000266933
+ size = 992
+ rendered = false
+ output buffer #9:
+ timeUs = 1000000233566
+ size = 623
+ rendered = false
+ output buffer #10:
+ timeUs = 1000000300300
+ size = 421
+ rendered = false
+ output buffer #11:
+ timeUs = 1000000433766
+ size = 4899
+ rendered = false
+ output buffer #12:
+ timeUs = 1000000400400
+ size = 568
+ rendered = false
+ output buffer #13:
+ timeUs = 1000000367033
+ size = 620
+ rendered = false
+ output buffer #14:
+ timeUs = 1000000567233
+ size = 5450
+ rendered = false
+ output buffer #15:
+ timeUs = 1000000500500
+ size = 1051
+ rendered = false
+ output buffer #16:
+ timeUs = 1000000467133
+ size = 874
+ rendered = false
+ output buffer #17:
+ timeUs = 1000000533866
+ size = 781
+ rendered = false
+ output buffer #18:
+ timeUs = 1000000700700
+ size = 4725
+ rendered = true
+ output buffer #19:
+ timeUs = 1000000633966
+ size = 1022
+ rendered = true
+ output buffer #20:
+ timeUs = 1000000600600
+ size = 790
+ rendered = true
+ output buffer #21:
+ timeUs = 1000000667333
+ size = 610
+ rendered = true
+ output buffer #22:
+ timeUs = 1000000834166
+ size = 2751
+ rendered = true
+ output buffer #23:
+ timeUs = 1000000767433
+ size = 745
+ rendered = true
+ output buffer #24:
+ timeUs = 1000000734066
+ size = 621
+ rendered = true
+ output buffer #25:
+ timeUs = 1000000800800
+ size = 505
+ rendered = true
+ output buffer #26:
+ timeUs = 1000000967633
+ size = 1268
+ rendered = true
+ output buffer #27:
+ timeUs = 1000000900900
+ size = 880
+ rendered = true
+ output buffer #28:
+ timeUs = 1000000867533
+ size = 530
+ rendered = true
+ output buffer #29:
+ timeUs = 1000000934266
+ size = 568
+ rendered = true
+AudioSink:
+ buffer count = 45
+ config:
+ pcmEncoding = 2
+ channelCount = 1
+ sampleRate = 44100
+ buffer #0:
+ time = 1000000044000
+ data = 1
+ buffer #1:
+ time = 1000000067219
+ data = 1
+ buffer #2:
+ time = 1000000090439
+ data = 1
+ buffer #3:
+ time = 1000000113659
+ data = 1
+ buffer #4:
+ time = 1000000136879
+ data = 1
+ buffer #5:
+ time = 1000000160099
+ data = 1
+ buffer #6:
+ time = 1000000183319
+ data = 1
+ buffer #7:
+ time = 1000000206539
+ data = 1
+ buffer #8:
+ time = 1000000229759
+ data = 1
+ discontinuity:
+ config:
+ pcmEncoding = 2
+ channelCount = 1
+ sampleRate = 44100
+ buffer #9:
+ time = 1000000252979
+ data = 1
+ buffer #10:
+ time = 1000000276199
+ data = 1
+ buffer #11:
+ time = 1000000299419
+ data = 1
+ buffer #12:
+ time = 1000000322639
+ data = 1
+ buffer #13:
+ time = 1000000345859
+ data = 1
+ buffer #14:
+ time = 1000000369079
+ data = 1
+ buffer #15:
+ time = 1000000392299
+ data = 1
+ buffer #16:
+ time = 1000000415519
+ data = 1
+ buffer #17:
+ time = 1000000438739
+ data = 1
+ buffer #18:
+ time = 1000000461959
+ data = 1
+ buffer #19:
+ time = 1000000485179
+ data = 1
+ buffer #20:
+ time = 1000000508399
+ data = 1
+ buffer #21:
+ time = 1000000531619
+ data = 1
+ buffer #22:
+ time = 1000000554839
+ data = 1
+ buffer #23:
+ time = 1000000578058
+ data = 1
+ discontinuity:
+ config:
+ pcmEncoding = 2
+ channelCount = 1
+ sampleRate = 44100
+ buffer #24:
+ time = 1000000601278
+ data = 1
+ buffer #25:
+ time = 1000000624498
+ data = 1
+ buffer #26:
+ time = 1000000647718
+ data = 1
+ buffer #27:
+ time = 1000000670938
+ data = 1
+ buffer #28:
+ time = 1000000694158
+ data = 1
+ buffer #29:
+ time = 1000000717378
+ data = 1
+ buffer #30:
+ time = 1000000740598
+ data = 1
+ buffer #31:
+ time = 1000000763818
+ data = 1
+ buffer #32:
+ time = 1000000787038
+ data = 1
+ buffer #33:
+ time = 1000000810258
+ data = 1
+ buffer #34:
+ time = 1000000833478
+ data = 1
+ buffer #35:
+ time = 1000000856698
+ data = 1
+ buffer #36:
+ time = 1000000879918
+ data = 1
+ buffer #37:
+ time = 1000000903138
+ data = 1
+ buffer #38:
+ time = 1000000926358
+ data = 1
+ buffer #39:
+ time = 1000000949578
+ data = 1
+ buffer #40:
+ time = 1000000972798
+ data = 1
+ buffer #41:
+ time = 1000000996018
+ data = 1
+ buffer #42:
+ time = 1000001019238
+ data = 1
+ buffer #43:
+ time = 1000001042458
+ data = 1
+ buffer #44:
+ time = 1000001065678
+ data = 1
diff --git a/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/threeItemPlaylist-withSecondaryVideoRenderer.dump b/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/threeItemPlaylist-withSecondaryVideoRenderer.dump
new file mode 100644
index 0000000000..edd0cfdf73
--- /dev/null
+++ b/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/threeItemPlaylist-withSecondaryVideoRenderer.dump
@@ -0,0 +1,657 @@
+MediaCodecAdapter (exotest.video.avc):
+ inputBuffers:
+ count = 31
+ input buffer #0:
+ timeUs = 1000000000000
+ contents = length 36692, hash D216076E
+ input buffer #1:
+ timeUs = 1000000066733
+ contents = length 5312, hash D45D3CA0
+ input buffer #2:
+ timeUs = 1000000033366
+ contents = length 599, hash 1BE7812D
+ input buffer #3:
+ timeUs = 1000000200200
+ contents = length 7735, hash 4490F110
+ input buffer #4:
+ timeUs = 1000000133466
+ contents = length 987, hash 560B5036
+ input buffer #5:
+ timeUs = 1000000100100
+ contents = length 673, hash ED7CD8C7
+ input buffer #6:
+ timeUs = 1000000166833
+ contents = length 523, hash 3020DF50
+ input buffer #7:
+ timeUs = 1000000333666
+ contents = length 6061, hash 736C72B2
+ input buffer #8:
+ timeUs = 1000000266933
+ contents = length 992, hash FE132F23
+ input buffer #9:
+ timeUs = 1000000233566
+ contents = length 623, hash 5B2C1816
+ input buffer #10:
+ timeUs = 1000000300300
+ contents = length 421, hash 742E69C1
+ input buffer #11:
+ timeUs = 1000000433766
+ contents = length 4899, hash F72F86A1
+ input buffer #12:
+ timeUs = 1000000400400
+ contents = length 568, hash 519A8E50
+ input buffer #13:
+ timeUs = 1000000367033
+ contents = length 620, hash 3990AA39
+ input buffer #14:
+ timeUs = 1000000567233
+ contents = length 5450, hash F06EC4AA
+ input buffer #15:
+ timeUs = 1000000500500
+ contents = length 1051, hash 92DFA63A
+ input buffer #16:
+ timeUs = 1000000467133
+ contents = length 874, hash 69587FB4
+ input buffer #17:
+ timeUs = 1000000533866
+ contents = length 781, hash 36BE495B
+ input buffer #18:
+ timeUs = 1000000700700
+ contents = length 4725, hash AC0C8CD3
+ input buffer #19:
+ timeUs = 1000000633966
+ contents = length 1022, hash 5D8BFF34
+ input buffer #20:
+ timeUs = 1000000600600
+ contents = length 790, hash 99413A99
+ input buffer #21:
+ timeUs = 1000000667333
+ contents = length 610, hash 5E129290
+ input buffer #22:
+ timeUs = 1000000834166
+ contents = length 2751, hash 769974CB
+ input buffer #23:
+ timeUs = 1000000767433
+ contents = length 745, hash B78A477A
+ input buffer #24:
+ timeUs = 1000000734066
+ contents = length 621, hash CF741E7A
+ input buffer #25:
+ timeUs = 1000000800800
+ contents = length 505, hash 1DB4894E
+ input buffer #26:
+ timeUs = 1000000967633
+ contents = length 1268, hash C15348DC
+ input buffer #27:
+ timeUs = 1000000900900
+ contents = length 880, hash C2DE85D0
+ input buffer #28:
+ timeUs = 1000000867533
+ contents = length 530, hash C98BC6A8
+ input buffer #29:
+ timeUs = 1000000934266
+ contents = length 568, hash 4FE5C8EA
+ input buffer #30:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 30
+ output buffer #0:
+ timeUs = 1000000000000
+ size = 36692
+ rendered = true
+ output buffer #1:
+ timeUs = 1000000066733
+ size = 5312
+ rendered = true
+ output buffer #2:
+ timeUs = 1000000033366
+ size = 599
+ rendered = true
+ output buffer #3:
+ timeUs = 1000000200200
+ size = 7735
+ rendered = true
+ output buffer #4:
+ timeUs = 1000000133466
+ size = 987
+ rendered = true
+ output buffer #5:
+ timeUs = 1000000100100
+ size = 673
+ rendered = true
+ output buffer #6:
+ timeUs = 1000000166833
+ size = 523
+ rendered = true
+ output buffer #7:
+ timeUs = 1000000333666
+ size = 6061
+ rendered = true
+ output buffer #8:
+ timeUs = 1000000266933
+ size = 992
+ rendered = true
+ output buffer #9:
+ timeUs = 1000000233566
+ size = 623
+ rendered = true
+ output buffer #10:
+ timeUs = 1000000300300
+ size = 421
+ rendered = true
+ output buffer #11:
+ timeUs = 1000000433766
+ size = 4899
+ rendered = true
+ output buffer #12:
+ timeUs = 1000000400400
+ size = 568
+ rendered = true
+ output buffer #13:
+ timeUs = 1000000367033
+ size = 620
+ rendered = true
+ output buffer #14:
+ timeUs = 1000000567233
+ size = 5450
+ rendered = true
+ output buffer #15:
+ timeUs = 1000000500500
+ size = 1051
+ rendered = true
+ output buffer #16:
+ timeUs = 1000000467133
+ size = 874
+ rendered = true
+ output buffer #17:
+ timeUs = 1000000533866
+ size = 781
+ rendered = true
+ output buffer #18:
+ timeUs = 1000000700700
+ size = 4725
+ rendered = true
+ output buffer #19:
+ timeUs = 1000000633966
+ size = 1022
+ rendered = true
+ output buffer #20:
+ timeUs = 1000000600600
+ size = 790
+ rendered = true
+ output buffer #21:
+ timeUs = 1000000667333
+ size = 610
+ rendered = true
+ output buffer #22:
+ timeUs = 1000000834166
+ size = 2751
+ rendered = true
+ output buffer #23:
+ timeUs = 1000000767433
+ size = 745
+ rendered = true
+ output buffer #24:
+ timeUs = 1000000734066
+ size = 621
+ rendered = true
+ output buffer #25:
+ timeUs = 1000000800800
+ size = 505
+ rendered = true
+ output buffer #26:
+ timeUs = 1000000967633
+ size = 1268
+ rendered = true
+ output buffer #27:
+ timeUs = 1000000900900
+ size = 880
+ rendered = true
+ output buffer #28:
+ timeUs = 1000000867533
+ size = 530
+ rendered = true
+ output buffer #29:
+ timeUs = 1000000934266
+ size = 568
+ rendered = true
+MediaCodecAdapter (exotest.video.avc):
+ inputBuffers:
+ count = 31
+ input buffer #0:
+ timeUs = 1000001024000
+ contents = length 36692, hash D216076E
+ input buffer #1:
+ timeUs = 1000001090733
+ contents = length 5312, hash D45D3CA0
+ input buffer #2:
+ timeUs = 1000001057366
+ contents = length 599, hash 1BE7812D
+ input buffer #3:
+ timeUs = 1000001224200
+ contents = length 7735, hash 4490F110
+ input buffer #4:
+ timeUs = 1000001157466
+ contents = length 987, hash 560B5036
+ input buffer #5:
+ timeUs = 1000001124100
+ contents = length 673, hash ED7CD8C7
+ input buffer #6:
+ timeUs = 1000001190833
+ contents = length 523, hash 3020DF50
+ input buffer #7:
+ timeUs = 1000001357666
+ contents = length 6061, hash 736C72B2
+ input buffer #8:
+ timeUs = 1000001290933
+ contents = length 992, hash FE132F23
+ input buffer #9:
+ timeUs = 1000001257566
+ contents = length 623, hash 5B2C1816
+ input buffer #10:
+ timeUs = 1000001324300
+ contents = length 421, hash 742E69C1
+ input buffer #11:
+ timeUs = 1000001457766
+ contents = length 4899, hash F72F86A1
+ input buffer #12:
+ timeUs = 1000001424400
+ contents = length 568, hash 519A8E50
+ input buffer #13:
+ timeUs = 1000001391033
+ contents = length 620, hash 3990AA39
+ input buffer #14:
+ timeUs = 1000001591233
+ contents = length 5450, hash F06EC4AA
+ input buffer #15:
+ timeUs = 1000001524500
+ contents = length 1051, hash 92DFA63A
+ input buffer #16:
+ timeUs = 1000001491133
+ contents = length 874, hash 69587FB4
+ input buffer #17:
+ timeUs = 1000001557866
+ contents = length 781, hash 36BE495B
+ input buffer #18:
+ timeUs = 1000001724700
+ contents = length 4725, hash AC0C8CD3
+ input buffer #19:
+ timeUs = 1000001657966
+ contents = length 1022, hash 5D8BFF34
+ input buffer #20:
+ timeUs = 1000001624600
+ contents = length 790, hash 99413A99
+ input buffer #21:
+ timeUs = 1000001691333
+ contents = length 610, hash 5E129290
+ input buffer #22:
+ timeUs = 1000001858166
+ contents = length 2751, hash 769974CB
+ input buffer #23:
+ timeUs = 1000001791433
+ contents = length 745, hash B78A477A
+ input buffer #24:
+ timeUs = 1000001758066
+ contents = length 621, hash CF741E7A
+ input buffer #25:
+ timeUs = 1000001824800
+ contents = length 505, hash 1DB4894E
+ input buffer #26:
+ timeUs = 1000001991633
+ contents = length 1268, hash C15348DC
+ input buffer #27:
+ timeUs = 1000001924900
+ contents = length 880, hash C2DE85D0
+ input buffer #28:
+ timeUs = 1000001891533
+ contents = length 530, hash C98BC6A8
+ input buffer #29:
+ timeUs = 1000001958266
+ contents = length 568, hash 4FE5C8EA
+ input buffer #30:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 30
+ output buffer #0:
+ timeUs = 1000001024000
+ size = 36692
+ rendered = false
+ output buffer #1:
+ timeUs = 1000001090733
+ size = 5312
+ rendered = false
+ output buffer #2:
+ timeUs = 1000001057366
+ size = 599
+ rendered = false
+ output buffer #3:
+ timeUs = 1000001224200
+ size = 7735
+ rendered = false
+ output buffer #4:
+ timeUs = 1000001157466
+ size = 987
+ rendered = false
+ output buffer #5:
+ timeUs = 1000001124100
+ size = 673
+ rendered = false
+ output buffer #6:
+ timeUs = 1000001190833
+ size = 523
+ rendered = false
+ output buffer #7:
+ timeUs = 1000001357666
+ size = 6061
+ rendered = false
+ output buffer #8:
+ timeUs = 1000001290933
+ size = 992
+ rendered = false
+ output buffer #9:
+ timeUs = 1000001257566
+ size = 623
+ rendered = false
+ output buffer #10:
+ timeUs = 1000001324300
+ size = 421
+ rendered = false
+ output buffer #11:
+ timeUs = 1000001457766
+ size = 4899
+ rendered = false
+ output buffer #12:
+ timeUs = 1000001424400
+ size = 568
+ rendered = false
+ output buffer #13:
+ timeUs = 1000001391033
+ size = 620
+ rendered = false
+ output buffer #14:
+ timeUs = 1000001591233
+ size = 5450
+ rendered = false
+ output buffer #15:
+ timeUs = 1000001524500
+ size = 1051
+ rendered = false
+ output buffer #16:
+ timeUs = 1000001491133
+ size = 874
+ rendered = false
+ output buffer #17:
+ timeUs = 1000001557866
+ size = 781
+ rendered = false
+ output buffer #18:
+ timeUs = 1000001724700
+ size = 4725
+ rendered = false
+ output buffer #19:
+ timeUs = 1000001657966
+ size = 1022
+ rendered = false
+ output buffer #20:
+ timeUs = 1000001624600
+ size = 790
+ rendered = false
+ output buffer #21:
+ timeUs = 1000001691333
+ size = 610
+ rendered = false
+ output buffer #22:
+ timeUs = 1000001858166
+ size = 2751
+ rendered = false
+ output buffer #23:
+ timeUs = 1000001791433
+ size = 745
+ rendered = false
+ output buffer #24:
+ timeUs = 1000001758066
+ size = 621
+ rendered = false
+ output buffer #25:
+ timeUs = 1000001824800
+ size = 505
+ rendered = false
+ output buffer #26:
+ timeUs = 1000001991633
+ size = 1268
+ rendered = false
+ output buffer #27:
+ timeUs = 1000001924900
+ size = 880
+ rendered = false
+ output buffer #28:
+ timeUs = 1000001891533
+ size = 530
+ rendered = false
+ output buffer #29:
+ timeUs = 1000001958266
+ size = 568
+ rendered = false
+MediaCodecAdapter (exotest.video.avc):
+ inputBuffers:
+ count = 31
+ input buffer #0:
+ timeUs = 1000002048000
+ contents = length 36692, hash D216076E
+ input buffer #1:
+ timeUs = 1000002114733
+ contents = length 5312, hash D45D3CA0
+ input buffer #2:
+ timeUs = 1000002081366
+ contents = length 599, hash 1BE7812D
+ input buffer #3:
+ timeUs = 1000002248200
+ contents = length 7735, hash 4490F110
+ input buffer #4:
+ timeUs = 1000002181466
+ contents = length 987, hash 560B5036
+ input buffer #5:
+ timeUs = 1000002148100
+ contents = length 673, hash ED7CD8C7
+ input buffer #6:
+ timeUs = 1000002214833
+ contents = length 523, hash 3020DF50
+ input buffer #7:
+ timeUs = 1000002381666
+ contents = length 6061, hash 736C72B2
+ input buffer #8:
+ timeUs = 1000002314933
+ contents = length 992, hash FE132F23
+ input buffer #9:
+ timeUs = 1000002281566
+ contents = length 623, hash 5B2C1816
+ input buffer #10:
+ timeUs = 1000002348300
+ contents = length 421, hash 742E69C1
+ input buffer #11:
+ timeUs = 1000002481766
+ contents = length 4899, hash F72F86A1
+ input buffer #12:
+ timeUs = 1000002448400
+ contents = length 568, hash 519A8E50
+ input buffer #13:
+ timeUs = 1000002415033
+ contents = length 620, hash 3990AA39
+ input buffer #14:
+ timeUs = 1000002615233
+ contents = length 5450, hash F06EC4AA
+ input buffer #15:
+ timeUs = 1000002548500
+ contents = length 1051, hash 92DFA63A
+ input buffer #16:
+ timeUs = 1000002515133
+ contents = length 874, hash 69587FB4
+ input buffer #17:
+ timeUs = 1000002581866
+ contents = length 781, hash 36BE495B
+ input buffer #18:
+ timeUs = 1000002748700
+ contents = length 4725, hash AC0C8CD3
+ input buffer #19:
+ timeUs = 1000002681966
+ contents = length 1022, hash 5D8BFF34
+ input buffer #20:
+ timeUs = 1000002648600
+ contents = length 790, hash 99413A99
+ input buffer #21:
+ timeUs = 1000002715333
+ contents = length 610, hash 5E129290
+ input buffer #22:
+ timeUs = 1000002882166
+ contents = length 2751, hash 769974CB
+ input buffer #23:
+ timeUs = 1000002815433
+ contents = length 745, hash B78A477A
+ input buffer #24:
+ timeUs = 1000002782066
+ contents = length 621, hash CF741E7A
+ input buffer #25:
+ timeUs = 1000002848800
+ contents = length 505, hash 1DB4894E
+ input buffer #26:
+ timeUs = 1000003015633
+ contents = length 1268, hash C15348DC
+ input buffer #27:
+ timeUs = 1000002948900
+ contents = length 880, hash C2DE85D0
+ input buffer #28:
+ timeUs = 1000002915533
+ contents = length 530, hash C98BC6A8
+ input buffer #29:
+ timeUs = 1000002982266
+ contents = length 568, hash 4FE5C8EA
+ input buffer #30:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 30
+ output buffer #0:
+ timeUs = 1000002048000
+ size = 36692
+ rendered = true
+ output buffer #1:
+ timeUs = 1000002114733
+ size = 5312
+ rendered = true
+ output buffer #2:
+ timeUs = 1000002081366
+ size = 599
+ rendered = true
+ output buffer #3:
+ timeUs = 1000002248200
+ size = 7735
+ rendered = true
+ output buffer #4:
+ timeUs = 1000002181466
+ size = 987
+ rendered = true
+ output buffer #5:
+ timeUs = 1000002148100
+ size = 673
+ rendered = true
+ output buffer #6:
+ timeUs = 1000002214833
+ size = 523
+ rendered = true
+ output buffer #7:
+ timeUs = 1000002381666
+ size = 6061
+ rendered = true
+ output buffer #8:
+ timeUs = 1000002314933
+ size = 992
+ rendered = true
+ output buffer #9:
+ timeUs = 1000002281566
+ size = 623
+ rendered = true
+ output buffer #10:
+ timeUs = 1000002348300
+ size = 421
+ rendered = true
+ output buffer #11:
+ timeUs = 1000002481766
+ size = 4899
+ rendered = true
+ output buffer #12:
+ timeUs = 1000002448400
+ size = 568
+ rendered = true
+ output buffer #13:
+ timeUs = 1000002415033
+ size = 620
+ rendered = true
+ output buffer #14:
+ timeUs = 1000002615233
+ size = 5450
+ rendered = true
+ output buffer #15:
+ timeUs = 1000002548500
+ size = 1051
+ rendered = true
+ output buffer #16:
+ timeUs = 1000002515133
+ size = 874
+ rendered = true
+ output buffer #17:
+ timeUs = 1000002581866
+ size = 781
+ rendered = true
+ output buffer #18:
+ timeUs = 1000002748700
+ size = 4725
+ rendered = true
+ output buffer #19:
+ timeUs = 1000002681966
+ size = 1022
+ rendered = true
+ output buffer #20:
+ timeUs = 1000002648600
+ size = 790
+ rendered = true
+ output buffer #21:
+ timeUs = 1000002715333
+ size = 610
+ rendered = true
+ output buffer #22:
+ timeUs = 1000002882166
+ size = 2751
+ rendered = true
+ output buffer #23:
+ timeUs = 1000002815433
+ size = 745
+ rendered = true
+ output buffer #24:
+ timeUs = 1000002782066
+ size = 621
+ rendered = true
+ output buffer #25:
+ timeUs = 1000002848800
+ size = 505
+ rendered = true
+ output buffer #26:
+ timeUs = 1000003015633
+ size = 1268
+ rendered = true
+ output buffer #27:
+ timeUs = 1000002948900
+ size = 880
+ rendered = true
+ output buffer #28:
+ timeUs = 1000002915533
+ size = 530
+ rendered = true
+ output buffer #29:
+ timeUs = 1000002982266
+ size = 568
+ rendered = true
diff --git a/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/twoItemPlaylist-withPrewarmingNonTransitioningRenderer.dump b/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/twoItemPlaylist-withPrewarmingNonTransitioningRenderer.dump
new file mode 100644
index 0000000000..296c051698
--- /dev/null
+++ b/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/twoItemPlaylist-withPrewarmingNonTransitioningRenderer.dump
@@ -0,0 +1,374 @@
+MediaCodecAdapter (exotest.audio.aac):
+ inputBuffers:
+ count = 13
+ input buffer #0:
+ timeUs = 1000000310972
+ contents = length 229, hash 2E8BA4DC
+ input buffer #1:
+ timeUs = 1000000334192
+ contents = length 232, hash 22F0C510
+ input buffer #2:
+ timeUs = 1000000357412
+ contents = length 235, hash 867AD0DC
+ input buffer #3:
+ timeUs = 1000000380632
+ contents = length 231, hash 84E823A8
+ input buffer #4:
+ timeUs = 1000000403852
+ contents = length 226, hash 1BEF3A95
+ input buffer #5:
+ timeUs = 1000000427072
+ contents = length 216, hash EAA345AE
+ input buffer #6:
+ timeUs = 1000000450292
+ contents = length 229, hash 6957411F
+ input buffer #7:
+ timeUs = 1000000473512
+ contents = length 219, hash 41275022
+ input buffer #8:
+ timeUs = 1000000496732
+ contents = length 241, hash 6495DF96
+ input buffer #9:
+ timeUs = 1000000519952
+ contents = length 228, hash 63D95906
+ input buffer #10:
+ timeUs = 1000000543172
+ contents = length 238, hash 34F676F9
+ input buffer #11:
+ timeUs = 1000000566391
+ contents = length 234, hash E5CBC045
+ input buffer #12:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 12
+ output buffer #0:
+ timeUs = 1000000310972
+ size = 0
+ rendered = false
+ output buffer #1:
+ timeUs = 1000000334192
+ size = 0
+ rendered = false
+ output buffer #2:
+ timeUs = 1000000357412
+ size = 0
+ rendered = false
+ output buffer #3:
+ timeUs = 1000000380632
+ size = 0
+ rendered = false
+ output buffer #4:
+ timeUs = 1000000403852
+ size = 0
+ rendered = false
+ output buffer #5:
+ timeUs = 1000000427072
+ size = 0
+ rendered = false
+ output buffer #6:
+ timeUs = 1000000450292
+ size = 0
+ rendered = false
+ output buffer #7:
+ timeUs = 1000000473512
+ size = 0
+ rendered = false
+ output buffer #8:
+ timeUs = 1000000496732
+ size = 0
+ rendered = false
+ output buffer #9:
+ timeUs = 1000000519952
+ size = 0
+ rendered = false
+ output buffer #10:
+ timeUs = 1000000543172
+ size = 0
+ rendered = false
+ output buffer #11:
+ timeUs = 1000000566391
+ size = 0
+ rendered = false
+MediaCodecAdapter (exotest.audio.ac3):
+ inputBuffers:
+ count = 10
+ input buffer #0:
+ timeUs = 1000000000000
+ contents = length 1536, hash 7108D5C2
+ input buffer #1:
+ timeUs = 1000000032000
+ contents = length 1536, hash 80BF3B34
+ input buffer #2:
+ timeUs = 1000000064000
+ contents = length 1536, hash 5D09685
+ input buffer #3:
+ timeUs = 1000000096000
+ contents = length 1536, hash A9A24E44
+ input buffer #4:
+ timeUs = 1000000128000
+ contents = length 1536, hash 6F856273
+ input buffer #5:
+ timeUs = 1000000160000
+ contents = length 1536, hash B1737D3C
+ input buffer #6:
+ timeUs = 1000000192000
+ contents = length 1536, hash 98FDEB9D
+ input buffer #7:
+ timeUs = 1000000224000
+ contents = length 1536, hash 99B9B943
+ input buffer #8:
+ timeUs = 1000000256000
+ contents = length 1536, hash AAD9FCD2
+ input buffer #9:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 9
+ output buffer #0:
+ timeUs = 1000000000000
+ size = 0
+ rendered = false
+ output buffer #1:
+ timeUs = 1000000032000
+ size = 0
+ rendered = false
+ output buffer #2:
+ timeUs = 1000000064000
+ size = 0
+ rendered = false
+ output buffer #3:
+ timeUs = 1000000096000
+ size = 0
+ rendered = false
+ output buffer #4:
+ timeUs = 1000000128000
+ size = 0
+ rendered = false
+ output buffer #5:
+ timeUs = 1000000160000
+ size = 0
+ rendered = false
+ output buffer #6:
+ timeUs = 1000000192000
+ size = 0
+ rendered = false
+ output buffer #7:
+ timeUs = 1000000224000
+ size = 0
+ rendered = false
+ output buffer #8:
+ timeUs = 1000000256000
+ size = 0
+ rendered = false
+MediaCodecAdapter (exotest.video.avc):
+ inputBuffers:
+ count = 19
+ input buffer #0:
+ timeUs = 999999988333
+ contents = length 36692, hash D216076E
+ input buffer #1:
+ timeUs = 1000000055066
+ contents = length 5312, hash D45D3CA0
+ input buffer #2:
+ timeUs = 1000000021699
+ contents = length 599, hash 1BE7812D
+ input buffer #3:
+ timeUs = 1000000188533
+ contents = length 7735, hash 4490F110
+ input buffer #4:
+ timeUs = 1000000121799
+ contents = length 987, hash 560B5036
+ input buffer #5:
+ timeUs = 1000000088433
+ contents = length 673, hash ED7CD8C7
+ input buffer #6:
+ timeUs = 1000000155166
+ contents = length 523, hash 3020DF50
+ input buffer #7:
+ timeUs = 1000000321999
+ contents = length 6061, hash 736C72B2
+ input buffer #8:
+ timeUs = 1000000255266
+ contents = length 992, hash FE132F23
+ input buffer #9:
+ timeUs = 1000000221899
+ contents = length 623, hash 5B2C1816
+ input buffer #10:
+ timeUs = 1000000288633
+ contents = length 421, hash 742E69C1
+ input buffer #11:
+ timeUs = 1000000422099
+ contents = length 4899, hash F72F86A1
+ input buffer #12:
+ timeUs = 1000000388733
+ contents = length 568, hash 519A8E50
+ input buffer #13:
+ timeUs = 1000000355366
+ contents = length 620, hash 3990AA39
+ input buffer #14:
+ timeUs = 1000000555566
+ contents = length 5450, hash F06EC4AA
+ input buffer #15:
+ timeUs = 1000000488833
+ contents = length 1051, hash 92DFA63A
+ input buffer #16:
+ timeUs = 1000000455466
+ contents = length 874, hash 69587FB4
+ input buffer #17:
+ timeUs = 1000000522199
+ contents = length 781, hash 36BE495B
+ input buffer #18:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 18
+ output buffer #0:
+ timeUs = 999999988333
+ size = 36692
+ rendered = false
+ output buffer #1:
+ timeUs = 1000000055066
+ size = 5312
+ rendered = false
+ output buffer #2:
+ timeUs = 1000000021699
+ size = 599
+ rendered = false
+ output buffer #3:
+ timeUs = 1000000188533
+ size = 7735
+ rendered = false
+ output buffer #4:
+ timeUs = 1000000121799
+ size = 987
+ rendered = false
+ output buffer #5:
+ timeUs = 1000000088433
+ size = 673
+ rendered = false
+ output buffer #6:
+ timeUs = 1000000155166
+ size = 523
+ rendered = false
+ output buffer #7:
+ timeUs = 1000000321999
+ size = 6061
+ rendered = true
+ output buffer #8:
+ timeUs = 1000000255266
+ size = 992
+ rendered = false
+ output buffer #9:
+ timeUs = 1000000221899
+ size = 623
+ rendered = false
+ output buffer #10:
+ timeUs = 1000000288633
+ size = 421
+ rendered = true
+ output buffer #11:
+ timeUs = 1000000422099
+ size = 4899
+ rendered = true
+ output buffer #12:
+ timeUs = 1000000388733
+ size = 568
+ rendered = true
+ output buffer #13:
+ timeUs = 1000000355366
+ size = 620
+ rendered = true
+ output buffer #14:
+ timeUs = 1000000555566
+ size = 5450
+ rendered = true
+ output buffer #15:
+ timeUs = 1000000488833
+ size = 1051
+ rendered = true
+ output buffer #16:
+ timeUs = 1000000455466
+ size = 874
+ rendered = true
+ output buffer #17:
+ timeUs = 1000000522199
+ size = 781
+ rendered = true
+AudioSink:
+ buffer count = 21
+ config:
+ pcmEncoding = 2
+ channelCount = 6
+ sampleRate = 48000
+ buffer #0:
+ time = 1000000000000
+ data = 1
+ buffer #1:
+ time = 1000000032000
+ data = 1
+ buffer #2:
+ time = 1000000064000
+ data = 1
+ buffer #3:
+ time = 1000000096000
+ data = 1
+ buffer #4:
+ time = 1000000128000
+ data = 1
+ buffer #5:
+ time = 1000000160000
+ data = 1
+ buffer #6:
+ time = 1000000192000
+ data = 1
+ buffer #7:
+ time = 1000000224000
+ data = 1
+ buffer #8:
+ time = 1000000256000
+ data = 1
+ discontinuity:
+ config:
+ pcmEncoding = 2
+ channelCount = 1
+ sampleRate = 44100
+ buffer #9:
+ time = 1000000310972
+ data = 1
+ buffer #10:
+ time = 1000000334192
+ data = 1
+ buffer #11:
+ time = 1000000357412
+ data = 1
+ buffer #12:
+ time = 1000000380632
+ data = 1
+ buffer #13:
+ time = 1000000403852
+ data = 1
+ buffer #14:
+ time = 1000000427072
+ data = 1
+ buffer #15:
+ time = 1000000450292
+ data = 1
+ buffer #16:
+ time = 1000000473512
+ data = 1
+ buffer #17:
+ time = 1000000496732
+ data = 1
+ buffer #18:
+ time = 1000000519952
+ data = 1
+ buffer #19:
+ time = 1000000543172
+ data = 1
+ buffer #20:
+ time = 1000000566391
+ data = 1
diff --git a/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/twoItemsPlaylist-withSecondaryVideoRenderer.dump b/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/twoItemsPlaylist-withSecondaryVideoRenderer.dump
new file mode 100644
index 0000000000..66b21bbcea
--- /dev/null
+++ b/libraries/test_data/src/test/assets/playbackdumps/prewarmingRenderer/twoItemsPlaylist-withSecondaryVideoRenderer.dump
@@ -0,0 +1,1362 @@
+MediaCodecAdapter (exotest.audio.aac):
+ inputBuffers:
+ count = 92
+ input buffer #0:
+ timeUs = 1000000044000
+ contents = length 23, hash 47DE9131
+ input buffer #1:
+ timeUs = 1000000067219
+ contents = length 6, hash 31EC5206
+ input buffer #2:
+ timeUs = 1000000090439
+ contents = length 148, hash 894A176B
+ input buffer #3:
+ timeUs = 1000000113659
+ contents = length 189, hash CEF235A1
+ input buffer #4:
+ timeUs = 1000000136879
+ contents = length 205, hash BBF5F7B0
+ input buffer #5:
+ timeUs = 1000000160099
+ contents = length 210, hash F278B193
+ input buffer #6:
+ timeUs = 1000000183319
+ contents = length 210, hash 82DA1589
+ input buffer #7:
+ timeUs = 1000000206539
+ contents = length 207, hash 5BE231DF
+ input buffer #8:
+ timeUs = 1000000229759
+ contents = length 225, hash 18819EE1
+ input buffer #9:
+ timeUs = 1000000252979
+ contents = length 215, hash CA7FA67B
+ input buffer #10:
+ timeUs = 1000000276199
+ contents = length 211, hash 581A1C18
+ input buffer #11:
+ timeUs = 1000000299419
+ contents = length 216, hash ADB88187
+ input buffer #12:
+ timeUs = 1000000322639
+ contents = length 229, hash 2E8BA4DC
+ input buffer #13:
+ timeUs = 1000000345859
+ contents = length 232, hash 22F0C510
+ input buffer #14:
+ timeUs = 1000000369079
+ contents = length 235, hash 867AD0DC
+ input buffer #15:
+ timeUs = 1000000392299
+ contents = length 231, hash 84E823A8
+ input buffer #16:
+ timeUs = 1000000415519
+ contents = length 226, hash 1BEF3A95
+ input buffer #17:
+ timeUs = 1000000438739
+ contents = length 216, hash EAA345AE
+ input buffer #18:
+ timeUs = 1000000461959
+ contents = length 229, hash 6957411F
+ input buffer #19:
+ timeUs = 1000000485179
+ contents = length 219, hash 41275022
+ input buffer #20:
+ timeUs = 1000000508399
+ contents = length 241, hash 6495DF96
+ input buffer #21:
+ timeUs = 1000000531619
+ contents = length 228, hash 63D95906
+ input buffer #22:
+ timeUs = 1000000554839
+ contents = length 238, hash 34F676F9
+ input buffer #23:
+ timeUs = 1000000578058
+ contents = length 234, hash E5CBC045
+ input buffer #24:
+ timeUs = 1000000601278
+ contents = length 231, hash 5FC43661
+ input buffer #25:
+ timeUs = 1000000624498
+ contents = length 217, hash 682708ED
+ input buffer #26:
+ timeUs = 1000000647718
+ contents = length 239, hash D43780FC
+ input buffer #27:
+ timeUs = 1000000670938
+ contents = length 243, hash C5E17980
+ input buffer #28:
+ timeUs = 1000000694158
+ contents = length 231, hash AC5837BA
+ input buffer #29:
+ timeUs = 1000000717378
+ contents = length 230, hash 169EE895
+ input buffer #30:
+ timeUs = 1000000740598
+ contents = length 238, hash C48FF3F1
+ input buffer #31:
+ timeUs = 1000000763818
+ contents = length 225, hash 531E4599
+ input buffer #32:
+ timeUs = 1000000787038
+ contents = length 232, hash CB3C6B8D
+ input buffer #33:
+ timeUs = 1000000810258
+ contents = length 243, hash F8C94C7
+ input buffer #34:
+ timeUs = 1000000833478
+ contents = length 232, hash A646A7D0
+ input buffer #35:
+ timeUs = 1000000856698
+ contents = length 237, hash E8B787A5
+ input buffer #36:
+ timeUs = 1000000879918
+ contents = length 228, hash 3FA7A29F
+ input buffer #37:
+ timeUs = 1000000903138
+ contents = length 235, hash B9B33B0A
+ input buffer #38:
+ timeUs = 1000000926358
+ contents = length 264, hash 71A4869E
+ input buffer #39:
+ timeUs = 1000000949578
+ contents = length 257, hash D049B54C
+ input buffer #40:
+ timeUs = 1000000972798
+ contents = length 227, hash 66757231
+ input buffer #41:
+ timeUs = 1000000996018
+ contents = length 227, hash BD374F1B
+ input buffer #42:
+ timeUs = 1000001019238
+ contents = length 235, hash 999477F6
+ input buffer #43:
+ timeUs = 1000001042458
+ contents = length 229, hash FFF98DF0
+ input buffer #44:
+ timeUs = 1000001065678
+ contents = length 6, hash 31B22286
+ input buffer #45:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ input buffer #46:
+ timeUs = 1000001068000
+ contents = length 23, hash 47DE9131
+ input buffer #47:
+ timeUs = 1000001091219
+ contents = length 6, hash 31EC5206
+ input buffer #48:
+ timeUs = 1000001114439
+ contents = length 148, hash 894A176B
+ input buffer #49:
+ timeUs = 1000001137659
+ contents = length 189, hash CEF235A1
+ input buffer #50:
+ timeUs = 1000001160879
+ contents = length 205, hash BBF5F7B0
+ input buffer #51:
+ timeUs = 1000001184099
+ contents = length 210, hash F278B193
+ input buffer #52:
+ timeUs = 1000001207319
+ contents = length 210, hash 82DA1589
+ input buffer #53:
+ timeUs = 1000001230539
+ contents = length 207, hash 5BE231DF
+ input buffer #54:
+ timeUs = 1000001253759
+ contents = length 225, hash 18819EE1
+ input buffer #55:
+ timeUs = 1000001276979
+ contents = length 215, hash CA7FA67B
+ input buffer #56:
+ timeUs = 1000001300199
+ contents = length 211, hash 581A1C18
+ input buffer #57:
+ timeUs = 1000001323419
+ contents = length 216, hash ADB88187
+ input buffer #58:
+ timeUs = 1000001346639
+ contents = length 229, hash 2E8BA4DC
+ input buffer #59:
+ timeUs = 1000001369859
+ contents = length 232, hash 22F0C510
+ input buffer #60:
+ timeUs = 1000001393079
+ contents = length 235, hash 867AD0DC
+ input buffer #61:
+ timeUs = 1000001416299
+ contents = length 231, hash 84E823A8
+ input buffer #62:
+ timeUs = 1000001439519
+ contents = length 226, hash 1BEF3A95
+ input buffer #63:
+ timeUs = 1000001462739
+ contents = length 216, hash EAA345AE
+ input buffer #64:
+ timeUs = 1000001485959
+ contents = length 229, hash 6957411F
+ input buffer #65:
+ timeUs = 1000001509179
+ contents = length 219, hash 41275022
+ input buffer #66:
+ timeUs = 1000001532399
+ contents = length 241, hash 6495DF96
+ input buffer #67:
+ timeUs = 1000001555619
+ contents = length 228, hash 63D95906
+ input buffer #68:
+ timeUs = 1000001578839
+ contents = length 238, hash 34F676F9
+ input buffer #69:
+ timeUs = 1000001602058
+ contents = length 234, hash E5CBC045
+ input buffer #70:
+ timeUs = 1000001625278
+ contents = length 231, hash 5FC43661
+ input buffer #71:
+ timeUs = 1000001648498
+ contents = length 217, hash 682708ED
+ input buffer #72:
+ timeUs = 1000001671718
+ contents = length 239, hash D43780FC
+ input buffer #73:
+ timeUs = 1000001694938
+ contents = length 243, hash C5E17980
+ input buffer #74:
+ timeUs = 1000001718158
+ contents = length 231, hash AC5837BA
+ input buffer #75:
+ timeUs = 1000001741378
+ contents = length 230, hash 169EE895
+ input buffer #76:
+ timeUs = 1000001764598
+ contents = length 238, hash C48FF3F1
+ input buffer #77:
+ timeUs = 1000001787818
+ contents = length 225, hash 531E4599
+ input buffer #78:
+ timeUs = 1000001811038
+ contents = length 232, hash CB3C6B8D
+ input buffer #79:
+ timeUs = 1000001834258
+ contents = length 243, hash F8C94C7
+ input buffer #80:
+ timeUs = 1000001857478
+ contents = length 232, hash A646A7D0
+ input buffer #81:
+ timeUs = 1000001880698
+ contents = length 237, hash E8B787A5
+ input buffer #82:
+ timeUs = 1000001903918
+ contents = length 228, hash 3FA7A29F
+ input buffer #83:
+ timeUs = 1000001927138
+ contents = length 235, hash B9B33B0A
+ input buffer #84:
+ timeUs = 1000001950358
+ contents = length 264, hash 71A4869E
+ input buffer #85:
+ timeUs = 1000001973578
+ contents = length 257, hash D049B54C
+ input buffer #86:
+ timeUs = 1000001996798
+ contents = length 227, hash 66757231
+ input buffer #87:
+ timeUs = 1000002020018
+ contents = length 227, hash BD374F1B
+ input buffer #88:
+ timeUs = 1000002043238
+ contents = length 235, hash 999477F6
+ input buffer #89:
+ timeUs = 1000002066458
+ contents = length 229, hash FFF98DF0
+ input buffer #90:
+ timeUs = 1000002089678
+ contents = length 6, hash 31B22286
+ input buffer #91:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 90
+ output buffer #0:
+ timeUs = 1000000044000
+ size = 0
+ rendered = false
+ output buffer #1:
+ timeUs = 1000000067219
+ size = 0
+ rendered = false
+ output buffer #2:
+ timeUs = 1000000090439
+ size = 0
+ rendered = false
+ output buffer #3:
+ timeUs = 1000000113659
+ size = 0
+ rendered = false
+ output buffer #4:
+ timeUs = 1000000136879
+ size = 0
+ rendered = false
+ output buffer #5:
+ timeUs = 1000000160099
+ size = 0
+ rendered = false
+ output buffer #6:
+ timeUs = 1000000183319
+ size = 0
+ rendered = false
+ output buffer #7:
+ timeUs = 1000000206539
+ size = 0
+ rendered = false
+ output buffer #8:
+ timeUs = 1000000229759
+ size = 0
+ rendered = false
+ output buffer #9:
+ timeUs = 1000000252979
+ size = 0
+ rendered = false
+ output buffer #10:
+ timeUs = 1000000276199
+ size = 0
+ rendered = false
+ output buffer #11:
+ timeUs = 1000000299419
+ size = 0
+ rendered = false
+ output buffer #12:
+ timeUs = 1000000322639
+ size = 0
+ rendered = false
+ output buffer #13:
+ timeUs = 1000000345859
+ size = 0
+ rendered = false
+ output buffer #14:
+ timeUs = 1000000369079
+ size = 0
+ rendered = false
+ output buffer #15:
+ timeUs = 1000000392299
+ size = 0
+ rendered = false
+ output buffer #16:
+ timeUs = 1000000415519
+ size = 0
+ rendered = false
+ output buffer #17:
+ timeUs = 1000000438739
+ size = 0
+ rendered = false
+ output buffer #18:
+ timeUs = 1000000461959
+ size = 0
+ rendered = false
+ output buffer #19:
+ timeUs = 1000000485179
+ size = 0
+ rendered = false
+ output buffer #20:
+ timeUs = 1000000508399
+ size = 0
+ rendered = false
+ output buffer #21:
+ timeUs = 1000000531619
+ size = 0
+ rendered = false
+ output buffer #22:
+ timeUs = 1000000554839
+ size = 0
+ rendered = false
+ output buffer #23:
+ timeUs = 1000000578058
+ size = 0
+ rendered = false
+ output buffer #24:
+ timeUs = 1000000601278
+ size = 0
+ rendered = false
+ output buffer #25:
+ timeUs = 1000000624498
+ size = 0
+ rendered = false
+ output buffer #26:
+ timeUs = 1000000647718
+ size = 0
+ rendered = false
+ output buffer #27:
+ timeUs = 1000000670938
+ size = 0
+ rendered = false
+ output buffer #28:
+ timeUs = 1000000694158
+ size = 0
+ rendered = false
+ output buffer #29:
+ timeUs = 1000000717378
+ size = 0
+ rendered = false
+ output buffer #30:
+ timeUs = 1000000740598
+ size = 0
+ rendered = false
+ output buffer #31:
+ timeUs = 1000000763818
+ size = 0
+ rendered = false
+ output buffer #32:
+ timeUs = 1000000787038
+ size = 0
+ rendered = false
+ output buffer #33:
+ timeUs = 1000000810258
+ size = 0
+ rendered = false
+ output buffer #34:
+ timeUs = 1000000833478
+ size = 0
+ rendered = false
+ output buffer #35:
+ timeUs = 1000000856698
+ size = 0
+ rendered = false
+ output buffer #36:
+ timeUs = 1000000879918
+ size = 0
+ rendered = false
+ output buffer #37:
+ timeUs = 1000000903138
+ size = 0
+ rendered = false
+ output buffer #38:
+ timeUs = 1000000926358
+ size = 0
+ rendered = false
+ output buffer #39:
+ timeUs = 1000000949578
+ size = 0
+ rendered = false
+ output buffer #40:
+ timeUs = 1000000972798
+ size = 0
+ rendered = false
+ output buffer #41:
+ timeUs = 1000000996018
+ size = 0
+ rendered = false
+ output buffer #42:
+ timeUs = 1000001019238
+ size = 0
+ rendered = false
+ output buffer #43:
+ timeUs = 1000001042458
+ size = 0
+ rendered = false
+ output buffer #44:
+ timeUs = 1000001065678
+ size = 0
+ rendered = false
+ output buffer #45:
+ timeUs = 1000001068000
+ size = 0
+ rendered = false
+ output buffer #46:
+ timeUs = 1000001091219
+ size = 0
+ rendered = false
+ output buffer #47:
+ timeUs = 1000001114439
+ size = 0
+ rendered = false
+ output buffer #48:
+ timeUs = 1000001137659
+ size = 0
+ rendered = false
+ output buffer #49:
+ timeUs = 1000001160879
+ size = 0
+ rendered = false
+ output buffer #50:
+ timeUs = 1000001184099
+ size = 0
+ rendered = false
+ output buffer #51:
+ timeUs = 1000001207319
+ size = 0
+ rendered = false
+ output buffer #52:
+ timeUs = 1000001230539
+ size = 0
+ rendered = false
+ output buffer #53:
+ timeUs = 1000001253759
+ size = 0
+ rendered = false
+ output buffer #54:
+ timeUs = 1000001276979
+ size = 0
+ rendered = false
+ output buffer #55:
+ timeUs = 1000001300199
+ size = 0
+ rendered = false
+ output buffer #56:
+ timeUs = 1000001323419
+ size = 0
+ rendered = false
+ output buffer #57:
+ timeUs = 1000001346639
+ size = 0
+ rendered = false
+ output buffer #58:
+ timeUs = 1000001369859
+ size = 0
+ rendered = false
+ output buffer #59:
+ timeUs = 1000001393079
+ size = 0
+ rendered = false
+ output buffer #60:
+ timeUs = 1000001416299
+ size = 0
+ rendered = false
+ output buffer #61:
+ timeUs = 1000001439519
+ size = 0
+ rendered = false
+ output buffer #62:
+ timeUs = 1000001462739
+ size = 0
+ rendered = false
+ output buffer #63:
+ timeUs = 1000001485959
+ size = 0
+ rendered = false
+ output buffer #64:
+ timeUs = 1000001509179
+ size = 0
+ rendered = false
+ output buffer #65:
+ timeUs = 1000001532399
+ size = 0
+ rendered = false
+ output buffer #66:
+ timeUs = 1000001555619
+ size = 0
+ rendered = false
+ output buffer #67:
+ timeUs = 1000001578839
+ size = 0
+ rendered = false
+ output buffer #68:
+ timeUs = 1000001602058
+ size = 0
+ rendered = false
+ output buffer #69:
+ timeUs = 1000001625278
+ size = 0
+ rendered = false
+ output buffer #70:
+ timeUs = 1000001648498
+ size = 0
+ rendered = false
+ output buffer #71:
+ timeUs = 1000001671718
+ size = 0
+ rendered = false
+ output buffer #72:
+ timeUs = 1000001694938
+ size = 0
+ rendered = false
+ output buffer #73:
+ timeUs = 1000001718158
+ size = 0
+ rendered = false
+ output buffer #74:
+ timeUs = 1000001741378
+ size = 0
+ rendered = false
+ output buffer #75:
+ timeUs = 1000001764598
+ size = 0
+ rendered = false
+ output buffer #76:
+ timeUs = 1000001787818
+ size = 0
+ rendered = false
+ output buffer #77:
+ timeUs = 1000001811038
+ size = 0
+ rendered = false
+ output buffer #78:
+ timeUs = 1000001834258
+ size = 0
+ rendered = false
+ output buffer #79:
+ timeUs = 1000001857478
+ size = 0
+ rendered = false
+ output buffer #80:
+ timeUs = 1000001880698
+ size = 0
+ rendered = false
+ output buffer #81:
+ timeUs = 1000001903918
+ size = 0
+ rendered = false
+ output buffer #82:
+ timeUs = 1000001927138
+ size = 0
+ rendered = false
+ output buffer #83:
+ timeUs = 1000001950358
+ size = 0
+ rendered = false
+ output buffer #84:
+ timeUs = 1000001973578
+ size = 0
+ rendered = false
+ output buffer #85:
+ timeUs = 1000001996798
+ size = 0
+ rendered = false
+ output buffer #86:
+ timeUs = 1000002020018
+ size = 0
+ rendered = false
+ output buffer #87:
+ timeUs = 1000002043238
+ size = 0
+ rendered = false
+ output buffer #88:
+ timeUs = 1000002066458
+ size = 0
+ rendered = false
+ output buffer #89:
+ timeUs = 1000002089678
+ size = 0
+ rendered = false
+MediaCodecAdapter (exotest.video.avc):
+ inputBuffers:
+ count = 31
+ input buffer #0:
+ timeUs = 1000000000000
+ contents = length 36692, hash D216076E
+ input buffer #1:
+ timeUs = 1000000066733
+ contents = length 5312, hash D45D3CA0
+ input buffer #2:
+ timeUs = 1000000033366
+ contents = length 599, hash 1BE7812D
+ input buffer #3:
+ timeUs = 1000000200200
+ contents = length 7735, hash 4490F110
+ input buffer #4:
+ timeUs = 1000000133466
+ contents = length 987, hash 560B5036
+ input buffer #5:
+ timeUs = 1000000100100
+ contents = length 673, hash ED7CD8C7
+ input buffer #6:
+ timeUs = 1000000166833
+ contents = length 523, hash 3020DF50
+ input buffer #7:
+ timeUs = 1000000333666
+ contents = length 6061, hash 736C72B2
+ input buffer #8:
+ timeUs = 1000000266933
+ contents = length 992, hash FE132F23
+ input buffer #9:
+ timeUs = 1000000233566
+ contents = length 623, hash 5B2C1816
+ input buffer #10:
+ timeUs = 1000000300300
+ contents = length 421, hash 742E69C1
+ input buffer #11:
+ timeUs = 1000000433766
+ contents = length 4899, hash F72F86A1
+ input buffer #12:
+ timeUs = 1000000400400
+ contents = length 568, hash 519A8E50
+ input buffer #13:
+ timeUs = 1000000367033
+ contents = length 620, hash 3990AA39
+ input buffer #14:
+ timeUs = 1000000567233
+ contents = length 5450, hash F06EC4AA
+ input buffer #15:
+ timeUs = 1000000500500
+ contents = length 1051, hash 92DFA63A
+ input buffer #16:
+ timeUs = 1000000467133
+ contents = length 874, hash 69587FB4
+ input buffer #17:
+ timeUs = 1000000533866
+ contents = length 781, hash 36BE495B
+ input buffer #18:
+ timeUs = 1000000700700
+ contents = length 4725, hash AC0C8CD3
+ input buffer #19:
+ timeUs = 1000000633966
+ contents = length 1022, hash 5D8BFF34
+ input buffer #20:
+ timeUs = 1000000600600
+ contents = length 790, hash 99413A99
+ input buffer #21:
+ timeUs = 1000000667333
+ contents = length 610, hash 5E129290
+ input buffer #22:
+ timeUs = 1000000834166
+ contents = length 2751, hash 769974CB
+ input buffer #23:
+ timeUs = 1000000767433
+ contents = length 745, hash B78A477A
+ input buffer #24:
+ timeUs = 1000000734066
+ contents = length 621, hash CF741E7A
+ input buffer #25:
+ timeUs = 1000000800800
+ contents = length 505, hash 1DB4894E
+ input buffer #26:
+ timeUs = 1000000967633
+ contents = length 1268, hash C15348DC
+ input buffer #27:
+ timeUs = 1000000900900
+ contents = length 880, hash C2DE85D0
+ input buffer #28:
+ timeUs = 1000000867533
+ contents = length 530, hash C98BC6A8
+ input buffer #29:
+ timeUs = 1000000934266
+ contents = length 568, hash 4FE5C8EA
+ input buffer #30:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 30
+ output buffer #0:
+ timeUs = 1000000000000
+ size = 36692
+ rendered = true
+ output buffer #1:
+ timeUs = 1000000066733
+ size = 5312
+ rendered = true
+ output buffer #2:
+ timeUs = 1000000033366
+ size = 599
+ rendered = true
+ output buffer #3:
+ timeUs = 1000000200200
+ size = 7735
+ rendered = true
+ output buffer #4:
+ timeUs = 1000000133466
+ size = 987
+ rendered = true
+ output buffer #5:
+ timeUs = 1000000100100
+ size = 673
+ rendered = true
+ output buffer #6:
+ timeUs = 1000000166833
+ size = 523
+ rendered = true
+ output buffer #7:
+ timeUs = 1000000333666
+ size = 6061
+ rendered = true
+ output buffer #8:
+ timeUs = 1000000266933
+ size = 992
+ rendered = true
+ output buffer #9:
+ timeUs = 1000000233566
+ size = 623
+ rendered = true
+ output buffer #10:
+ timeUs = 1000000300300
+ size = 421
+ rendered = true
+ output buffer #11:
+ timeUs = 1000000433766
+ size = 4899
+ rendered = true
+ output buffer #12:
+ timeUs = 1000000400400
+ size = 568
+ rendered = true
+ output buffer #13:
+ timeUs = 1000000367033
+ size = 620
+ rendered = true
+ output buffer #14:
+ timeUs = 1000000567233
+ size = 5450
+ rendered = true
+ output buffer #15:
+ timeUs = 1000000500500
+ size = 1051
+ rendered = true
+ output buffer #16:
+ timeUs = 1000000467133
+ size = 874
+ rendered = true
+ output buffer #17:
+ timeUs = 1000000533866
+ size = 781
+ rendered = true
+ output buffer #18:
+ timeUs = 1000000700700
+ size = 4725
+ rendered = true
+ output buffer #19:
+ timeUs = 1000000633966
+ size = 1022
+ rendered = true
+ output buffer #20:
+ timeUs = 1000000600600
+ size = 790
+ rendered = true
+ output buffer #21:
+ timeUs = 1000000667333
+ size = 610
+ rendered = true
+ output buffer #22:
+ timeUs = 1000000834166
+ size = 2751
+ rendered = true
+ output buffer #23:
+ timeUs = 1000000767433
+ size = 745
+ rendered = true
+ output buffer #24:
+ timeUs = 1000000734066
+ size = 621
+ rendered = true
+ output buffer #25:
+ timeUs = 1000000800800
+ size = 505
+ rendered = true
+ output buffer #26:
+ timeUs = 1000000967633
+ size = 1268
+ rendered = true
+ output buffer #27:
+ timeUs = 1000000900900
+ size = 880
+ rendered = true
+ output buffer #28:
+ timeUs = 1000000867533
+ size = 530
+ rendered = true
+ output buffer #29:
+ timeUs = 1000000934266
+ size = 568
+ rendered = true
+MediaCodecAdapter (exotest.video.avc):
+ inputBuffers:
+ count = 31
+ input buffer #0:
+ timeUs = 1000001024000
+ contents = length 36692, hash D216076E
+ input buffer #1:
+ timeUs = 1000001090733
+ contents = length 5312, hash D45D3CA0
+ input buffer #2:
+ timeUs = 1000001057366
+ contents = length 599, hash 1BE7812D
+ input buffer #3:
+ timeUs = 1000001224200
+ contents = length 7735, hash 4490F110
+ input buffer #4:
+ timeUs = 1000001157466
+ contents = length 987, hash 560B5036
+ input buffer #5:
+ timeUs = 1000001124100
+ contents = length 673, hash ED7CD8C7
+ input buffer #6:
+ timeUs = 1000001190833
+ contents = length 523, hash 3020DF50
+ input buffer #7:
+ timeUs = 1000001357666
+ contents = length 6061, hash 736C72B2
+ input buffer #8:
+ timeUs = 1000001290933
+ contents = length 992, hash FE132F23
+ input buffer #9:
+ timeUs = 1000001257566
+ contents = length 623, hash 5B2C1816
+ input buffer #10:
+ timeUs = 1000001324300
+ contents = length 421, hash 742E69C1
+ input buffer #11:
+ timeUs = 1000001457766
+ contents = length 4899, hash F72F86A1
+ input buffer #12:
+ timeUs = 1000001424400
+ contents = length 568, hash 519A8E50
+ input buffer #13:
+ timeUs = 1000001391033
+ contents = length 620, hash 3990AA39
+ input buffer #14:
+ timeUs = 1000001591233
+ contents = length 5450, hash F06EC4AA
+ input buffer #15:
+ timeUs = 1000001524500
+ contents = length 1051, hash 92DFA63A
+ input buffer #16:
+ timeUs = 1000001491133
+ contents = length 874, hash 69587FB4
+ input buffer #17:
+ timeUs = 1000001557866
+ contents = length 781, hash 36BE495B
+ input buffer #18:
+ timeUs = 1000001724700
+ contents = length 4725, hash AC0C8CD3
+ input buffer #19:
+ timeUs = 1000001657966
+ contents = length 1022, hash 5D8BFF34
+ input buffer #20:
+ timeUs = 1000001624600
+ contents = length 790, hash 99413A99
+ input buffer #21:
+ timeUs = 1000001691333
+ contents = length 610, hash 5E129290
+ input buffer #22:
+ timeUs = 1000001858166
+ contents = length 2751, hash 769974CB
+ input buffer #23:
+ timeUs = 1000001791433
+ contents = length 745, hash B78A477A
+ input buffer #24:
+ timeUs = 1000001758066
+ contents = length 621, hash CF741E7A
+ input buffer #25:
+ timeUs = 1000001824800
+ contents = length 505, hash 1DB4894E
+ input buffer #26:
+ timeUs = 1000001991633
+ contents = length 1268, hash C15348DC
+ input buffer #27:
+ timeUs = 1000001924900
+ contents = length 880, hash C2DE85D0
+ input buffer #28:
+ timeUs = 1000001891533
+ contents = length 530, hash C98BC6A8
+ input buffer #29:
+ timeUs = 1000001958266
+ contents = length 568, hash 4FE5C8EA
+ input buffer #30:
+ timeUs = 0
+ flags = 4
+ contents = length 0, hash 1
+ outputBuffers:
+ count = 30
+ output buffer #0:
+ timeUs = 1000001024000
+ size = 36692
+ rendered = false
+ output buffer #1:
+ timeUs = 1000001090733
+ size = 5312
+ rendered = false
+ output buffer #2:
+ timeUs = 1000001057366
+ size = 599
+ rendered = false
+ output buffer #3:
+ timeUs = 1000001224200
+ size = 7735
+ rendered = false
+ output buffer #4:
+ timeUs = 1000001157466
+ size = 987
+ rendered = false
+ output buffer #5:
+ timeUs = 1000001124100
+ size = 673
+ rendered = false
+ output buffer #6:
+ timeUs = 1000001190833
+ size = 523
+ rendered = false
+ output buffer #7:
+ timeUs = 1000001357666
+ size = 6061
+ rendered = false
+ output buffer #8:
+ timeUs = 1000001290933
+ size = 992
+ rendered = false
+ output buffer #9:
+ timeUs = 1000001257566
+ size = 623
+ rendered = false
+ output buffer #10:
+ timeUs = 1000001324300
+ size = 421
+ rendered = false
+ output buffer #11:
+ timeUs = 1000001457766
+ size = 4899
+ rendered = false
+ output buffer #12:
+ timeUs = 1000001424400
+ size = 568
+ rendered = false
+ output buffer #13:
+ timeUs = 1000001391033
+ size = 620
+ rendered = false
+ output buffer #14:
+ timeUs = 1000001591233
+ size = 5450
+ rendered = false
+ output buffer #15:
+ timeUs = 1000001524500
+ size = 1051
+ rendered = false
+ output buffer #16:
+ timeUs = 1000001491133
+ size = 874
+ rendered = false
+ output buffer #17:
+ timeUs = 1000001557866
+ size = 781
+ rendered = false
+ output buffer #18:
+ timeUs = 1000001724700
+ size = 4725
+ rendered = false
+ output buffer #19:
+ timeUs = 1000001657966
+ size = 1022
+ rendered = false
+ output buffer #20:
+ timeUs = 1000001624600
+ size = 790
+ rendered = false
+ output buffer #21:
+ timeUs = 1000001691333
+ size = 610
+ rendered = false
+ output buffer #22:
+ timeUs = 1000001858166
+ size = 2751
+ rendered = false
+ output buffer #23:
+ timeUs = 1000001791433
+ size = 745
+ rendered = false
+ output buffer #24:
+ timeUs = 1000001758066
+ size = 621
+ rendered = false
+ output buffer #25:
+ timeUs = 1000001824800
+ size = 505
+ rendered = false
+ output buffer #26:
+ timeUs = 1000001991633
+ size = 1268
+ rendered = false
+ output buffer #27:
+ timeUs = 1000001924900
+ size = 880
+ rendered = false
+ output buffer #28:
+ timeUs = 1000001891533
+ size = 530
+ rendered = false
+ output buffer #29:
+ timeUs = 1000001958266
+ size = 568
+ rendered = false
+AudioSink:
+ buffer count = 90
+ config:
+ pcmEncoding = 2
+ channelCount = 1
+ sampleRate = 44100
+ buffer #0:
+ time = 1000000044000
+ data = 1
+ buffer #1:
+ time = 1000000067219
+ data = 1
+ buffer #2:
+ time = 1000000090439
+ data = 1
+ buffer #3:
+ time = 1000000113659
+ data = 1
+ buffer #4:
+ time = 1000000136879
+ data = 1
+ buffer #5:
+ time = 1000000160099
+ data = 1
+ buffer #6:
+ time = 1000000183319
+ data = 1
+ buffer #7:
+ time = 1000000206539
+ data = 1
+ buffer #8:
+ time = 1000000229759
+ data = 1
+ buffer #9:
+ time = 1000000252979
+ data = 1
+ buffer #10:
+ time = 1000000276199
+ data = 1
+ buffer #11:
+ time = 1000000299419
+ data = 1
+ buffer #12:
+ time = 1000000322639
+ data = 1
+ buffer #13:
+ time = 1000000345859
+ data = 1
+ buffer #14:
+ time = 1000000369079
+ data = 1
+ buffer #15:
+ time = 1000000392299
+ data = 1
+ buffer #16:
+ time = 1000000415519
+ data = 1
+ buffer #17:
+ time = 1000000438739
+ data = 1
+ buffer #18:
+ time = 1000000461959
+ data = 1
+ buffer #19:
+ time = 1000000485179
+ data = 1
+ buffer #20:
+ time = 1000000508399
+ data = 1
+ buffer #21:
+ time = 1000000531619
+ data = 1
+ buffer #22:
+ time = 1000000554839
+ data = 1
+ buffer #23:
+ time = 1000000578058
+ data = 1
+ buffer #24:
+ time = 1000000601278
+ data = 1
+ buffer #25:
+ time = 1000000624498
+ data = 1
+ buffer #26:
+ time = 1000000647718
+ data = 1
+ buffer #27:
+ time = 1000000670938
+ data = 1
+ buffer #28:
+ time = 1000000694158
+ data = 1
+ buffer #29:
+ time = 1000000717378
+ data = 1
+ buffer #30:
+ time = 1000000740598
+ data = 1
+ buffer #31:
+ time = 1000000763818
+ data = 1
+ buffer #32:
+ time = 1000000787038
+ data = 1
+ buffer #33:
+ time = 1000000810258
+ data = 1
+ buffer #34:
+ time = 1000000833478
+ data = 1
+ buffer #35:
+ time = 1000000856698
+ data = 1
+ buffer #36:
+ time = 1000000879918
+ data = 1
+ buffer #37:
+ time = 1000000903138
+ data = 1
+ buffer #38:
+ time = 1000000926358
+ data = 1
+ buffer #39:
+ time = 1000000949578
+ data = 1
+ buffer #40:
+ time = 1000000972798
+ data = 1
+ buffer #41:
+ time = 1000000996018
+ data = 1
+ buffer #42:
+ time = 1000001019238
+ data = 1
+ buffer #43:
+ time = 1000001042458
+ data = 1
+ buffer #44:
+ time = 1000001065678
+ data = 1
+ discontinuity:
+ config:
+ pcmEncoding = 2
+ channelCount = 1
+ sampleRate = 44100
+ buffer #45:
+ time = 1000001068000
+ data = 1
+ buffer #46:
+ time = 1000001091219
+ data = 1
+ buffer #47:
+ time = 1000001114439
+ data = 1
+ buffer #48:
+ time = 1000001137659
+ data = 1
+ buffer #49:
+ time = 1000001160879
+ data = 1
+ buffer #50:
+ time = 1000001184099
+ data = 1
+ buffer #51:
+ time = 1000001207319
+ data = 1
+ buffer #52:
+ time = 1000001230539
+ data = 1
+ buffer #53:
+ time = 1000001253759
+ data = 1
+ buffer #54:
+ time = 1000001276979
+ data = 1
+ buffer #55:
+ time = 1000001300199
+ data = 1
+ buffer #56:
+ time = 1000001323419
+ data = 1
+ buffer #57:
+ time = 1000001346639
+ data = 1
+ buffer #58:
+ time = 1000001369859
+ data = 1
+ buffer #59:
+ time = 1000001393079
+ data = 1
+ buffer #60:
+ time = 1000001416299
+ data = 1
+ buffer #61:
+ time = 1000001439519
+ data = 1
+ buffer #62:
+ time = 1000001462739
+ data = 1
+ buffer #63:
+ time = 1000001485959
+ data = 1
+ buffer #64:
+ time = 1000001509179
+ data = 1
+ buffer #65:
+ time = 1000001532399
+ data = 1
+ buffer #66:
+ time = 1000001555619
+ data = 1
+ buffer #67:
+ time = 1000001578839
+ data = 1
+ buffer #68:
+ time = 1000001602058
+ data = 1
+ buffer #69:
+ time = 1000001625278
+ data = 1
+ buffer #70:
+ time = 1000001648498
+ data = 1
+ buffer #71:
+ time = 1000001671718
+ data = 1
+ buffer #72:
+ time = 1000001694938
+ data = 1
+ buffer #73:
+ time = 1000001718158
+ data = 1
+ buffer #74:
+ time = 1000001741378
+ data = 1
+ buffer #75:
+ time = 1000001764598
+ data = 1
+ buffer #76:
+ time = 1000001787818
+ data = 1
+ buffer #77:
+ time = 1000001811038
+ data = 1
+ buffer #78:
+ time = 1000001834258
+ data = 1
+ buffer #79:
+ time = 1000001857478
+ data = 1
+ buffer #80:
+ time = 1000001880698
+ data = 1
+ buffer #81:
+ time = 1000001903918
+ data = 1
+ buffer #82:
+ time = 1000001927138
+ data = 1
+ buffer #83:
+ time = 1000001950358
+ data = 1
+ buffer #84:
+ time = 1000001973578
+ data = 1
+ buffer #85:
+ time = 1000001996798
+ data = 1
+ buffer #86:
+ time = 1000002020018
+ data = 1
+ buffer #87:
+ time = 1000002043238
+ data = 1
+ buffer #88:
+ time = 1000002066458
+ data = 1
+ buffer #89:
+ time = 1000002089678
+ data = 1
diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java
index 080f1f22cd..6f0e49bb71 100644
--- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java
+++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/CapturingRenderersFactory.java
@@ -126,7 +126,7 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
MetadataOutput metadataRendererOutput) {
ArrayList