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 renderers = new ArrayList<>(); renderers.add( - new MediaCodecVideoRenderer( + new CapturingMediaCodecVideoRenderer( context, mediaCodecAdapterFactory, MediaCodecSelector.DEFAULT, @@ -134,27 +134,7 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa /* enableDecoderFallback= */ false, eventHandler, videoRendererEventListener, - DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY) { - @Override - protected boolean shouldDropOutputBuffer( - long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) { - // Do not drop output buffers due to slow processing. - return false; - } - - @Override - protected boolean shouldDropBuffersToKeyframe( - long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) { - // Do not drop output buffers due to slow processing. - return false; - } - - @Override - protected boolean shouldSkipBuffersWithIdenticalReleaseTime() { - // Do not skip buffers with identical vsync times as we can't control this from tests. - return false; - } - }); + DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); renderers.add( new MediaCodecAudioRenderer( context, @@ -190,6 +170,72 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa Renderer create(TextOutput textOutput, Looper outputLooper); } + /** + * Returns new instance of a specialized {@link MediaCodecVideoRenderer} that will not drop or + * skip buffers due to slow processing. + * + * @param eventHandler A handler to use when invoking event listeners and outputs. + * @param videoRendererEventListener An event listener for video renderers. + * @return a new instance of a specialized {@link MediaCodecVideoRenderer}. + */ + protected MediaCodecVideoRenderer createMediaCodecVideoRenderer( + Handler eventHandler, VideoRendererEventListener videoRendererEventListener) { + return new CapturingMediaCodecVideoRenderer( + context, + mediaCodecAdapterFactory, + MediaCodecSelector.DEFAULT, + DefaultRenderersFactory.DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS, + /* enableDecoderFallback= */ false, + eventHandler, + videoRendererEventListener, + DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); + } + + /** + * A {@link MediaCodecVideoRenderer} that will not skip or drop buffers due to slow processing. + */ + private static class CapturingMediaCodecVideoRenderer extends MediaCodecVideoRenderer { + private CapturingMediaCodecVideoRenderer( + Context context, + MediaCodecAdapter.Factory codecAdapterFactory, + MediaCodecSelector mediaCodecSelector, + long allowedJoiningTimeMs, + boolean enableDecoderFallback, + @Nullable Handler eventHandler, + @Nullable VideoRendererEventListener eventListener, + int maxDroppedFramesToNotify) { + super( + context, + codecAdapterFactory, + mediaCodecSelector, + allowedJoiningTimeMs, + enableDecoderFallback, + eventHandler, + eventListener, + maxDroppedFramesToNotify); + } + + @Override + protected boolean shouldDropOutputBuffer( + long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) { + // Do not drop output buffers due to slow processing. + return false; + } + + @Override + protected boolean shouldDropBuffersToKeyframe( + long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) { + // Do not drop output buffers due to slow processing. + return false; + } + + @Override + protected boolean shouldSkipBuffersWithIdenticalReleaseTime() { + // Do not skip buffers with identical vsync times as we can't control this from tests. + return false; + } + } + /** * A {@link MediaCodecAdapter} that captures interactions and exposes them for test assertions via * {@link Dumper.Dumpable}.