mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Prevent stuck playback if shouldContinueLoading returns false
If LoadControl.shouldContinueLoading returns false and the renderers are not ready for playback using the already buffered data, playback is stuck. To prevent this situation, we always continue loading if the buffer is almost empty. We already have a similar workaround for when LoadControl.shouldStartPlayback returns false even if loading stopped. Having both workarounds allows playback to continue even if the LoadControl tries to prevent loading and playing all the time. PiperOrigin-RevId: 283516750
This commit is contained in:
parent
4f37d28eb1
commit
b84bde0252
@ -1817,6 +1817,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
long bufferedDurationUs =
|
||||
getTotalBufferedDurationUs(queue.getLoadingPeriod().getNextLoadPositionUs());
|
||||
if (bufferedDurationUs < 500_000) {
|
||||
// Prevent loading from getting stuck even if LoadControl.shouldContinueLoading returns false
|
||||
// when the buffer is empty or almost empty. We can't compare against 0 to account for small
|
||||
// differences between the renderer position and buffered position in the media at the point
|
||||
// where playback gets stuck.
|
||||
return true;
|
||||
}
|
||||
float playbackSpeed = mediaClock.getPlaybackParameters().speed;
|
||||
return loadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
|
||||
}
|
||||
|
@ -52,6 +52,10 @@ import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget;
|
||||
import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
|
||||
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
|
||||
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder;
|
||||
import com.google.android.exoplayer2.testutil.FakeAdaptiveDataSet;
|
||||
import com.google.android.exoplayer2.testutil.FakeAdaptiveMediaSource;
|
||||
import com.google.android.exoplayer2.testutil.FakeChunkSource;
|
||||
import com.google.android.exoplayer2.testutil.FakeDataSource;
|
||||
import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
|
||||
import com.google.android.exoplayer2.testutil.FakeMediaPeriod;
|
||||
import com.google.android.exoplayer2.testutil.FakeMediaSource;
|
||||
@ -3143,6 +3147,41 @@ public final class ExoPlayerTest {
|
||||
testRunner.blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadControlNeverWantsToLoadOrPlay_playbackDoesNotGetStuck() throws Exception {
|
||||
LoadControl neverLoadingOrPlayingLoadControl =
|
||||
new DefaultLoadControl() {
|
||||
@Override
|
||||
public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldStartPlayback(
|
||||
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Use chunked data to ensure the player actually needs to continue loading and playing.
|
||||
FakeAdaptiveDataSet.Factory dataSetFactory =
|
||||
new FakeAdaptiveDataSet.Factory(
|
||||
/* chunkDurationUs= */ 500_000, /* bitratePercentStdDev= */ 10.0);
|
||||
MediaSource chunkedMediaSource =
|
||||
new FakeAdaptiveMediaSource(
|
||||
new FakeTimeline(/* windowCount= */ 1),
|
||||
new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)),
|
||||
new FakeChunkSource.Factory(dataSetFactory, new FakeDataSource.Factory()));
|
||||
|
||||
new ExoPlayerTestRunner.Builder()
|
||||
.setLoadControl(neverLoadingOrPlayingLoadControl)
|
||||
.setMediaSource(chunkedMediaSource)
|
||||
.build(context)
|
||||
.start()
|
||||
// This throws if playback doesn't finish within timeout.
|
||||
.blockUntilEnded(TIMEOUT_MS);
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
|
||||
|
@ -56,18 +56,34 @@ public final class ExoPlayerTestRunner implements Player.EventListener, ActionSc
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
/**
|
||||
* A generic video {@link Format} which can be used to set up media sources and renderers.
|
||||
*/
|
||||
public static final Format VIDEO_FORMAT = Format.createVideoSampleFormat(null,
|
||||
MimeTypes.VIDEO_H264, null, Format.NO_VALUE, Format.NO_VALUE, 1280, 720, Format.NO_VALUE,
|
||||
null, null);
|
||||
/** A generic video {@link Format} which can be used to set up media sources and renderers. */
|
||||
public static final Format VIDEO_FORMAT =
|
||||
Format.createVideoSampleFormat(
|
||||
/* id= */ null,
|
||||
/* sampleMimeType= */ MimeTypes.VIDEO_H264,
|
||||
/* codecs= */ null,
|
||||
/* bitrate= */ 800_000,
|
||||
/* maxInputSize= */ Format.NO_VALUE,
|
||||
/* width= */ 1280,
|
||||
/* height= */ 720,
|
||||
/* frameRate= */ Format.NO_VALUE,
|
||||
/* initializationData= */ null,
|
||||
/* drmInitData= */ null);
|
||||
|
||||
/**
|
||||
* A generic audio {@link Format} which can be used to set up media sources and renderers.
|
||||
*/
|
||||
public static final Format AUDIO_FORMAT = Format.createAudioSampleFormat(null,
|
||||
MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null);
|
||||
/** A generic audio {@link Format} which can be used to set up media sources and renderers. */
|
||||
public static final Format AUDIO_FORMAT =
|
||||
Format.createAudioSampleFormat(
|
||||
/* id= */ null,
|
||||
/* sampleMimeType= */ MimeTypes.AUDIO_AAC,
|
||||
/* codecs= */ null,
|
||||
/* bitrate= */ 100_000,
|
||||
/* maxInputSize= */ Format.NO_VALUE,
|
||||
/* channelCount= */ 2,
|
||||
/* sampleRate= */ 44100,
|
||||
/* initializationData=*/ null,
|
||||
/* drmInitData= */ null,
|
||||
/* selectionFlags= */ 0,
|
||||
/* language= */ null);
|
||||
|
||||
private Clock clock;
|
||||
private Timeline timeline;
|
||||
|
Loading…
x
Reference in New Issue
Block a user