Fix an issue with seeking that can lead to STATE_END not delivered.

GitHub: #1897

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=174841175
This commit is contained in:
hoangtc 2017-11-07 05:22:48 -08:00 committed by Oliver Woodman
parent 3472eda6dc
commit 26366f6c80
2 changed files with 279 additions and 1 deletions

View File

@ -63,7 +63,11 @@ public final class CompositeSequenceableLoader implements SequenceableLoader {
break; break;
} }
for (SequenceableLoader loader : loaders) { for (SequenceableLoader loader : loaders) {
if (loader.getNextLoadPositionUs() == nextLoadPositionUs) { long loaderNextLoadPositionUs = loader.getNextLoadPositionUs();
boolean isLoaderBehind =
loaderNextLoadPositionUs != C.TIME_END_OF_SOURCE
&& loaderNextLoadPositionUs <= positionUs;
if (loaderNextLoadPositionUs == nextLoadPositionUs || isLoaderBehind) {
madeProgressThisIteration |= loader.continueLoading(positionUs); madeProgressThisIteration |= loader.continueLoading(positionUs);
} }
} }

View File

@ -0,0 +1,274 @@
/*
* 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.source;
import static com.google.common.truth.Truth.assertThat;
import com.google.android.exoplayer2.C;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
/**
* Unit test for {@link CompositeSequenceableLoader}.
*/
@RunWith(RobolectricTestRunner.class)
@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE)
public final class CompositeSequenceableLoaderTest {
/**
* Tests that {@link CompositeSequenceableLoader#getBufferedPositionUs()} returns minimum buffered
* position among all sub-loaders.
*/
@Test
public void testGetBufferedPositionUsReturnsMinimumLoaderBufferedPosition() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2001);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2});
assertThat(compositeSequenceableLoader.getBufferedPositionUs()).isEqualTo(1000);
}
/**
* Tests that {@link CompositeSequenceableLoader#getBufferedPositionUs()} returns minimum buffered
* position that is not {@link C#TIME_END_OF_SOURCE} among all sub-loaders.
*/
@Test
public void testGetBufferedPositionUsReturnsMinimumNonEndOfSourceLoaderBufferedPosition() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader3 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ C.TIME_END_OF_SOURCE,
/* nextLoadPositionUs */ C.TIME_END_OF_SOURCE);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2, loader3});
assertThat(compositeSequenceableLoader.getBufferedPositionUs()).isEqualTo(1000);
}
/**
* Tests that {@link CompositeSequenceableLoader#getBufferedPositionUs()} returns
* {@link C#TIME_END_OF_SOURCE} when all sub-loaders have buffered till end-of-source.
*/
@Test
public void testGetBufferedPositionUsReturnsEndOfSourceWhenAllLoaderBufferedTillEndOfSource() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ C.TIME_END_OF_SOURCE,
/* nextLoadPositionUs */ C.TIME_END_OF_SOURCE);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ C.TIME_END_OF_SOURCE,
/* nextLoadPositionUs */ C.TIME_END_OF_SOURCE);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2});
assertThat(compositeSequenceableLoader.getBufferedPositionUs()).isEqualTo(C.TIME_END_OF_SOURCE);
}
/**
* Tests that {@link CompositeSequenceableLoader#getNextLoadPositionUs()} returns minimum next
* load position among all sub-loaders.
*/
@Test
public void testGetNextLoadPositionUsReturnMinimumLoaderNextLoadPositionUs() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2001);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2000);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2});
assertThat(compositeSequenceableLoader.getNextLoadPositionUs()).isEqualTo(2000);
}
/**
* Tests that {@link CompositeSequenceableLoader#getNextLoadPositionUs()} returns minimum next
* load position that is not {@link C#TIME_END_OF_SOURCE} among all sub-loaders.
*/
@Test
public void testGetNextLoadPositionUsReturnMinimumNonEndOfSourceLoaderNextLoadPositionUs() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2001);
FakeSequenceableLoader loader3 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2, loader3});
assertThat(compositeSequenceableLoader.getNextLoadPositionUs()).isEqualTo(2000);
}
/**
* Tests that {@link CompositeSequenceableLoader#getNextLoadPositionUs()} returns
* {@link C#TIME_END_OF_SOURCE} when all sub-loaders have next load position at end-of-source.
*/
@Test
public void testGetNextLoadPositionUsReturnsEndOfSourceWhenAllLoaderLoadingLastChunk() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2});
assertThat(compositeSequenceableLoader.getNextLoadPositionUs()).isEqualTo(C.TIME_END_OF_SOURCE);
}
/**
* Tests that {@link CompositeSequenceableLoader#continueLoading(long)} only allows the loader
* with minimum next load position to continue loading if next load positions are not behind
* current playback position.
*/
@Test
public void testContinueLoadingOnlyAllowFurthestBehindLoaderToLoadIfNotBehindPlaybackPosition() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2001);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2});
compositeSequenceableLoader.continueLoading(100);
assertThat(loader1.numInvocations).isEqualTo(1);
assertThat(loader2.numInvocations).isEqualTo(0);
}
/**
* Tests that {@link CompositeSequenceableLoader#continueLoading(long)} allows all loaders
* with next load position behind current playback position to continue loading.
*/
@Test
public void testContinueLoadingReturnAllowAllLoadersBehindPlaybackPositionToLoad() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2001);
FakeSequenceableLoader loader3 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1002, /* nextLoadPositionUs */ 2002);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2, loader3});
compositeSequenceableLoader.continueLoading(3000);
assertThat(loader1.numInvocations).isEqualTo(1);
assertThat(loader2.numInvocations).isEqualTo(1);
assertThat(loader3.numInvocations).isEqualTo(1);
}
/**
* Tests that {@link CompositeSequenceableLoader#continueLoading(long)} does not allow loader
* with next load position at end-of-source to continue loading.
*/
@Test
public void testContinueLoadingOnlyNotAllowEndOfSourceLoaderToLoad() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(
/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ C.TIME_END_OF_SOURCE);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2});
compositeSequenceableLoader.continueLoading(3000);
assertThat(loader1.numInvocations).isEqualTo(0);
assertThat(loader2.numInvocations).isEqualTo(0);
}
/**
* Tests that {@link CompositeSequenceableLoader#continueLoading(long)} returns true if the loader
* with minimum next load position can make progress if next load positions are not behind
* current playback position.
*/
@Test
public void testContinueLoadingReturnTrueIfFurthestBehindLoaderCanMakeProgress() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2001);
loader1.setNextChunkDurationUs(1000);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2});
assertThat(compositeSequenceableLoader.continueLoading(100)).isTrue();
}
/**
* Tests that {@link CompositeSequenceableLoader#continueLoading(long)} returns true if any loader
* that are behind current playback position can make progress, even if it is not the one with
* minimum next load position.
*/
@Test
public void testContinueLoadingReturnTrueIfLoaderBehindPlaybackPositionCanMakeProgress() {
FakeSequenceableLoader loader1 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1000, /* nextLoadPositionUs */ 2000);
FakeSequenceableLoader loader2 =
new FakeSequenceableLoader(/* bufferedPositionUs */ 1001, /* nextLoadPositionUs */ 2001);
// loader2 is not the furthest behind, but it can make progress if allowed.
loader2.setNextChunkDurationUs(1000);
CompositeSequenceableLoader compositeSequenceableLoader = new CompositeSequenceableLoader(
new SequenceableLoader[] {loader1, loader2});
assertThat(compositeSequenceableLoader.continueLoading(3000)).isTrue();
}
private static class FakeSequenceableLoader implements SequenceableLoader {
private long bufferedPositionUs;
private long nextLoadPositionUs;
private int numInvocations;
private int nextChunkDurationUs;
private FakeSequenceableLoader(long bufferedPositionUs, long nextLoadPositionUs) {
this.bufferedPositionUs = bufferedPositionUs;
this.nextLoadPositionUs = nextLoadPositionUs;
}
@Override
public long getBufferedPositionUs() {
return bufferedPositionUs;
}
@Override
public long getNextLoadPositionUs() {
return nextLoadPositionUs;
}
@Override
public boolean continueLoading(long positionUs) {
numInvocations++;
boolean loaded = nextChunkDurationUs != 0;
// The current chunk has been loaded, advance to next chunk.
bufferedPositionUs = nextLoadPositionUs;
nextLoadPositionUs += nextChunkDurationUs;
nextChunkDurationUs = 0;
return loaded;
}
private void setNextChunkDurationUs(int nextChunkDurationUs) {
this.nextChunkDurationUs = nextChunkDurationUs;
}
}
}