Commit ced7de15 by olly Committed by Oliver Woodman

Promote DemoPlayer to library as SimpleExoPlayer.

DemoPlayer moves into core library as SimpleExoPlayer, which
implements ExoPlayer.

Issue: #383
Issue: #592
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123090184
parent 075e095c
Showing with 419 additions and 232 deletions
......@@ -16,16 +16,17 @@
package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DefaultTrackSelector;
import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.SimpleExoPlayer;
import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
import com.google.android.exoplayer.demo.player.DemoPlayer;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
......@@ -39,9 +40,9 @@ import java.util.Locale;
/**
* Logs player events using {@link Log}.
*/
public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener,
public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.DebugListener,
ChunkTrackStreamEventListener, ExtractorSampleSource.EventListener,
StreamingDrmSessionManager.EventListener {
StreamingDrmSessionManager.EventListener, DefaultTrackSelector.EventListener {
private static final String TAG = "EventLogger";
private static final NumberFormat TIME_FORMAT;
......@@ -62,26 +63,26 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
Log.d(TAG, "end [" + getSessionTimeString() + "]");
}
// DemoPlayer.Listener
// ExoPlayer.EventListener
@Override
public void onStateChanged(boolean playWhenReady, int state) {
public void onPlayerStateChanged(boolean playWhenReady, int state) {
Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", "
+ getStateString(state) + "]");
}
@Override
public void onError(ExoPlaybackException e) {
Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e);
public void onPlayWhenReadyCommitted() {
// Do nothing.
}
@Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) {
Log.d(TAG, "videoSizeChanged [" + width + ", " + height + ", " + unappliedRotationDegrees
+ ", " + pixelWidthHeightRatio + "]");
public void onPlayerError(ExoPlaybackException e) {
Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e);
}
// DefaultTrackSelector.EventListener
@Override
public void onTracksChanged(TrackInfo trackInfo) {
Log.d(TAG, "Tracks [");
......@@ -130,7 +131,7 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
Log.d(TAG, "]");
}
// DemoPlayer.InfoListener
// SimpleExoPlayer.DebugListener
@Override
public void onAudioDecoderInitialized(String decoderName, long elapsedRealtimeMs,
......
......@@ -17,15 +17,18 @@ package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.AspectRatioFrameLayout;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DefaultTrackSelectionPolicy;
import com.google.android.exoplayer.DefaultTrackSelector;
import com.google.android.exoplayer.DefaultTrackSelector.TrackInfo;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.ExoPlayerFactory;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SimpleExoPlayer;
import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.dash.DashSampleSource;
import com.google.android.exoplayer.demo.player.DemoPlayer;
import com.google.android.exoplayer.demo.ui.TrackSelectionHelper;
import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
......@@ -46,6 +49,7 @@ import com.google.android.exoplayer.upstream.DataSourceFactory;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer.util.DebugTextViewHelper;
import com.google.android.exoplayer.util.PlayerControl;
import com.google.android.exoplayer.util.Util;
import android.Manifest.permission;
......@@ -61,6 +65,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
......@@ -81,10 +86,11 @@ import java.util.List;
import java.util.UUID;
/**
* An activity that plays media using {@link DemoPlayer}.
* An activity that plays media using {@link SimpleExoPlayer}.
*/
public class PlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener {
ExoPlayer.EventListener, SimpleExoPlayer.VideoListener, SimpleExoPlayer.CaptionListener,
SimpleExoPlayer.Id3MetadataListener, DefaultTrackSelector.EventListener {
// For use within demo app code.
public static final String CONTENT_TYPE_EXTRA = "content_type";
......@@ -114,12 +120,12 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
private AspectRatioFrameLayout videoFrame;
private SurfaceView surfaceView;
private TextView debugTextView;
private TextView playerStateTextView;
private SubtitleLayout subtitleLayout;
private Button retryButton;
private DataSourceFactory dataSourceFactory;
private DemoPlayer player;
private SimpleExoPlayer player;
private DefaultTrackSelector trackSelector;
private TrackSelectionHelper trackSelectionHelper;
private DebugTextViewHelper debugViewHelper;
private boolean playerNeedsSource;
......@@ -163,10 +169,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
surfaceView = (SurfaceView) findViewById(R.id.surface_view);
surfaceView.getHolder().addCallback(this);
debugTextView = (TextView) findViewById(R.id.debug_text_view);
playerStateTextView = (TextView) findViewById(R.id.player_state_view);
subtitleLayout = (SubtitleLayout) findViewById(R.id.subtitles);
mediaController = new KeyCompatibleMediaController(this);
retryButton = (Button) findViewById(R.id.retry_button);
retryButton.setOnClickListener(this);
......@@ -226,7 +229,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
initializePlayer();
} else if (view.getParent() == debugRootView) {
trackSelectionHelper.showSelectionDialog(this, ((Button) view).getText(),
player.getTrackInfo(), (int) view.getTag());
trackSelector.getTrackInfo(), (int) view.getTag());
}
}
......@@ -279,6 +282,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
int type = intent.getIntExtra(CONTENT_TYPE_EXTRA,
inferContentType(uri, intent.getStringExtra(CONTENT_EXT_EXTRA)));
if (player == null) {
boolean useExtensionDecoders = intent.getBooleanExtra(USE_EXTENSION_DECODERS, false);
UUID drmSchemeUuid = (UUID) intent.getSerializableExtra(DRM_SCHEME_UUID_EXTRA);
DrmSessionManager drmSessionManager = null;
if (drmSchemeUuid != null) {
......@@ -291,20 +295,24 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
return;
}
}
boolean useExtensionDecoders = intent.getBooleanExtra(USE_EXTENSION_DECODERS, false);
eventLogger = new EventLogger();
eventLogger.startSession();
player = new DemoPlayer(this, drmSessionManager, useExtensionDecoders);
trackSelector = new DefaultTrackSelector(new DefaultTrackSelectionPolicy(), mainHandler);
trackSelector.addListener(this);
trackSelector.addListener(eventLogger);
trackSelectionHelper = new TrackSelectionHelper(trackSelector);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, drmSessionManager,
useExtensionDecoders);
player.addListener(this);
player.addListener(eventLogger);
player.setInfoListener(eventLogger);
player.setDebugListener(eventLogger);
player.setVideoListener(this);
player.setCaptionListener(this);
player.setMetadataListener(this);
player.seekTo(playerPosition);
player.setSurface(surfaceView.getHolder().getSurface());
player.setPlayWhenReady(true);
trackSelectionHelper = new TrackSelectionHelper(player.getTrackSelector());
mediaController.setMediaPlayer(player.getPlayerControl());
mediaController.setMediaPlayer(new PlayerControl(player));
mediaController.setAnchorView(rootView);
debugViewHelper = new DebugTextViewHelper(player, debugTextView);
debugViewHelper.start();
......@@ -373,47 +381,30 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
playerPosition = player.getCurrentPosition();
player.release();
player = null;
trackSelector = null;
trackSelectionHelper = null;
eventLogger.endSession();
eventLogger = null;
}
}
// DemoPlayer.Listener implementation
@Override
public void onTracksChanged(TrackInfo trackSet) {
updateButtonVisibilities();
}
// ExoPlayer.EventListener implementation
@Override
public void onStateChanged(boolean playWhenReady, int playbackState) {
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == ExoPlayer.STATE_ENDED) {
showControls();
}
String text = "playWhenReady=" + playWhenReady + ", playbackState=";
switch(playbackState) {
case ExoPlayer.STATE_BUFFERING:
text += "buffering";
break;
case ExoPlayer.STATE_ENDED:
text += "ended";
break;
case ExoPlayer.STATE_IDLE:
text += "idle";
break;
case ExoPlayer.STATE_READY:
text += "ready";
break;
default:
text += "unknown";
break;
}
playerStateTextView.setText(text);
updateButtonVisibilities();
}
@Override
public void onError(ExoPlaybackException e) {
public void onPlayWhenReadyCommitted() {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException e) {
String errorString = null;
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
Exception cause = e.getRendererException();
......@@ -445,14 +436,27 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
showControls();
}
// SimpleExoPlayer.VideoListener implementation
@Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthAspectRatio) {
shutterView.setVisibility(View.GONE);
videoFrame.setAspectRatio(
height == 0 ? 1 : (width * pixelWidthAspectRatio) / height);
}
@Override
public void onDrawnToSurface(Surface surface) {
shutterView.setVisibility(View.GONE);
}
// DefaultTrackSelector.EventListener implementation
@Override
public void onTracksChanged(TrackInfo trackSet) {
updateButtonVisibilities();
}
// User controls
private void updateButtonVisibilities() {
......@@ -461,8 +465,12 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
retryButton.setVisibility(playerNeedsSource ? View.VISIBLE : View.GONE);
debugRootView.addView(retryButton);
TrackInfo trackInfo;
if (player == null || (trackInfo = player.getTrackInfo()) == null) {
if (player == null) {
return;
}
TrackInfo trackInfo = trackSelector.getTrackInfo();
if (trackInfo == null) {
return;
}
......@@ -500,14 +508,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
debugRootView.setVisibility(View.VISIBLE);
}
// DemoPlayer.CaptionListener implementation
// SimpleExoPlayer.CaptionListener implementation
@Override
public void onCues(List<Cue> cues) {
subtitleLayout.setCues(cues);
}
// DemoPlayer.MetadataListener implementation
// SimpleExoPlayer.MetadataListener implementation
@Override
public void onId3Metadata(List<Id3Frame> id3Frames) {
......
......@@ -48,14 +48,6 @@
android:background="#88000000"
android:orientation="vertical">
<TextView android:id="@+id/player_state_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textSize="10sp"
tools:ignore="SmallSp"/>
<TextView android:id="@+id/debug_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
......
......@@ -19,6 +19,7 @@ import com.google.android.exoplayer.DefaultTrackSelectionPolicy;
import com.google.android.exoplayer.DefaultTrackSelector;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.ExoPlayerFactory;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
......@@ -57,7 +58,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
}
}
private static class TestPlaybackThread extends Thread implements ExoPlayer.Listener {
private static class TestPlaybackThread extends Thread implements ExoPlayer.EventListener {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int BUFFER_SEGMENT_COUNT = 16;
......@@ -77,9 +78,9 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
public void run() {
Looper.prepare();
LibflacAudioTrackRenderer audioRenderer = new LibflacAudioTrackRenderer();
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null, null,
new DefaultTrackSelectionPolicy());
player = ExoPlayer.Factory.newInstance(new TrackRenderer[] {audioRenderer}, trackSelector);
DefaultTrackSelector trackSelector = new DefaultTrackSelector(
new DefaultTrackSelectionPolicy(), null);
player = ExoPlayerFactory.newInstance(new TrackRenderer[] {audioRenderer}, trackSelector);
player.addListener(this);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(
uri,
......
......@@ -19,6 +19,7 @@ import com.google.android.exoplayer.DefaultTrackSelectionPolicy;
import com.google.android.exoplayer.DefaultTrackSelector;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.ExoPlayerFactory;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
......@@ -57,7 +58,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
}
}
private static class TestPlaybackThread extends Thread implements ExoPlayer.Listener {
private static class TestPlaybackThread extends Thread implements ExoPlayer.EventListener {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int BUFFER_SEGMENT_COUNT = 16;
......@@ -77,9 +78,9 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
public void run() {
Looper.prepare();
LibopusAudioTrackRenderer audioRenderer = new LibopusAudioTrackRenderer();
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null, null,
new DefaultTrackSelectionPolicy());
player = ExoPlayer.Factory.newInstance(new TrackRenderer[] {audioRenderer}, trackSelector);
DefaultTrackSelector trackSelector = new DefaultTrackSelector(
new DefaultTrackSelectionPolicy(), null);
player = ExoPlayerFactory.newInstance(new TrackRenderer[] {audioRenderer}, trackSelector);
player.addListener(this);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(
uri,
......
......@@ -19,6 +19,7 @@ import com.google.android.exoplayer.DefaultTrackSelectionPolicy;
import com.google.android.exoplayer.DefaultTrackSelector;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.ExoPlayerFactory;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
......@@ -73,7 +74,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
}
}
private static class TestPlaybackThread extends Thread implements ExoPlayer.Listener {
private static class TestPlaybackThread extends Thread implements ExoPlayer.EventListener {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int BUFFER_SEGMENT_COUNT = 16;
......@@ -93,9 +94,9 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
public void run() {
Looper.prepare();
LibvpxVideoTrackRenderer videoRenderer = new LibvpxVideoTrackRenderer(true);
DefaultTrackSelector trackSelector = new DefaultTrackSelector(null, null,
new DefaultTrackSelectionPolicy());
player = ExoPlayer.Factory.newInstance(new TrackRenderer[] {videoRenderer}, trackSelector);
DefaultTrackSelector trackSelector = new DefaultTrackSelector(
new DefaultTrackSelectionPolicy(), null);
player = ExoPlayerFactory.newInstance(new TrackRenderer[] {videoRenderer}, trackSelector);
player.addListener(this);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(
uri,
......
......@@ -333,7 +333,8 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
@Override
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
eventDispatcher.codecCounters(codecCounters);
codecCounters.reset();
eventDispatcher.enabled(codecCounters);
}
@Override
......@@ -360,6 +361,7 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
}
} finally {
super.onDisabled();
eventDispatcher.disabled();
}
}
......
......@@ -27,11 +27,11 @@ import android.os.SystemClock;
public interface AudioTrackRendererEventListener {
/**
* Invoked to pass the codec counters when the renderer is enabled.
* Invoked when the renderer is enabled.
*
* @param counters CodecCounters object used by the renderer.
* @param counters {@link CodecCounters} that will be updated by the renderer.
*/
void onAudioCodecCounters(CodecCounters counters);
void onAudioEnabled(CodecCounters counters);
/**
* Invoked when a decoder is created.
......@@ -63,6 +63,11 @@ public interface AudioTrackRendererEventListener {
void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
/**
* Invoked when the renderer is disabled.
*/
void onAudioDisabled();
/**
* Dispatches events to a {@link AudioTrackRendererEventListener}.
*/
final class EventDispatcher {
......@@ -75,12 +80,12 @@ public interface AudioTrackRendererEventListener {
this.listener = listener;
}
public void codecCounters(final CodecCounters codecCounters) {
public void enabled(final CodecCounters codecCounters) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onAudioCodecCounters(codecCounters);
listener.onAudioEnabled(codecCounters);
}
});
}
......@@ -122,6 +127,17 @@ public interface AudioTrackRendererEventListener {
}
}
public void disabled() {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onAudioDisabled();
}
});
}
}
}
}
......@@ -70,15 +70,17 @@ public final class CodecCounters {
// call this method.
}
public String getDebugString() {
ensureUpdated();
return "ic:" + codecInitCount
+ " rc:" + codecReleaseCount
+ " ib:" + inputBufferCount
+ " rb:" + renderedOutputBufferCount
+ " sb:" + skippedOutputBufferCount
+ " db:" + droppedOutputBufferCount
+ " mcdb:" + maxConsecutiveDroppedOutputBufferCount;
/**
* Resets all counters to zero.
*/
public void reset() {
codecInitCount = 0;
codecReleaseCount = 0;
inputBufferCount = 0;
renderedOutputBufferCount = 0;
skippedOutputBufferCount = 0;
droppedOutputBufferCount = 0;
maxConsecutiveDroppedOutputBufferCount = 0;
}
}
......@@ -26,11 +26,12 @@ import android.util.SparseBooleanArray;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* A {@link TrackSelector} suitable for a wide range of use cases.
*/
public class DefaultTrackSelector extends TrackSelector implements
public final class DefaultTrackSelector extends TrackSelector implements
TrackSelectionPolicy.InvalidationListener{
/**
......@@ -48,7 +49,7 @@ public class DefaultTrackSelector extends TrackSelector implements
}
private final Handler eventHandler;
private final EventListener eventListener;
private final CopyOnWriteArraySet<EventListener> listeners;
private final SparseArray<Map<TrackGroupArray, TrackSelection>> trackSelectionOverrides;
private final SparseBooleanArray rendererDisabledFlags;
private final TrackSelectionPolicy trackSelectionPolicy;
......@@ -56,19 +57,37 @@ public class DefaultTrackSelector extends TrackSelector implements
private TrackInfo activeTrackInfo;
/**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param trackSelectionPolicy Defines the policy for track selection.
* @param eventHandler A handler to use when delivering events to listeners added via
* {@link #addListener(EventListener)}.
*/
public DefaultTrackSelector(Handler eventHandler, EventListener eventListener,
TrackSelectionPolicy trackSelectionPolicy) {
public DefaultTrackSelector(TrackSelectionPolicy trackSelectionPolicy, Handler eventHandler) {
this.trackSelectionPolicy = Assertions.checkNotNull(trackSelectionPolicy);
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.listeners = new CopyOnWriteArraySet<>();
trackSelectionOverrides = new SparseArray<>();
rendererDisabledFlags = new SparseBooleanArray();
this.trackSelectionPolicy = Assertions.checkNotNull(trackSelectionPolicy);
this.trackSelectionPolicy.init(this);
trackSelectionPolicy.init(this);
}
/**
* Register a listener to receive events from the selector. The listener's methods will be invoked
* using the {@link Handler} that was passed to the constructor.
*
* @param listener The listener to register.
*/
public void addListener(EventListener listener) {
Assertions.checkState(eventHandler != null);
listeners.add(listener);
}
/**
* Unregister a listener. The listener will no longer receive events from the selector.
*
* @param listener The listener to unregister.
*/
public void removeListener(EventListener listener) {
listeners.remove(listener);
}
/**
......@@ -391,11 +410,13 @@ public class DefaultTrackSelector extends TrackSelector implements
}
private void notifyTrackInfoChanged(final TrackInfo trackInfo) {
if (eventHandler != null && eventListener != null) {
if (eventHandler != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onTracksChanged(trackInfo);
for (EventListener listener : listeners) {
listener.onTracksChanged(trackInfo);
}
}
});
}
......
......@@ -15,8 +15,6 @@
*/
package com.google.android.exoplayer;
import android.os.Looper;
/**
* An extensible media player exposing traditional high-level media player functionality, such as
* the ability to buffer media, play, pause and seek.
......@@ -62,8 +60,8 @@ import android.os.Looper;
* discouraged, however if an application does wish to do this then it may do so provided that it
* ensures accesses are synchronized.
* </li>
* <li>Registered {@link Listener}s are invoked on the thread that created the {@link ExoPlayer}
* instance.</li>
* <li>Registered {@link EventListener}s are invoked on the thread that created the
* {@link ExoPlayer} instance.</li>
* <li>An internal playback thread is responsible for managing playback and invoking the
* {@link TrackRenderer}s in order to load and play the media.</li>
* <li>{@link TrackRenderer} implementations (or any upstream components that they depend on) may
......@@ -94,62 +92,10 @@ import android.os.Looper;
public interface ExoPlayer {
/**
* A factory for instantiating ExoPlayer instances.
*/
final class Factory {
/**
* The default minimum duration of data that must be buffered for playback to start or resume
* following a user action such as a seek.
*/
public static final int DEFAULT_MIN_BUFFER_MS = 2500;
/**
* The default minimum duration of data that must be buffered for playback to resume
* after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
* not due to a user action such as starting playback or seeking).
*/
public static final int DEFAULT_MIN_REBUFFER_MS = 5000;
private Factory() {}
/**
* Obtains an {@link ExoPlayer} instance.
* <p>
* Must be invoked from a thread that has an associated {@link Looper}.
*
* @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param minBufferMs A minimum duration of data that must be buffered for playback to start
* or resume following a user action such as a seek.
* @param minRebufferMs A minimum duration of data that must be buffered for playback to resume
* after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
* not due to a user action such as starting playback or seeking).
*/
public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector,
int minBufferMs, int minRebufferMs) {
return new ExoPlayerImpl(renderers, trackSelector, minBufferMs, minRebufferMs);
}
/**
* Obtains an {@link ExoPlayer} instance.
* <p>
* Must be invoked from a thread that has an associated {@link Looper}.
*
* @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
*/
public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector) {
return new ExoPlayerImpl(renderers, trackSelector, DEFAULT_MIN_BUFFER_MS,
DEFAULT_MIN_REBUFFER_MS);
}
}
/**
* Interface definition for a callback to be notified of changes in player state.
*/
interface Listener {
interface EventListener {
/**
* Invoked when the value returned from either {@link ExoPlayer#getPlayWhenReady()} or
* {@link ExoPlayer#getPlaybackState()} changes.
......@@ -159,6 +105,7 @@ public interface ExoPlayer {
* interface.
*/
void onPlayerStateChanged(boolean playWhenReady, int playbackState);
/**
* Invoked when the current value of {@link ExoPlayer#getPlayWhenReady()} has been reflected
* by the internal playback thread.
......@@ -169,6 +116,7 @@ public interface ExoPlayer {
* has been reflected.
*/
void onPlayWhenReadyCommitted();
/**
* Invoked when an error occurs. The playback state will transition to
* {@link ExoPlayer#STATE_IDLE} immediately after this method is invoked. The player instance
......@@ -178,6 +126,7 @@ public interface ExoPlayer {
* @param error The error.
*/
void onPlayerError(ExoPlaybackException error);
}
/**
......@@ -230,14 +179,14 @@ public interface ExoPlayer {
*
* @param listener The listener to register.
*/
void addListener(Listener listener);
void addListener(EventListener listener);
/**
* Unregister a listener. The listener will no longer receive events from the player.
*
* @param listener The listener to unregister.
*/
void removeListener(Listener listener);
void removeListener(EventListener listener);
/**
* Returns the current state of the player.
......
/*
* Copyright (C) 2014 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.exoplayer;
import com.google.android.exoplayer.drm.DrmSessionManager;
import android.content.Context;
import android.os.Looper;
/**
* A factory for instantiating {@link ExoPlayer} instances.
*/
public final class ExoPlayerFactory {
/**
* The default minimum duration of data that must be buffered for playback to start or resume
* following a user action such as a seek.
*/
public static final int DEFAULT_MIN_BUFFER_MS = 2500;
/**
* The default minimum duration of data that must be buffered for playback to resume
* after a player-invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
* not due to a user action such as starting playback or seeking).
*/
public static final int DEFAULT_MIN_REBUFFER_MS = 5000;
private ExoPlayerFactory() {}
/**
* Obtains a {@link SimpleExoPlayer} instance.
* <p>
* Must be called from a thread that has an associated {@link Looper}.
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector) {
return newSimpleInstance(context, trackSelector, null, false);
}
/**
* Obtains a {@link SimpleExoPlayer} instance.
* <p>
* Must be called from a thread that has an associated {@link Looper}.
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param useExtensionDecoders True to include {@link TrackRenderer} instances defined in
* available extensions. Note that the required extensions must be included in the application
* build for setting this flag to have any effect.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
DrmSessionManager drmSessionManager, boolean useExtensionDecoders) {
return newSimpleInstance(context, trackSelector, drmSessionManager, useExtensionDecoders,
DEFAULT_MIN_BUFFER_MS, DEFAULT_MIN_REBUFFER_MS);
}
/**
* Obtains a {@link SimpleExoPlayer} instance.
* <p>
* Must be called from a thread that has an associated {@link Looper}.
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @param useExtensionDecoders True to include {@link TrackRenderer} instances defined in
* available extensions. Note that the required extensions must be included in the application
* build for setting this flag to have any effect.
* @param minBufferMs A minimum duration of data that must be buffered for playback to start
* or resume following a user action such as a seek.
* @param minRebufferMs A minimum duration of data that must be buffered for playback to resume
* after a player-invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
* not due to a user action such as starting playback or seeking).
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
DrmSessionManager drmSessionManager, boolean useExtensionDecoders, int minBufferMs,
int minRebufferMs) {
return new SimpleExoPlayer(context, trackSelector, drmSessionManager, useExtensionDecoders,
minBufferMs, minRebufferMs);
}
/**
* Obtains an {@link ExoPlayer} instance.
* <p>
* Must be called from a thread that has an associated {@link Looper}.
*
* @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param minBufferMs A minimum duration of data that must be buffered for playback to start
* or resume following a user action such as a seek.
* @param minRebufferMs A minimum duration of data that must be buffered for playback to resume
* after a player-invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
* not due to a user action such as starting playback or seeking).
*/
public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector,
int minBufferMs, int minRebufferMs) {
return new ExoPlayerImpl(renderers, trackSelector, minBufferMs, minRebufferMs);
}
/**
* Obtains an {@link ExoPlayer} instance.
* <p>
* Must be called from a thread that has an associated {@link Looper}.
*
* @param renderers The {@link TrackRenderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
*/
public static ExoPlayer newInstance(TrackRenderer[] renderers, TrackSelector trackSelector) {
return new ExoPlayerImpl(renderers, trackSelector, DEFAULT_MIN_BUFFER_MS,
DEFAULT_MIN_REBUFFER_MS);
}
}
......@@ -34,7 +34,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
private final Handler eventHandler;
private final ExoPlayerImplInternal internalPlayer;
private final CopyOnWriteArraySet<Listener> listeners;
private final CopyOnWriteArraySet<EventListener> listeners;
private boolean playWhenReady;
private int playbackState;
......@@ -71,12 +71,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
@Override
public void addListener(Listener listener) {
public void addListener(EventListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(Listener listener) {
public void removeListener(EventListener listener) {
listeners.remove(listener);
}
......@@ -96,7 +96,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
this.playWhenReady = playWhenReady;
pendingPlayWhenReadyAcks++;
internalPlayer.setPlayWhenReady(playWhenReady);
for (Listener listener : listeners) {
for (EventListener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
}
......@@ -166,7 +166,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
switch (msg.what) {
case ExoPlayerImplInternal.MSG_STATE_CHANGED: {
playbackState = msg.arg1;
for (Listener listener : listeners) {
for (EventListener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
break;
......@@ -174,7 +174,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
case ExoPlayerImplInternal.MSG_SET_PLAY_WHEN_READY_ACK: {
pendingPlayWhenReadyAcks--;
if (pendingPlayWhenReadyAcks == 0) {
for (Listener listener : listeners) {
for (EventListener listener : listeners) {
listener.onPlayWhenReadyCommitted();
}
}
......@@ -182,7 +182,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
case ExoPlayerImplInternal.MSG_ERROR: {
ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
for (Listener listener : listeners) {
for (EventListener listener : listeners) {
listener.onPlayerError(exception);
}
break;
......
......@@ -272,8 +272,9 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
@Override
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
codecCounters.reset();
eventDispatcher.enabled(codecCounters);
super.onEnabled(formats, joining);
eventDispatcher.codecCounters(codecCounters);
}
@Override
......@@ -290,6 +291,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
@Override
protected void onDisabled() {
eventDispatcher.disabled();
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
try {
audioTrack.release();
......
......@@ -210,6 +210,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
@Override
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
codecCounters.reset();
eventDispatcher.enabled(codecCounters);
super.onEnabled(formats, joining);
adaptiveMaxWidth = Format.NO_VALUE;
adaptiveMaxHeight = Format.NO_VALUE;
......@@ -228,7 +230,6 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
joiningDeadlineMs = SystemClock.elapsedRealtime() + allowedJoiningTimeMs;
}
frameReleaseTimeHelper.enable();
eventDispatcher.codecCounters(codecCounters);
}
@Override
......@@ -274,6 +275,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
@Override
protected void onDisabled() {
eventDispatcher.disabled();
currentWidth = -1;
currentHeight = -1;
currentPixelWidthHeightRatio = -1;
......
......@@ -28,11 +28,11 @@ import android.view.TextureView;
public interface VideoTrackRendererEventListener {
/**
* Invoked to pass the codec counters when the renderer is enabled.
* Invoked when the renderer is enabled.
*
* @param counters CodecCounters object used by the renderer.
* @param counters {@link CodecCounters} that will be updated by the renderer.
*/
void onVideoCodecCounters(CodecCounters counters);
void onVideoEnabled(CodecCounters counters);
/**
* Invoked when a decoder is created.
......@@ -93,6 +93,11 @@ public interface VideoTrackRendererEventListener {
void onDrawnToSurface(Surface surface);
/**
* Invoked when the renderer is disabled.
*/
void onVideoDisabled();
/**
* Dispatches events to a {@link VideoTrackRendererEventListener}.
*/
final class EventDispatcher {
......@@ -105,12 +110,12 @@ public interface VideoTrackRendererEventListener {
this.listener = listener;
}
public void codecCounters(final CodecCounters codecCounters) {
public void enabled(final CodecCounters codecCounters) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onVideoCodecCounters(codecCounters);
listener.onVideoEnabled(codecCounters);
}
});
}
......@@ -175,6 +180,17 @@ public interface VideoTrackRendererEventListener {
}
}
public void disabled() {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onVideoDisabled();
}
});
}
}
}
}
......@@ -306,7 +306,8 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
@Override
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
eventDispatcher.codecCounters(codecCounters);
codecCounters.reset();
eventDispatcher.enabled(codecCounters);
}
@Override
......@@ -321,6 +322,7 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
@Override
protected void onDisabled() {
eventDispatcher.disabled();
inputBuffer = null;
outputBuffer = null;
inputFormat = null;
......
......@@ -16,54 +16,33 @@
package com.google.android.exoplayer.util;
import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.SimpleExoPlayer;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import android.widget.TextView;
/**
* A helper class for periodically updating debug information displayed by a {@link TextView}.
* A helper class for periodically updating a {@link TextView} with debug information obtained from
* a {@link SimpleExoPlayer}.
*/
public final class DebugTextViewHelper implements Runnable {
/**
* Provides debug information about an ongoing playback.
*/
public interface Provider {
/**
* Returns the current playback position, in milliseconds.
*/
long getCurrentPosition();
/**
* Returns a format whose information should be displayed, or null.
*/
Format getFormat();
/**
* Returns a {@link BandwidthMeter} whose estimate should be displayed, or null.
*/
BandwidthMeter getBandwidthMeter();
/**
* Returns a {@link CodecCounters} whose information should be displayed, or null.
*/
CodecCounters getCodecCounters();
}
public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListener {
private static final int REFRESH_INTERVAL_MS = 1000;
private final SimpleExoPlayer player;
private final TextView textView;
private final Provider debuggable;
private boolean started;
/**
* @param debuggable The {@link Provider} from which debug information should be obtained.
* @param player The {@link SimpleExoPlayer} from which debug information should be obtained.
* @param textView The {@link TextView} that should be updated to display the information.
*/
public DebugTextViewHelper(Provider debuggable, TextView textView) {
this.debuggable = debuggable;
public DebugTextViewHelper(SimpleExoPlayer player, TextView textView) {
this.player = player;
this.textView = textView;
}
......@@ -73,7 +52,11 @@ public final class DebugTextViewHelper implements Runnable {
* Should be called from the application's main thread.
*/
public void start() {
stop();
if (started) {
return;
}
started = true;
player.addListener(this);
run();
}
......@@ -83,43 +66,101 @@ public final class DebugTextViewHelper implements Runnable {
* Should be called from the application's main thread.
*/
public void stop() {
if (!started) {
return;
}
started = false;
player.removeListener(this);
textView.removeCallbacks(this);
}
@Override
public void run() {
textView.setText(getRenderString());
updateTextView();
textView.postDelayed(this, REFRESH_INTERVAL_MS);
}
private String getRenderString() {
return getTimeString() + " " + getQualityString() + " " + getBandwidthString() + " "
+ getVideoCodecCountersString();
private void updateTextView() {
textView.setText(getPlayerStateString() + getBandwidthString() + getVideoString()
+ getAudioString());
}
private String getTimeString() {
return "ms(" + debuggable.getCurrentPosition() + ")";
}
private String getQualityString() {
Format format = debuggable.getFormat();
return format == null ? "id:? br:? h:?"
: "id:" + format.id + " br:" + format.bitrate + " h:" + format.height;
public String getPlayerStateString() {
String text = "playWhenReady:" + player.getPlayWhenReady() + " playbackState:";
switch(player.getPlaybackState()) {
case ExoPlayer.STATE_BUFFERING:
text += "buffering";
break;
case ExoPlayer.STATE_ENDED:
text += "ended";
break;
case ExoPlayer.STATE_IDLE:
text += "idle";
break;
case ExoPlayer.STATE_READY:
text += "ready";
break;
default:
text += "unknown";
break;
}
return text;
}
private String getBandwidthString() {
BandwidthMeter bandwidthMeter = debuggable.getBandwidthMeter();
BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
if (bandwidthMeter == null
|| bandwidthMeter.getBitrateEstimate() == BandwidthMeter.NO_ESTIMATE) {
return "bw:?";
return " bw:?";
} else {
return "bw:" + (bandwidthMeter.getBitrateEstimate() / 1000);
return " bw:" + (bandwidthMeter.getBitrateEstimate() / 1000);
}
}
private String getVideoString() {
Format format = player.getVideoFormat();
if (format == null) {
return "";
}
return "\n" + format.sampleMimeType + "(r:" + format.width + "x" + format.height
+ getCodecCounterBufferCountString(player.getVideoCodecCounters()) + ")";
}
private String getAudioString() {
Format format = player.getAudioFormat();
if (format == null) {
return "";
}
return "\n" + format.sampleMimeType + "(hz:" + format.sampleRate + " ch:" + format.channelCount
+ getCodecCounterBufferCountString(player.getAudioCodecCounters()) + ")";
}
private static String getCodecCounterBufferCountString(CodecCounters counters) {
if (counters == null) {
return "";
}
counters.ensureUpdated();
return " rb:" + counters.renderedOutputBufferCount
+ " sb:" + counters.skippedOutputBufferCount
+ " db:" + counters.droppedOutputBufferCount
+ " mcdb:" + counters.maxConsecutiveDroppedOutputBufferCount;
}
private String getVideoCodecCountersString() {
CodecCounters codecCounters = debuggable.getCodecCounters();
return codecCounters == null ? "" : codecCounters.getDebugString();
// ExoPlayer.EventListener implementation
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
updateTextView();
}
@Override
public void onPlayWhenReadyCommitted() {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException error) {
// Do nothing.
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment