mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Implement track reselection support for pre-warming renderers
PiperOrigin-RevId: 704371028
This commit is contained in:
parent
ef19740c92
commit
bb62278627
@ -1898,6 +1898,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
enabledRendererCount -= enabledRendererCountBeforeDisabling;
|
enabledRendererCount -= enabledRendererCountBeforeDisabling;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void disableAndResetPrewarmingRenderers() {
|
||||||
|
if (!hasSecondaryRenderers || !areRenderersPrewarming()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (RendererHolder renderer : renderers) {
|
||||||
|
int enabledRendererCountBeforeDisabling = renderer.getEnabledRendererCount();
|
||||||
|
renderer.disablePrewarming(mediaClock);
|
||||||
|
enabledRendererCount -=
|
||||||
|
enabledRendererCountBeforeDisabling - renderer.getEnabledRendererCount();
|
||||||
|
}
|
||||||
|
prewarmingMediaPeriodDiscontinuity = C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
private void reselectTracksInternalAndSeek() throws ExoPlaybackException {
|
private void reselectTracksInternalAndSeek() throws ExoPlaybackException {
|
||||||
reselectTracksInternal();
|
reselectTracksInternal();
|
||||||
seekToCurrentPosition(/* sendDiscontinuity= */ true);
|
seekToCurrentPosition(/* sendDiscontinuity= */ true);
|
||||||
@ -1962,24 +1975,44 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
resetRendererPosition(periodPositionUs);
|
resetRendererPosition(periodPositionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable pre-warming renderers.
|
||||||
|
disableAndResetPrewarmingRenderers();
|
||||||
|
|
||||||
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
|
||||||
for (int i = 0; i < renderers.length; i++) {
|
for (int i = 0; i < renderers.length; i++) {
|
||||||
rendererWasEnabledFlags[i] = renderers[i].getEnabledRendererCount() > 0;
|
int enabledRendererCountBeforeDisabling = renderers[i].getEnabledRendererCount();
|
||||||
if (rendererWasEnabledFlags[i]) {
|
rendererWasEnabledFlags[i] = renderers[i].isRendererEnabled();
|
||||||
if (!renderers[i].isReadingFromPeriod(playingPeriodHolder)) {
|
|
||||||
disableRenderer(i);
|
renderers[i].maybeDisableOrResetPosition(
|
||||||
} else if (streamResetFlags[i]) {
|
playingPeriodHolder.sampleStreams[i],
|
||||||
renderers[i].resetPosition(playingPeriodHolder, rendererPositionUs);
|
mediaClock,
|
||||||
}
|
rendererPositionUs,
|
||||||
|
streamResetFlags[i]);
|
||||||
|
if (enabledRendererCountBeforeDisabling - renderers[i].getEnabledRendererCount() > 0) {
|
||||||
|
maybeTriggerOnRendererReadyChanged(i, /* allowsPlayback= */ false);
|
||||||
}
|
}
|
||||||
|
enabledRendererCount -=
|
||||||
|
enabledRendererCountBeforeDisabling - renderers[i].getEnabledRendererCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
enableRenderers(rendererWasEnabledFlags, /* startPositionUs= */ rendererPositionUs);
|
enableRenderers(rendererWasEnabledFlags, /* startPositionUs= */ rendererPositionUs);
|
||||||
|
playingPeriodHolder.allRenderersInCorrectState = true;
|
||||||
} else {
|
} else {
|
||||||
// Release and re-prepare/buffer periods after the one whose selection changed.
|
// Release and re-prepare/buffer periods after the one whose selection changed.
|
||||||
queue.removeAfter(periodHolder);
|
queue.removeAfter(periodHolder);
|
||||||
if (periodHolder.prepared) {
|
if (periodHolder.prepared) {
|
||||||
long loadingPeriodPositionUs =
|
long loadingPeriodPositionUs =
|
||||||
max(periodHolder.info.startPositionUs, periodHolder.toPeriodTime(rendererPositionUs));
|
max(periodHolder.info.startPositionUs, periodHolder.toPeriodTime(rendererPositionUs));
|
||||||
|
if (hasSecondaryRenderers
|
||||||
|
&& areRenderersPrewarming()
|
||||||
|
&& queue.getPrewarmingPeriod() == periodHolder) {
|
||||||
|
// If renderers are enabled early and track reselection is on the enabled-early period
|
||||||
|
// then there is a need to disable those renderers. Must be done prior to call to
|
||||||
|
// applyTrackSelection.
|
||||||
|
// TODO: Only disable pre-warming renderers for those whose streams will be changed by
|
||||||
|
// track reselection. Will require allowing partial maybePrewarmRenderersForNextPeriod.
|
||||||
|
disableAndResetPrewarmingRenderers();
|
||||||
|
}
|
||||||
periodHolder.applyTrackSelection(newTrackSelectorResult, loadingPeriodPositionUs, false);
|
periodHolder.applyTrackSelection(newTrackSelectorResult, loadingPeriodPositionUs, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -546,6 +546,56 @@ import java.util.Objects;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void disablePrewarming(DefaultMediaClock mediaClock) {
|
||||||
|
if (!isPrewarming()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean isPrewarmingPrimary =
|
||||||
|
prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY
|
||||||
|
|| prewarmingState == RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY;
|
||||||
|
boolean isSecondaryActiveRenderer =
|
||||||
|
prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY;
|
||||||
|
disableRenderer(
|
||||||
|
isPrewarmingPrimary ? primaryRenderer : checkNotNull(secondaryRenderer), mediaClock);
|
||||||
|
maybeResetRenderer(/* resetPrimary= */ isPrewarmingPrimary);
|
||||||
|
prewarmingState =
|
||||||
|
isSecondaryActiveRenderer
|
||||||
|
? RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_SECONDARY
|
||||||
|
: RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void maybeDisableOrResetPosition(
|
||||||
|
SampleStream sampleStream,
|
||||||
|
DefaultMediaClock mediaClock,
|
||||||
|
long rendererPositionUs,
|
||||||
|
boolean streamReset)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
maybeDisableOrResetPositionInternal(
|
||||||
|
primaryRenderer, sampleStream, mediaClock, rendererPositionUs, streamReset);
|
||||||
|
if (secondaryRenderer != null) {
|
||||||
|
maybeDisableOrResetPositionInternal(
|
||||||
|
secondaryRenderer, sampleStream, mediaClock, rendererPositionUs, streamReset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeDisableOrResetPositionInternal(
|
||||||
|
Renderer renderer,
|
||||||
|
SampleStream sampleStream,
|
||||||
|
DefaultMediaClock mediaClock,
|
||||||
|
long rendererPositionUs,
|
||||||
|
boolean streamReset)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
if (isRendererEnabled(renderer)) {
|
||||||
|
if (sampleStream != renderer.getStream()) {
|
||||||
|
// We need to disable the renderer.
|
||||||
|
disableRenderer(renderer, mediaClock);
|
||||||
|
} else if (streamReset) {
|
||||||
|
// The renderer will continue to consume from its current stream, but needs to be reset.
|
||||||
|
renderer.resetPosition(rendererPositionUs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable a {@link Renderer} if its enabled.
|
* Disable a {@link Renderer} if its enabled.
|
||||||
*
|
*
|
||||||
|
@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.util.Pair;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
@ -37,8 +38,11 @@ import androidx.media3.exoplayer.drm.DrmSessionManager;
|
|||||||
import androidx.media3.exoplayer.metadata.MetadataOutput;
|
import androidx.media3.exoplayer.metadata.MetadataOutput;
|
||||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
||||||
|
import androidx.media3.exoplayer.source.SampleStream;
|
||||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||||
import androidx.media3.exoplayer.text.TextOutput;
|
import androidx.media3.exoplayer.text.TextOutput;
|
||||||
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||||
|
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||||
import androidx.media3.exoplayer.upstream.Allocator;
|
import androidx.media3.exoplayer.upstream.Allocator;
|
||||||
import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
||||||
import androidx.media3.test.utils.ExoPlayerTestRunner;
|
import androidx.media3.test.utils.ExoPlayerTestRunner;
|
||||||
@ -55,6 +59,7 @@ import androidx.test.core.app.ApplicationProvider;
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -306,6 +311,305 @@ public class ExoPlayerWithPrewarmingRenderersTest {
|
|||||||
assertThat(videoState3).isEqualTo(Renderer.STATE_ENABLED);
|
assertThat(videoState3).isEqualTo(Renderer.STATE_ENABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
setTrackSelectionParameters_onPlayingPeriodUsingSecondaryAndPrimaryIsPrewarming_renderersNotSwapped()
|
||||||
|
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);
|
||||||
|
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 primary renderer is being prewarmed, 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();
|
||||||
|
// Disable the Audio track to trigger track reselection.
|
||||||
|
player.setTrackSelectionParameters(
|
||||||
|
player
|
||||||
|
.getTrackSelectionParameters()
|
||||||
|
.buildUpon()
|
||||||
|
.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, true)
|
||||||
|
.build());
|
||||||
|
run(player).untilPendingCommandsAreFullyHandled();
|
||||||
|
run(player)
|
||||||
|
.untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_ENABLED);
|
||||||
|
@Renderer.State int videoState2 = videoRenderer.getState();
|
||||||
|
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
|
||||||
|
player.release();
|
||||||
|
|
||||||
|
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_STARTED);
|
||||||
|
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
|
||||||
|
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_STARTED);
|
||||||
|
assertThat(videoState2).isEqualTo(Renderer.STATE_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
setTrackSelectionParameters_onPlayingPeriodWithPrewarmingNonTransitioningPrimaryRenderer_swapsToSecondaryRendererForPrewarming()
|
||||||
|
throws Exception {
|
||||||
|
Format videoFormat1 =
|
||||||
|
ExoPlayerTestRunner.VIDEO_FORMAT.buildUpon().setAverageBitrate(800_000).build();
|
||||||
|
Format videoFormat2 =
|
||||||
|
ExoPlayerTestRunner.VIDEO_FORMAT.buildUpon().setAverageBitrate(500_000).build();
|
||||||
|
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
|
||||||
|
DefaultTrackSelector.Parameters defaultTrackSelectorParameters =
|
||||||
|
new DefaultTrackSelector.Parameters.Builder(context)
|
||||||
|
.setMaxVideoBitrate(videoFormat2.averageBitrate)
|
||||||
|
.setExceedVideoConstraintsIfNecessary(false)
|
||||||
|
.build();
|
||||||
|
DefaultTrackSelector defaultTrackSelector =
|
||||||
|
new DefaultTrackSelector(context, defaultTrackSelectorParameters);
|
||||||
|
ExoPlayer player =
|
||||||
|
new TestExoPlayerBuilder(context)
|
||||||
|
.setClock(fakeClock)
|
||||||
|
.setTrackSelector(defaultTrackSelector)
|
||||||
|
.setRenderersFactory(
|
||||||
|
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
|
||||||
|
.build();
|
||||||
|
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
|
||||||
|
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
|
||||||
|
player.setMediaSources(
|
||||||
|
ImmutableList.of(
|
||||||
|
// Set media source with a video track with average bitrate above max.
|
||||||
|
new FakeMediaSource(new FakeTimeline(), videoFormat1, ExoPlayerTestRunner.AUDIO_FORMAT),
|
||||||
|
new FakeMediaSource(new FakeTimeline(), videoFormat1, videoFormat2)));
|
||||||
|
player.prepare();
|
||||||
|
|
||||||
|
// Play a bit until the primary renderer is being pre-warmed, 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();
|
||||||
|
SampleStream sampleStream1 = videoRenderer.getStream();
|
||||||
|
// Set maximum video bitrate to trigger track reselection and enable video on first media item.
|
||||||
|
player.setTrackSelectionParameters(
|
||||||
|
player
|
||||||
|
.getTrackSelectionParameters()
|
||||||
|
.buildUpon()
|
||||||
|
.setMaxVideoBitrate(videoFormat1.averageBitrate)
|
||||||
|
.build());
|
||||||
|
run(player).untilPendingCommandsAreFullyHandled();
|
||||||
|
run(player)
|
||||||
|
.untilBackgroundThreadCondition(
|
||||||
|
() -> secondaryVideoRenderer.getState() == Renderer.STATE_ENABLED);
|
||||||
|
run(player).untilPendingCommandsAreFullyHandled();
|
||||||
|
@Renderer.State int videoState2 = videoRenderer.getState();
|
||||||
|
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
|
||||||
|
SampleStream sampleStream2 = videoRenderer.getStream();
|
||||||
|
player.release();
|
||||||
|
|
||||||
|
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
|
||||||
|
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_DISABLED);
|
||||||
|
assertThat(videoState2).isEqualTo(Renderer.STATE_STARTED);
|
||||||
|
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_ENABLED);
|
||||||
|
assertThat(sampleStream1).isNotEqualTo(sampleStream2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
setTrackSelectionParameters_onPeriodAfterReadingWithEarlyEnabledSecondaryRenderer_createsNewSampleStreamForPrewarmingRenderer()
|
||||||
|
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);
|
||||||
|
Format videoFormat1 =
|
||||||
|
ExoPlayerTestRunner.VIDEO_FORMAT.buildUpon().setAverageBitrate(800_000).build();
|
||||||
|
Format videoFormat2 =
|
||||||
|
ExoPlayerTestRunner.VIDEO_FORMAT.buildUpon().setAverageBitrate(500_000).build();
|
||||||
|
Timeline timeline = new FakeTimeline();
|
||||||
|
// Set a playlist that allows a new renderer to be enabled early.
|
||||||
|
player.setMediaSources(
|
||||||
|
ImmutableList.of(
|
||||||
|
// Use FakeBlockingMediaSource so the reading period is not advanced when pre-warming.
|
||||||
|
new FakeBlockingMediaSource(timeline, videoFormat1),
|
||||||
|
new FakeMediaSource(timeline, videoFormat1, videoFormat2)));
|
||||||
|
player.prepare();
|
||||||
|
|
||||||
|
// Play until the second renderer has been enabled and reading period has not advanced.
|
||||||
|
run(player).untilState(Player.STATE_READY);
|
||||||
|
player.play();
|
||||||
|
run(player).untilPendingCommandsAreFullyHandled();
|
||||||
|
@Renderer.State int videoState1 = videoRenderer.getState();
|
||||||
|
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
|
||||||
|
SampleStream videoStream1 = videoRenderer.getStream();
|
||||||
|
SampleStream secondaryVideoStream1 = secondaryVideoRenderer.getStream();
|
||||||
|
// Set max bitrate to trigger track reselection.
|
||||||
|
player.setTrackSelectionParameters(
|
||||||
|
player
|
||||||
|
.getTrackSelectionParameters()
|
||||||
|
.buildUpon()
|
||||||
|
.setMaxVideoBitrate(videoFormat2.averageBitrate)
|
||||||
|
.build());
|
||||||
|
run(player)
|
||||||
|
.untilBackgroundThreadCondition(
|
||||||
|
() -> secondaryVideoRenderer.getState() == Renderer.STATE_ENABLED);
|
||||||
|
run(player).untilPendingCommandsAreFullyHandled();
|
||||||
|
SampleStream videoStream2 = videoRenderer.getStream();
|
||||||
|
SampleStream secondaryVideoStream2 = secondaryVideoRenderer.getStream();
|
||||||
|
player.release();
|
||||||
|
|
||||||
|
assertThat(videoState1).isEqualTo(Renderer.STATE_STARTED);
|
||||||
|
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_ENABLED);
|
||||||
|
assertThat(videoStream1).isEqualTo(videoStream2);
|
||||||
|
assertThat(secondaryVideoStream1).isNotEqualTo(secondaryVideoStream2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
setTrackSelectionParameters_onPeriodAfterReadingWithEarlyEnabledNonTransitioningPrimaryRenderer_createsNewSampleStreamForPrewarmingRenderer()
|
||||||
|
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);
|
||||||
|
Format videoFormat1 =
|
||||||
|
ExoPlayerTestRunner.VIDEO_FORMAT.buildUpon().setAverageBitrate(800_000).build();
|
||||||
|
Format videoFormat2 =
|
||||||
|
ExoPlayerTestRunner.VIDEO_FORMAT.buildUpon().setAverageBitrate(500_000).build();
|
||||||
|
Timeline timeline = new FakeTimeline();
|
||||||
|
// Set a playlist that allows a new renderer to be enabled early.
|
||||||
|
player.setMediaSources(
|
||||||
|
ImmutableList.of(
|
||||||
|
// Use FakeBlockingMediaSource so the reading period is not advanced when pre-warming.
|
||||||
|
new FakeBlockingMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT),
|
||||||
|
new FakeMediaSource(timeline, videoFormat1, videoFormat2)));
|
||||||
|
player.prepare();
|
||||||
|
|
||||||
|
// Play until the second renderer has been enabled and reading period has not advanced.
|
||||||
|
run(player).untilState(Player.STATE_READY);
|
||||||
|
player.play();
|
||||||
|
run(player).untilPendingCommandsAreFullyHandled();
|
||||||
|
@Renderer.State int videoState1 = videoRenderer.getState();
|
||||||
|
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
|
||||||
|
SampleStream videoStream1 = videoRenderer.getStream();
|
||||||
|
// Set max bitrate to trigger track reselection.
|
||||||
|
player.setTrackSelectionParameters(
|
||||||
|
player
|
||||||
|
.getTrackSelectionParameters()
|
||||||
|
.buildUpon()
|
||||||
|
.setMaxVideoBitrate(videoFormat2.averageBitrate)
|
||||||
|
.build());
|
||||||
|
run(player).untilPendingCommandsAreFullyHandled();
|
||||||
|
SampleStream videoStream2 = videoRenderer.getStream();
|
||||||
|
player.release();
|
||||||
|
|
||||||
|
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
|
||||||
|
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_DISABLED);
|
||||||
|
assertThat(videoStream1).isNotEqualTo(videoStream2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
setTrackSelectionParameters_onPeriodAfterEarlyEnabledPeriod_prewarmingRendererKeepsSampleStreams()
|
||||||
|
throws Exception {
|
||||||
|
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
|
||||||
|
AtomicReference<Pair<ExoTrackSelection.Definition, Integer>> selectedAudioTrack =
|
||||||
|
new AtomicReference<>();
|
||||||
|
DefaultTrackSelector.Parameters trackSelectionParameters =
|
||||||
|
new DefaultTrackSelector.Parameters.Builder(context)
|
||||||
|
.setExceedAudioConstraintsIfNecessary(false)
|
||||||
|
.build();
|
||||||
|
DefaultTrackSelector trackSelector =
|
||||||
|
new DefaultTrackSelector(context, trackSelectionParameters) {
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected Pair<ExoTrackSelection.Definition, Integer> selectAudioTrack(
|
||||||
|
MappedTrackInfo mappedTrackInfo,
|
||||||
|
@RendererCapabilities.Capabilities int[][][] rendererFormatSupports,
|
||||||
|
@RendererCapabilities.AdaptiveSupport int[] rendererMixedMimeTypeAdaptationSupports,
|
||||||
|
Parameters params)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
Pair<ExoTrackSelection.Definition, Integer> result =
|
||||||
|
super.selectAudioTrack(
|
||||||
|
mappedTrackInfo,
|
||||||
|
rendererFormatSupports,
|
||||||
|
rendererMixedMimeTypeAdaptationSupports,
|
||||||
|
params);
|
||||||
|
selectedAudioTrack.set(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ExoPlayer player =
|
||||||
|
new TestExoPlayerBuilder(context)
|
||||||
|
.setClock(fakeClock)
|
||||||
|
.setRenderersFactory(
|
||||||
|
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
|
||||||
|
.setTrackSelector(trackSelector)
|
||||||
|
.build();
|
||||||
|
Format audioFormat =
|
||||||
|
ExoPlayerTestRunner.AUDIO_FORMAT.buildUpon().setAverageBitrate(70_000).build();
|
||||||
|
FakeMediaSource mediaSource =
|
||||||
|
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT, audioFormat);
|
||||||
|
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
|
||||||
|
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
|
||||||
|
Renderer audioRenderer = player.getRenderer(/* index= */ 1);
|
||||||
|
// 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),
|
||||||
|
mediaSource));
|
||||||
|
player.prepare();
|
||||||
|
|
||||||
|
// Play a bit until the final media source has been prepared and gone through track selection.
|
||||||
|
player.play();
|
||||||
|
run(player).untilBackgroundThreadCondition(() -> selectedAudioTrack.get() != null);
|
||||||
|
@Renderer.State int videoState1 = videoRenderer.getState();
|
||||||
|
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
|
||||||
|
SampleStream secondaryVideoStream1 = secondaryVideoRenderer.getStream();
|
||||||
|
// Disable the Audio track to trigger track reselection.
|
||||||
|
player.setTrackSelectionParameters(
|
||||||
|
player.getTrackSelectionParameters().buildUpon().setMaxAudioBitrate(60_000).build());
|
||||||
|
run(player)
|
||||||
|
.untilBackgroundThreadCondition(() -> audioRenderer.getState() == Renderer.STATE_DISABLED);
|
||||||
|
@Renderer.State int videoState2 = videoRenderer.getState();
|
||||||
|
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
|
||||||
|
SampleStream secondaryVideoStream2 = secondaryVideoRenderer.getStream();
|
||||||
|
player.release();
|
||||||
|
|
||||||
|
assertThat(videoState1).isEqualTo(Renderer.STATE_STARTED);
|
||||||
|
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_ENABLED);
|
||||||
|
assertThat(videoState2).isEqualTo(Renderer.STATE_STARTED);
|
||||||
|
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_ENABLED);
|
||||||
|
assertThat(secondaryVideoStream1).isEqualTo(secondaryVideoStream2);
|
||||||
|
assertThat(selectedAudioTrack.get()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void
|
public void
|
||||||
seek_intoCurrentPeriodWithSecondaryBeforeReadingPeriodAdvanced_doesNotSwapToPrimaryRenderer()
|
seek_intoCurrentPeriodWithSecondaryBeforeReadingPeriodAdvanced_doesNotSwapToPrimaryRenderer()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user