Replace FakeExoPlayer with real player running with fake clock.
This ensures that simulated playbacks always use the current player implementation. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=179929911
This commit is contained in:
parent
410e614cfd
commit
f279f3c843
@ -19,7 +19,6 @@ import android.os.HandlerThread;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.LoadControl;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
@ -32,11 +31,11 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.metadata.MetadataOutput;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder.PlayerFactory;
|
||||
import com.google.android.exoplayer2.text.TextOutput;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.HandlerWrapper;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
@ -59,26 +58,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
/**
|
||||
* Factory to create an {@link SimpleExoPlayer} instance. The player will be created on its own
|
||||
* {@link HandlerThread}.
|
||||
*/
|
||||
public interface PlayerFactory {
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleExoPlayer} using the provided renderers factory, track selector,
|
||||
* and load control.
|
||||
*
|
||||
* @param renderersFactory A {@link RenderersFactory} to be used for the new player.
|
||||
* @param trackSelector A {@link MappingTrackSelector} to be used for the new player.
|
||||
* @param loadControl A {@link LoadControl} to be used for the new player.
|
||||
* @return A new {@link SimpleExoPlayer}.
|
||||
*/
|
||||
SimpleExoPlayer createExoPlayer(RenderersFactory renderersFactory,
|
||||
MappingTrackSelector trackSelector, LoadControl loadControl);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic video {@link Format} which can be used to set up media sources and renderers.
|
||||
*/
|
||||
@ -93,7 +72,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
|
||||
MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null);
|
||||
|
||||
private Clock clock;
|
||||
private PlayerFactory playerFactory;
|
||||
private Timeline timeline;
|
||||
private Object manifest;
|
||||
private MediaSource mediaSource;
|
||||
@ -223,21 +201,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link PlayerFactory} which creates the {@link SimpleExoPlayer} to be used by the
|
||||
* test runner. The default value is a {@link SimpleExoPlayer} with the renderers provided by
|
||||
* {@link #setRenderers(Renderer...)} or {@link #setRenderersFactory(RenderersFactory)}, the
|
||||
* track selector provided by {@link #setTrackSelector(MappingTrackSelector)} and the load
|
||||
* control provided by {@link #setLoadControl(LoadControl)}.
|
||||
*
|
||||
* @param playerFactory A {@link PlayerFactory} to create the player.
|
||||
* @return This builder.
|
||||
*/
|
||||
public Builder setExoPlayer(PlayerFactory playerFactory) {
|
||||
this.playerFactory = playerFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} to be used by the test runner. The default value is {@link
|
||||
* Clock#DEFAULT}.
|
||||
@ -345,15 +308,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
|
||||
if (clock == null) {
|
||||
clock = Clock.DEFAULT;
|
||||
}
|
||||
if (playerFactory == null) {
|
||||
playerFactory = new PlayerFactory() {
|
||||
@Override
|
||||
public SimpleExoPlayer createExoPlayer(RenderersFactory renderersFactory,
|
||||
MappingTrackSelector trackSelector, LoadControl loadControl) {
|
||||
return ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, loadControl);
|
||||
}
|
||||
};
|
||||
}
|
||||
if (mediaSource == null) {
|
||||
if (timeline == null) {
|
||||
timeline = new FakeTimeline(1);
|
||||
@ -365,7 +319,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
|
||||
}
|
||||
return new ExoPlayerTestRunner(
|
||||
clock,
|
||||
playerFactory,
|
||||
mediaSource,
|
||||
renderersFactory,
|
||||
trackSelector,
|
||||
@ -378,7 +331,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
|
||||
}
|
||||
}
|
||||
|
||||
private final PlayerFactory playerFactory;
|
||||
private final Clock clock;
|
||||
private final MediaSource mediaSource;
|
||||
private final RenderersFactory renderersFactory;
|
||||
private final MappingTrackSelector trackSelector;
|
||||
@ -405,7 +358,6 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
|
||||
|
||||
private ExoPlayerTestRunner(
|
||||
Clock clock,
|
||||
PlayerFactory playerFactory,
|
||||
MediaSource mediaSource,
|
||||
RenderersFactory renderersFactory,
|
||||
MappingTrackSelector trackSelector,
|
||||
@ -415,7 +367,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
|
||||
@Nullable VideoRendererEventListener videoRendererEventListener,
|
||||
@Nullable AudioRendererEventListener audioRendererEventListener,
|
||||
int expectedPlayerEndedCount) {
|
||||
this.playerFactory = playerFactory;
|
||||
this.clock = clock;
|
||||
this.mediaSource = mediaSource;
|
||||
this.renderersFactory = renderersFactory;
|
||||
this.trackSelector = trackSelector;
|
||||
@ -451,7 +403,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
player = playerFactory.createExoPlayer(renderersFactory, trackSelector, loadControl);
|
||||
player = new TestSimpleExoPlayer(renderersFactory, trackSelector, loadControl, clock);
|
||||
player.addListener(ExoPlayerTestRunner.this);
|
||||
if (eventListener != null) {
|
||||
player.addListener(eventListener);
|
||||
@ -685,4 +637,15 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
|
||||
actionScheduleFinishedCountDownLatch.countDown();
|
||||
}
|
||||
|
||||
/** SimpleExoPlayer implementation using a custom Clock. */
|
||||
private static final class TestSimpleExoPlayer extends SimpleExoPlayer {
|
||||
|
||||
public TestSimpleExoPlayer(
|
||||
RenderersFactory renderersFactory,
|
||||
TrackSelector trackSelector,
|
||||
LoadControl loadControl,
|
||||
Clock clock) {
|
||||
super(renderersFactory, trackSelector, loadControl, clock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Fake {@link Clock} implementation independent of {@link android.os.SystemClock}. */
|
||||
public final class FakeClock implements Clock {
|
||||
public class FakeClock implements Clock {
|
||||
|
||||
private final List<Long> wakeUpTimes;
|
||||
private final List<HandlerMessageData> handlerMessages;
|
||||
|
@ -1,541 +0,0 @@
|
||||
/*
|
||||
* 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.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.LoadControl;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Renderer;
|
||||
import com.google.android.exoplayer2.RendererCapabilities;
|
||||
import com.google.android.exoplayer2.RenderersFactory;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.Timeline.Period;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.source.SampleStream;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* Fake {@link SimpleExoPlayer} which runs a simplified copy of the playback loop as fast as
|
||||
* possible without waiting. It does only support single period timelines and does not support
|
||||
* updates during playback (like seek, timeline changes, repeat mode changes).
|
||||
*/
|
||||
public class FakeSimpleExoPlayer extends SimpleExoPlayer {
|
||||
|
||||
private FakeExoPlayer player;
|
||||
|
||||
public FakeSimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector,
|
||||
LoadControl loadControl, FakeClock clock) {
|
||||
super(renderersFactory, trackSelector, loadControl, clock);
|
||||
player.setFakeClock(clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExoPlayer createExoPlayerImpl(
|
||||
Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Clock clock) {
|
||||
this.player = new FakeExoPlayer(renderers, trackSelector, loadControl);
|
||||
return player;
|
||||
}
|
||||
|
||||
private static class FakeExoPlayer extends StubExoPlayer implements MediaSource.Listener,
|
||||
MediaPeriod.Callback, Runnable {
|
||||
|
||||
private final Renderer[] renderers;
|
||||
private final TrackSelector trackSelector;
|
||||
private final LoadControl loadControl;
|
||||
private final CopyOnWriteArraySet<Player.EventListener> eventListeners;
|
||||
private final HandlerThread playbackThread;
|
||||
private final Handler playbackHandler;
|
||||
private final Handler eventListenerHandler;
|
||||
|
||||
private FakeClock clock;
|
||||
private MediaSource mediaSource;
|
||||
private Timeline timeline;
|
||||
private Object manifest;
|
||||
private MediaPeriod mediaPeriod;
|
||||
private TrackSelectorResult selectorResult;
|
||||
|
||||
private boolean isStartingUp;
|
||||
private boolean isLoading;
|
||||
private int playbackState;
|
||||
private long rendererPositionUs;
|
||||
private long durationUs;
|
||||
private volatile long currentPositionMs;
|
||||
private volatile long bufferedPositionMs;
|
||||
|
||||
public FakeExoPlayer(Renderer[] renderers, TrackSelector trackSelector,
|
||||
LoadControl loadControl) {
|
||||
this.renderers = renderers;
|
||||
this.trackSelector = trackSelector;
|
||||
this.loadControl = loadControl;
|
||||
this.eventListeners = new CopyOnWriteArraySet<>();
|
||||
Looper eventListenerLooper = Looper.myLooper();
|
||||
this.eventListenerHandler = new Handler(eventListenerLooper != null ? eventListenerLooper
|
||||
: Looper.getMainLooper());
|
||||
this.playbackThread = new HandlerThread("FakeExoPlayer Thread");
|
||||
playbackThread.start();
|
||||
this.playbackHandler = new Handler(playbackThread.getLooper());
|
||||
this.isStartingUp = true;
|
||||
this.isLoading = false;
|
||||
this.playbackState = Player.STATE_IDLE;
|
||||
this.durationUs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
public void setFakeClock(FakeClock clock) {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Player.EventListener listener) {
|
||||
eventListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(Player.EventListener listener) {
|
||||
eventListeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPlaybackState() {
|
||||
return playbackState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayWhenReady(boolean playWhenReady) {
|
||||
if (!playWhenReady) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPlayWhenReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRepeatMode() {
|
||||
return Player.REPEAT_MODE_OFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getShuffleModeEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoading() {
|
||||
return isLoading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaybackParameters getPlaybackParameters() {
|
||||
return PlaybackParameters.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
stop(/* reset= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
stopPlayback(/* quitPlaybackThread= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("ThreadJoinLoop")
|
||||
public void release() {
|
||||
stopPlayback(/* quitPlaybackThread= */ true);
|
||||
while (playbackThread.isAlive()) {
|
||||
try {
|
||||
playbackThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore interrupt.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRendererCount() {
|
||||
return renderers.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRendererType(int index) {
|
||||
return renderers[index].getTrackType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackGroupArray getCurrentTrackGroups() {
|
||||
return selectorResult != null ? selectorResult.groups : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackSelectionArray getCurrentTrackSelections() {
|
||||
return selectorResult != null ? selectorResult.selections : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getCurrentManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeline getCurrentTimeline() {
|
||||
return timeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentPeriodIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentWindowIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextWindowIndex() {
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviousWindowIndex() {
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return C.usToMs(durationUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCurrentPosition() {
|
||||
return currentPositionMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPosition() {
|
||||
return bufferedPositionMs == C.TIME_END_OF_SOURCE ? getDuration() : bufferedPositionMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferedPercentage() {
|
||||
long duration = getDuration();
|
||||
return duration == C.TIME_UNSET ? 0 : (int) (getBufferedPosition() * 100 / duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentWindowDynamic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentWindowSeekable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayingAd() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentAdGroupIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentAdIndexInAdGroup() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentPosition() {
|
||||
return getCurrentPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Looper getPlaybackLooper() {
|
||||
return playbackThread.getLooper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(MediaSource mediaSource) {
|
||||
prepare(mediaSource, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(final MediaSource mediaSource, boolean resetPosition, boolean resetState) {
|
||||
if (!resetPosition || !resetState) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
this.mediaSource = mediaSource;
|
||||
playbackHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.prepareSource(FakeExoPlayer.this, true, FakeExoPlayer.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// MediaSource.Listener
|
||||
|
||||
@Override
|
||||
public void onSourceInfoRefreshed(MediaSource source, final Timeline timeline,
|
||||
final @Nullable Object manifest) {
|
||||
if (this.timeline != null) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
Assertions.checkArgument(timeline.getPeriodCount() == 1);
|
||||
Assertions.checkArgument(timeline.getWindowCount() == 1);
|
||||
final ConditionVariable waitForNotification = new ConditionVariable();
|
||||
eventListenerHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Player.EventListener eventListener : eventListeners) {
|
||||
FakeExoPlayer.this.durationUs = timeline.getPeriod(0, new Period()).durationUs;
|
||||
FakeExoPlayer.this.timeline = timeline;
|
||||
FakeExoPlayer.this.manifest = manifest;
|
||||
eventListener.onTimelineChanged(timeline, manifest,
|
||||
Player.TIMELINE_CHANGE_REASON_PREPARED);
|
||||
waitForNotification.open();
|
||||
}
|
||||
}
|
||||
});
|
||||
waitForNotification.block();
|
||||
this.mediaPeriod = mediaSource.createPeriod(new MediaPeriodId(0), loadControl.getAllocator());
|
||||
mediaPeriod.prepare(this, 0);
|
||||
}
|
||||
|
||||
// MediaPeriod.Callback
|
||||
|
||||
@Override
|
||||
public void onContinueLoadingRequested(MediaPeriod source) {
|
||||
maybeContinueLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepared(MediaPeriod mediaPeriod) {
|
||||
try {
|
||||
initializePlaybackLoop();
|
||||
} catch (ExoPlaybackException e) {
|
||||
handlePlayerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Runnable (Playback loop).
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
maybeContinueLoading();
|
||||
mediaPeriod.discardBuffer(rendererPositionUs, /* toKeyframe= */ false);
|
||||
boolean allRenderersEnded = true;
|
||||
boolean allRenderersReadyOrEnded = true;
|
||||
if (playbackState == Player.STATE_READY) {
|
||||
for (Renderer renderer : renderers) {
|
||||
renderer.render(rendererPositionUs, C.msToUs(clock.elapsedRealtime()));
|
||||
if (!renderer.isEnded()) {
|
||||
allRenderersEnded = false;
|
||||
}
|
||||
if (!(renderer.isReady() || renderer.isEnded())) {
|
||||
allRenderersReadyOrEnded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rendererPositionUs >= durationUs && allRenderersEnded) {
|
||||
changePlaybackState(Player.STATE_ENDED);
|
||||
return;
|
||||
}
|
||||
long bufferedPositionUs = mediaPeriod.getBufferedPositionUs();
|
||||
if (playbackState == Player.STATE_BUFFERING && allRenderersReadyOrEnded
|
||||
&& haveSufficientBuffer(!isStartingUp, rendererPositionUs, bufferedPositionUs)) {
|
||||
changePlaybackState(Player.STATE_READY);
|
||||
isStartingUp = false;
|
||||
} else if (playbackState == Player.STATE_READY && !allRenderersReadyOrEnded) {
|
||||
changePlaybackState(Player.STATE_BUFFERING);
|
||||
}
|
||||
// Advance simulated time by 10ms.
|
||||
clock.advanceTime(10);
|
||||
if (playbackState == Player.STATE_READY) {
|
||||
rendererPositionUs += 10000;
|
||||
}
|
||||
this.currentPositionMs = C.usToMs(rendererPositionUs);
|
||||
this.bufferedPositionMs = C.usToMs(bufferedPositionUs);
|
||||
playbackHandler.post(this);
|
||||
} catch (ExoPlaybackException e) {
|
||||
handlePlayerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal logic
|
||||
|
||||
private void initializePlaybackLoop() throws ExoPlaybackException {
|
||||
Assertions.checkNotNull(clock);
|
||||
trackSelector.init(new InvalidationListener() {
|
||||
@Override
|
||||
public void onTrackSelectionsInvalidated() {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
});
|
||||
RendererCapabilities[] rendererCapabilities = new RendererCapabilities[renderers.length];
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
rendererCapabilities[i] = renderers[i].getCapabilities();
|
||||
}
|
||||
selectorResult = trackSelector.selectTracks(rendererCapabilities,
|
||||
mediaPeriod.getTrackGroups());
|
||||
SampleStream[] sampleStreams = new SampleStream[renderers.length];
|
||||
boolean[] mayRetainStreamFlags = new boolean[renderers.length];
|
||||
Arrays.fill(mayRetainStreamFlags, true);
|
||||
mediaPeriod.selectTracks(
|
||||
selectorResult.selections.getAll(),
|
||||
mayRetainStreamFlags,
|
||||
sampleStreams,
|
||||
new boolean[renderers.length],
|
||||
/* positionUs = */ 0);
|
||||
eventListenerHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Player.EventListener eventListener : eventListeners) {
|
||||
eventListener.onTracksChanged(selectorResult.groups, selectorResult.selections);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loadControl.onPrepared();
|
||||
loadControl.onTracksSelected(renderers, selectorResult.groups, selectorResult.selections);
|
||||
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
TrackSelection selection = selectorResult.selections.get(i);
|
||||
Format[] formats = new Format[selection.length()];
|
||||
for (int j = 0; j < formats.length; j++) {
|
||||
formats[j] = selection.getFormat(j);
|
||||
}
|
||||
renderers[i].enable(selectorResult.rendererConfigurations[i], formats, sampleStreams[i], 0,
|
||||
false, 0);
|
||||
renderers[i].setCurrentStreamFinal();
|
||||
}
|
||||
|
||||
rendererPositionUs = 0;
|
||||
changePlaybackState(Player.STATE_BUFFERING);
|
||||
playbackHandler.post(this);
|
||||
}
|
||||
|
||||
private void maybeContinueLoading() {
|
||||
boolean newIsLoading = false;
|
||||
long nextLoadPositionUs = mediaPeriod.getNextLoadPositionUs();
|
||||
if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) {
|
||||
long bufferedDurationUs = nextLoadPositionUs - rendererPositionUs;
|
||||
if (loadControl.shouldContinueLoading(bufferedDurationUs, 1f)) {
|
||||
newIsLoading = true;
|
||||
mediaPeriod.continueLoading(rendererPositionUs);
|
||||
}
|
||||
}
|
||||
if (newIsLoading != isLoading) {
|
||||
isLoading = newIsLoading;
|
||||
eventListenerHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Player.EventListener eventListener : eventListeners) {
|
||||
eventListener.onLoadingChanged(isLoading);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean haveSufficientBuffer(boolean rebuffering, long rendererPositionUs,
|
||||
long bufferedPositionUs) {
|
||||
return bufferedPositionUs == C.TIME_END_OF_SOURCE
|
||||
|| loadControl.shouldStartPlayback(
|
||||
bufferedPositionUs - rendererPositionUs, 1f, rebuffering);
|
||||
}
|
||||
|
||||
private void handlePlayerError(final ExoPlaybackException e) {
|
||||
eventListenerHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Player.EventListener listener : eventListeners) {
|
||||
listener.onPlayerError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
changePlaybackState(Player.STATE_ENDED);
|
||||
}
|
||||
|
||||
private void changePlaybackState(final int playbackState) {
|
||||
this.playbackState = playbackState;
|
||||
eventListenerHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Player.EventListener listener : eventListeners) {
|
||||
listener.onPlayerStateChanged(true, playbackState);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void releaseMedia() {
|
||||
if (mediaSource != null) {
|
||||
if (mediaPeriod != null) {
|
||||
mediaSource.releasePeriod(mediaPeriod);
|
||||
mediaPeriod = null;
|
||||
}
|
||||
mediaSource.releaseSource();
|
||||
mediaSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPlayback(final boolean quitPlaybackThread) {
|
||||
playbackHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run () {
|
||||
playbackHandler.removeCallbacksAndMessages(null);
|
||||
releaseMedia();
|
||||
changePlaybackState(Player.STATE_IDLE);
|
||||
if (quitPlaybackThread) {
|
||||
playbackThread.quit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user