Notify TrackSelection when it's enabled and disabled.

Add onEnable() and onDisable() call-backs to TrackSelection. This allows
TrackSelection to perform interesting operations (like subscribe to
NetworkStatus) and clean up after itself.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=175558485
This commit is contained in:
hoangtc 2017-11-13 11:10:54 -08:00 committed by Oliver Woodman
parent 877c89a0e1
commit 5bf4c249a2
6 changed files with 420 additions and 2 deletions

View File

@ -28,6 +28,8 @@ import com.google.android.exoplayer2.testutil.FakeRenderer;
import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.FakeTrackSelection;
import com.google.android.exoplayer2.testutil.FakeTrackSelector;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@ -311,4 +313,143 @@ public final class ExoPlayerTest extends TestCase {
assertEquals(Player.STATE_BUFFERING, (int) playbackStatesWhenSeekProcessed.get(2));
}
public void testAllActivatedTrackSelectionAreReleasedForSinglePeriod() throws Exception {
Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(false, false, /* durationUs= */ 500_000));
MediaSource mediaSource =
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT);
FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
FakeRenderer audioRenderer = new FakeRenderer(Builder.AUDIO_FORMAT);
FakeTrackSelector trackSelector = new FakeTrackSelector();
new ExoPlayerTestRunner.Builder()
.setMediaSource(mediaSource)
.setRenderers(videoRenderer, audioRenderer)
.setTrackSelector(trackSelector)
.build().start().blockUntilEnded(TIMEOUT_MS);
List<FakeTrackSelection> createdTrackSelections = trackSelector.getSelectedTrackSelections();
int numSelectionsEnabled = 0;
// Assert that all tracks selection are disabled at the end of the playback.
for (FakeTrackSelection trackSelection : createdTrackSelections) {
assertFalse(trackSelection.isEnabled);
numSelectionsEnabled += trackSelection.enableCount;
}
// There are 2 renderers, and track selections are made once (1 period).
// Track selections are not reused, so there are 2 track selections made.
assertEquals(2, createdTrackSelections.size());
// There should be 2 track selections enabled in total.
assertEquals(2, numSelectionsEnabled);
}
public void testAllActivatedTrackSelectionAreReleasedForMultiPeriods() throws Exception {
Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(false, false, /* durationUs= */ 500_000),
new TimelineWindowDefinition(false, false, /* durationUs= */ 500_000));
MediaSource mediaSource =
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT);
FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
FakeRenderer audioRenderer = new FakeRenderer(Builder.AUDIO_FORMAT);
FakeTrackSelector trackSelector = new FakeTrackSelector();
new ExoPlayerTestRunner.Builder()
.setMediaSource(mediaSource)
.setRenderers(videoRenderer, audioRenderer)
.setTrackSelector(trackSelector)
.build().start().blockUntilEnded(TIMEOUT_MS);
List<FakeTrackSelection> createdTrackSelections = trackSelector.getSelectedTrackSelections();
int numSelectionsEnabled = 0;
// Assert that all tracks selection are disabled at the end of the playback.
for (FakeTrackSelection trackSelection : createdTrackSelections) {
assertFalse(trackSelection.isEnabled);
numSelectionsEnabled += trackSelection.enableCount;
}
// There are 2 renderers, and track selections are made twice (2 periods).
// Track selections are not reused, so there are 4 track selections made.
assertEquals(4, createdTrackSelections.size());
// There should be 4 track selections enabled in total.
assertEquals(4, numSelectionsEnabled);
}
public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreRemade()
throws Exception {
Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(false, false, /* durationUs= */ 500_000));
MediaSource mediaSource =
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT);
FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
FakeRenderer audioRenderer = new FakeRenderer(Builder.AUDIO_FORMAT);
final FakeTrackSelector trackSelector = new FakeTrackSelector();
ActionSchedule disableTrackAction = new ActionSchedule.Builder("testChangeTrackSelection")
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(new Runnable() {
@Override
public void run() {
trackSelector.setRendererDisabled(0, true);
}
}).build();
new ExoPlayerTestRunner.Builder()
.setMediaSource(mediaSource)
.setRenderers(videoRenderer, audioRenderer)
.setTrackSelector(trackSelector)
.setActionSchedule(disableTrackAction)
.build().start().blockUntilEnded(TIMEOUT_MS);
List<FakeTrackSelection> createdTrackSelections = trackSelector.getSelectedTrackSelections();
int numSelectionsEnabled = 0;
// Assert that all tracks selection are disabled at the end of the playback.
for (FakeTrackSelection trackSelection : createdTrackSelections) {
assertFalse(trackSelection.isEnabled);
numSelectionsEnabled += trackSelection.enableCount;
}
// There are 2 renderers, and track selections are made twice.
// Track selections are not reused, so there are 4 track selections made.
assertEquals(4, createdTrackSelections.size());
// Initially there are 2 track selections enabled.
// The second time one renderer is disabled, so only 1 track selection should be enabled.
assertEquals(3, numSelectionsEnabled);
}
public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreUsed()
throws Exception {
Timeline timeline =
new FakeTimeline(new TimelineWindowDefinition(false, false, /* durationUs= */ 500_000));
MediaSource mediaSource =
new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT);
FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
FakeRenderer audioRenderer = new FakeRenderer(Builder.AUDIO_FORMAT);
final FakeTrackSelector trackSelector = new FakeTrackSelector(/* reuse track selection */ true);
ActionSchedule disableTrackAction = new ActionSchedule.Builder("testReuseTrackSelection")
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(new Runnable() {
@Override
public void run() {
trackSelector.setRendererDisabled(0, true);
}
}).build();
new ExoPlayerTestRunner.Builder()
.setMediaSource(mediaSource)
.setRenderers(videoRenderer, audioRenderer)
.setTrackSelector(trackSelector)
.setActionSchedule(disableTrackAction)
.build().start().blockUntilEnded(TIMEOUT_MS);
List<FakeTrackSelection> createdTrackSelections = trackSelector.getSelectedTrackSelections();
int numSelectionsEnabled = 0;
// Assert that all tracks selection are disabled at the end of the playback.
for (FakeTrackSelection trackSelection : createdTrackSelections) {
assertFalse(trackSelection.isEnabled);
numSelectionsEnabled += trackSelection.enableCount;
}
// There are 2 renderers, and track selections are made twice.
// TrackSelections are reused, so there are only 2 track selections made for 2 renderers.
assertEquals(2, createdTrackSelections.size());
// Initially there are 2 track selections enabled.
// The second time one renderer is disabled, so only 1 track selection should be enabled.
assertEquals(3, numSelectionsEnabled);
}
}

View File

@ -1666,11 +1666,11 @@ import java.io.IOException;
// Undo the effect of previous call to associate no-sample renderers with empty tracks
// so the mediaPeriod receives back whatever it sent us before.
disassociateNoSampleRenderersWithEmptySampleStream(sampleStreams);
updatePeriodTrackSelectorResult(trackSelectorResult);
// Disable streams on the period and get new streams for updated/newly-enabled tracks.
positionUs = mediaPeriod.selectTracks(trackSelections.getAll(), mayRetainStreamFlags,
sampleStreams, streamResetFlags, positionUs);
associateNoSampleRenderersWithEmptySampleStream(sampleStreams);
periodTrackSelectorResult = trackSelectorResult;
// Update whether we have enabled tracks and sanity check the expected streams are non-null.
hasEnabledTracks = false;
@ -1692,6 +1692,7 @@ import java.io.IOException;
}
public void release() {
updatePeriodTrackSelectorResult(null);
try {
if (info.endPositionUs != C.TIME_END_OF_SOURCE) {
mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
@ -1704,6 +1705,36 @@ import java.io.IOException;
}
}
private void updatePeriodTrackSelectorResult(TrackSelectorResult trackSelectorResult) {
if (periodTrackSelectorResult != null) {
disableTrackSelectionsInResult(periodTrackSelectorResult);
}
periodTrackSelectorResult = trackSelectorResult;
if (periodTrackSelectorResult != null) {
enableTrackSelectionsInResult(periodTrackSelectorResult);
}
}
private void enableTrackSelectionsInResult(TrackSelectorResult trackSelectorResult) {
for (int i = 0; i < trackSelectorResult.renderersEnabled.length; i++) {
boolean rendererEnabled = trackSelectorResult.renderersEnabled[i];
TrackSelection trackSelection = trackSelectorResult.selections.get(i);
if (rendererEnabled && trackSelection != null) {
trackSelection.enable();
}
}
}
private void disableTrackSelectionsInResult(TrackSelectorResult trackSelectorResult) {
for (int i = 0; i < trackSelectorResult.renderersEnabled.length; i++) {
boolean rendererEnabled = trackSelectorResult.renderersEnabled[i];
TrackSelection trackSelection = trackSelectorResult.selections.get(i);
if (rendererEnabled && trackSelection != null) {
trackSelection.disable();
}
}
}
/**
* For each renderer of type {@link C#TRACK_TYPE_NONE}, we will remove the dummy
* {@link EmptySampleStream} that was associated with it.

View File

@ -78,6 +78,16 @@ public abstract class BaseTrackSelection implements TrackSelection {
blacklistUntilTimes = new long[length];
}
@Override
public void enable() {
// Do nothing.
}
@Override
public void disable() {
// Do nothing.
}
@Override
public final TrackGroup getTrackGroup() {
return group;

View File

@ -47,6 +47,20 @@ public interface TrackSelection {
}
/**
* Enables the track selection.
* <p>
* This method may not be called when the track selection is already enabled.
*/
void enable();
/**
* Disables this track selection.
* <p>
* This method may only be called when the track selection is already enabled.
*/
void disable();
/**
* Returns the {@link TrackGroup} to which the selected tracks belong.
*/
@ -124,6 +138,8 @@ public interface TrackSelection {
/**
* Updates the selected track.
* <p>
* This method may only be called when the selection is enabled.
*
* @param playbackPositionUs The current playback position in microseconds. If playback of the
* period to which this track selection belongs has not yet started, the value will be the
@ -150,7 +166,7 @@ public interface TrackSelection {
* An example of a case where a smaller value may be returned is if network conditions have
* improved dramatically, allowing chunks to be discarded and re-buffered in a track of
* significantly higher quality. Discarding chunks may allow faster switching to a higher quality
* track in this case.
* track in this case. This method may only be called when the selection is enabled.
*
* @param playbackPositionUs The current playback position in microseconds. If playback of the
* period to which this track selection belongs has not yet started, the value will be the
@ -167,6 +183,8 @@ public interface TrackSelection {
* period of time. Blacklisting will fail if all other tracks are currently blacklisted. If
* blacklisting the currently selected track, note that it will remain selected until the next
* call to {@link #updateSelectedTrack(long, long, long)}.
* <p>
* This method may only be called when the selection is enabled.
*
* @param index The index of the track in the selection.
* @param blacklistDurationMs The duration of time for which the track should be blacklisted, in

View File

@ -0,0 +1,132 @@
/*
* Copyright (C) 2017 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 com.google.android.exoplayer2.testutil;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import java.util.List;
import junit.framework.Assert;
/**
* A fake {@link TrackSelection} that only returns 1 fixed track, and allows querying the number
* of calls to its methods.
*/
public final class FakeTrackSelection implements TrackSelection {
private final TrackGroup rendererTrackGroup;
public int enableCount;
public int releaseCount;
public boolean isEnabled;
public FakeTrackSelection(TrackGroup rendererTrackGroup) {
this.rendererTrackGroup = rendererTrackGroup;
}
@Override
public void enable() {
// assert that track selection is in disabled state before this call.
Assert.assertFalse(isEnabled);
enableCount++;
isEnabled = true;
}
@Override
public void disable() {
// assert that track selection is in enabled state before this call.
Assert.assertTrue(isEnabled);
releaseCount++;
isEnabled = false;
}
@Override
public TrackGroup getTrackGroup() {
return rendererTrackGroup;
}
@Override
public int length() {
return rendererTrackGroup.length;
}
@Override
public Format getFormat(int index) {
return rendererTrackGroup.getFormat(0);
}
@Override
public int getIndexInTrackGroup(int index) {
return 0;
}
@Override
public int indexOf(Format format) {
Assert.assertTrue(isEnabled);
return 0;
}
@Override
public int indexOf(int indexInTrackGroup) {
return 0;
}
@Override
public Format getSelectedFormat() {
return rendererTrackGroup.getFormat(0);
}
@Override
public int getSelectedIndexInTrackGroup() {
return 0;
}
@Override
public int getSelectedIndex() {
return 0;
}
@Override
public int getSelectionReason() {
return C.SELECTION_REASON_UNKNOWN;
}
@Override
public Object getSelectionData() {
return null;
}
@Override
public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs,
long availableDurationUs) {
Assert.assertTrue(isEnabled);
}
@Override
public int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
Assert.assertTrue(isEnabled);
return 0;
}
@Override
public boolean blacklist(int index, long blacklistDurationMs) {
Assert.assertTrue(isEnabled);
return false;
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2017 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 com.google.android.exoplayer2.testutil;
import android.support.annotation.NonNull;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import java.util.ArrayList;
import java.util.List;
/**
* A fake {@link MappingTrackSelector} that returns {@link FakeTrackSelection}s.
*/
public class FakeTrackSelector extends MappingTrackSelector {
private final List<FakeTrackSelection> selectedTrackSelections = new ArrayList<>();
private final boolean mayReuseTrackSelection;
public FakeTrackSelector() {
this(false);
}
/**
* @param mayReuseTrackSelection Whether this {@link FakeTrackSelector} will reuse
* {@link TrackSelection}s during track selection, when it finds previously-selected track
* selection using the same {@link TrackGroup}.
*/
public FakeTrackSelector(boolean mayReuseTrackSelection) {
this.mayReuseTrackSelection = mayReuseTrackSelection;
}
@Override
protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities,
TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports)
throws ExoPlaybackException {
List<FakeTrackSelection> resultList = new ArrayList<>();
for (TrackGroupArray trackGroupArray : rendererTrackGroupArrays) {
TrackGroup trackGroup = trackGroupArray.get(0);
FakeTrackSelection trackSelectionForRenderer = reuseOrCreateTrackSelection(trackGroup);
resultList.add(trackSelectionForRenderer);
}
return resultList.toArray(new TrackSelection[resultList.size()]);
}
@NonNull
private FakeTrackSelection reuseOrCreateTrackSelection(TrackGroup trackGroup) {
FakeTrackSelection trackSelectionForRenderer = null;
if (mayReuseTrackSelection) {
for (FakeTrackSelection selectedTrackSelection : selectedTrackSelections) {
if (selectedTrackSelection.getTrackGroup().equals(trackGroup)) {
trackSelectionForRenderer = selectedTrackSelection;
}
}
}
if (trackSelectionForRenderer == null) {
trackSelectionForRenderer = new FakeTrackSelection(trackGroup);
selectedTrackSelections.add(trackSelectionForRenderer);
}
return trackSelectionForRenderer;
}
/**
* Returns list of all {@link FakeTrackSelection}s that this track selector has made so far.
*/
public List<FakeTrackSelection> getSelectedTrackSelections() {
return selectedTrackSelections;
}
}