Add fake simple exo player.
This implementation runs as fast as possible by triggering a simplified playback loop continuously without waiting. The class only supports a basic use case with a single-period timeline, no timeline update, no seeks or other user-initiated actions. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=164120420
This commit is contained in:
parent
6f7dc974c9
commit
ccd05cbd04
@ -141,7 +141,7 @@ public class SimpleExoPlayer implements ExoPlayer {
|
||||
videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
|
||||
|
||||
// Build the player and associated objects.
|
||||
player = new ExoPlayerImpl(renderers, trackSelector, loadControl);
|
||||
player = createExoPlayerImpl(renderers, trackSelector, loadControl);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -722,6 +722,19 @@ public class SimpleExoPlayer implements ExoPlayer {
|
||||
|
||||
// Internal methods.
|
||||
|
||||
/**
|
||||
* Creates the ExoPlayer implementation used by this {@link SimpleExoPlayer}.
|
||||
*
|
||||
* @param renderers The {@link Renderer}s that will be used by the instance.
|
||||
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||
* @param loadControl The {@link LoadControl} that will be used by the instance.
|
||||
* @return A new {@link ExoPlayer} instance.
|
||||
*/
|
||||
protected ExoPlayer createExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector,
|
||||
LoadControl loadControl) {
|
||||
return new ExoPlayerImpl(renderers, trackSelector, loadControl);
|
||||
}
|
||||
|
||||
private void removeSurfaceCallbacks() {
|
||||
if (textureView != null) {
|
||||
if (textureView.getSurfaceTextureListener() != componentListener) {
|
||||
|
@ -0,0 +1,521 @@
|
||||
/*
|
||||
* 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 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);
|
||||
player.setFakeClock(clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExoPlayer createExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector,
|
||||
LoadControl loadControl) {
|
||||
this.player = new FakeExoPlayer(renderers, trackSelector, loadControl);
|
||||
return player;
|
||||
}
|
||||
|
||||
private class FakeExoPlayer implements ExoPlayer, 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 != true) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPlayWhenReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRepeatMode(@RepeatMode int repeatMode) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRepeatMode() {
|
||||
return Player.REPEAT_MODE_OFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoading() {
|
||||
return isLoading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToDefaultPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToDefaultPosition(int windowIndex) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(long positionMs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(int windowIndex, long positionMs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaybackParameters getPlaybackParameters() {
|
||||
return PlaybackParameters.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
playbackThread.quit();
|
||||
}
|
||||
|
||||
@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 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 != true || resetState != true) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
this.mediaSource = mediaSource;
|
||||
playbackHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mediaSource.prepareSource(FakeExoPlayer.this, true, FakeExoPlayer.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessages(ExoPlayerMessage... messages) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blockingSendMessages(ExoPlayerMessage... messages) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
// MediaSource.Listener
|
||||
|
||||
@Override
|
||||
public void onSourceInfoRefreshed(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);
|
||||
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();
|
||||
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], 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)) {
|
||||
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) {
|
||||
if (bufferedPositionUs == C.TIME_END_OF_SOURCE) {
|
||||
return true;
|
||||
}
|
||||
return loadControl.shouldStartPlayback(bufferedPositionUs - rendererPositionUs, 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user