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
This commit is contained in:
olly 2016-05-24 03:38:51 -07:00 committed by Oliver Woodman
parent 075e095cba
commit ced7de15a9
19 changed files with 710 additions and 439 deletions

View File

@ -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,

View File

@ -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
// ExoPlayer.EventListener implementation
@Override
public void onTracksChanged(TrackInfo trackSet) {
updateButtonVisibilities();
}
@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) {

View File

@ -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"

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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();
}
}

View File

@ -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.
@ -62,6 +62,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}.
*/
@ -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();
}
});
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
});
}

View File

@ -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
@ -93,63 +91,11 @@ 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.

View File

@ -0,0 +1,130 @@
/*
* 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);
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -13,23 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.demo.player;
package com.google.android.exoplayer;
import com.google.android.exoplayer.AudioTrackRendererEventListener;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.CodecCounters;
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.Format;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecSelector;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.VideoTrackRendererEventListener;
import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
@ -41,8 +26,6 @@ import com.google.android.exoplayer.text.TextRenderer;
import com.google.android.exoplayer.text.TextTrackRenderer;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.util.DebugTextViewHelper;
import com.google.android.exoplayer.util.PlayerControl;
import android.content.Context;
import android.media.AudioManager;
@ -54,30 +37,27 @@ import android.view.Surface;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* A wrapper around {@link ExoPlayer} that provides a higher level interface.
* An {@link ExoPlayer} that uses default {@link TrackRenderer} components.
* <p>
* Instances of this class can be obtained from {@link ExoPlayerFactory}.
*/
public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.EventListener,
VideoTrackRendererEventListener, AudioTrackRendererEventListener, TextRenderer,
MetadataRenderer<List<Id3Frame>>, DebugTextViewHelper.Provider {
public final class SimpleExoPlayer implements ExoPlayer {
/**
* A listener for core events.
* A listener for video rendering information.
*/
public interface Listener {
void onStateChanged(boolean playWhenReady, int playbackState);
void onError(ExoPlaybackException e);
void onTracksChanged(TrackInfo trackInfo);
public interface VideoListener {
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio);
void onDrawnToSurface(Surface surface);
}
/**
* A listener for debugging information.
*/
public interface InfoListener {
public interface DebugListener {
void onAudioDecoderInitialized(String decoderName, long elapsedRealtimeMs,
long initializationDurationMs);
void onVideoDecoderInitialized(String decoderName, long elapsedRealtimeMs,
@ -100,30 +80,31 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
void onId3Metadata(List<Id3Frame> id3Frames);
}
private static final String TAG = "DemoPlayer";
private static final String TAG = "SimpleExoPlayer";
private final ExoPlayer player;
private final DefaultTrackSelector trackSelector;
private final BandwidthMeter bandwidthMeter;
private final TrackRenderer[] renderers;
private final PlayerControl playerControl;
private final ComponentListener componentListener;
private final Handler mainHandler;
private final CopyOnWriteArrayList<Listener> listeners;
private Surface surface;
private Format videoFormat;
private TrackInfo trackInfo;
private Format audioFormat;
private CaptionListener captionListener;
private Id3MetadataListener id3MetadataListener;
private InfoListener infoListener;
private VideoListener videoListener;
private DebugListener debugListener;
private CodecCounters videoCodecCounters;
private CodecCounters audioCodecCounters;
public DemoPlayer(Context context, DrmSessionManager drmSessionManager,
boolean useExtensionDecoders) {
/* package */ SimpleExoPlayer(Context context, TrackSelector trackSelector,
DrmSessionManager drmSessionManager, boolean useExtensionDecoders, int minBufferMs,
int minRebufferMs) {
mainHandler = new Handler();
bandwidthMeter = new DefaultBandwidthMeter();
listeners = new CopyOnWriteArrayList<>();
componentListener = new ComponentListener();
// Build the renderers.
ArrayList<TrackRenderer> renderersList = new ArrayList<>();
@ -134,88 +115,186 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
renderers = renderersList.toArray(new TrackRenderer[renderersList.size()]);
// Build the player and associated objects.
trackSelector = new DefaultTrackSelector(mainHandler, this, new DefaultTrackSelectionPolicy());
player = ExoPlayer.Factory.newInstance(renderers, trackSelector, 1000, 5000);
player.addListener(this);
playerControl = new PlayerControl(player);
player = new ExoPlayerImpl(renderers, trackSelector, minBufferMs, minRebufferMs);
}
public DefaultTrackSelector getTrackSelector() {
return trackSelector;
/**
* Returns the number of renderers.
*
* @return The number of renderers.
*/
public int getRendererCount() {
return renderers.length;
}
public PlayerControl getPlayerControl() {
return playerControl;
}
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
public void setInfoListener(InfoListener listener) {
infoListener = listener;
}
public void setCaptionListener(CaptionListener listener) {
captionListener = listener;
}
public void setMetadataListener(Id3MetadataListener listener) {
id3MetadataListener = listener;
/**
* Returns the track type that the renderer at a given index handles.
*
* @see {@link TrackRenderer#getTrackType()}.
* @param index The index of the renderer.
* @return One of the TRACK_TYPE_* constants defined in {@link C}.
*/
public int getRendererType(int index) {
return renderers[index].getTrackType();
}
/**
* Sets the {@link Surface} onto which video will be rendered.
*
* @param surface The {@link Surface}.
*/
public void setSurface(Surface surface) {
this.surface = surface;
pushSurface(false);
}
/**
* Clears the {@link Surface} onto which video will be rendered.
*/
public void blockingClearSurface() {
surface = null;
pushSurface(true);
}
public TrackInfo getTrackInfo() {
return trackInfo;
/**
* @return The {@link BandwidthMeter} being used by the player.
*/
public BandwidthMeter getBandwidthMeter() {
return bandwidthMeter;
}
public void setSource(SampleSource source) {
player.setSource(source);
/**
* @return The video format currently being played, or null if there is no video component to the
* current media.
*/
public Format getVideoFormat() {
return videoFormat;
}
public void setPlayWhenReady(boolean playWhenReady) {
player.setPlayWhenReady(playWhenReady);
/**
* @return The audio format currently being played, or null if there is no audio component to the
* current media.
*/
public Format getAudioFormat() {
return audioFormat;
}
public void seekTo(long positionMs) {
player.seekTo(positionMs);
/**
* @return The {@link CodecCounters} for video, or null if there is no video component to the
* current media.
*/
public CodecCounters getVideoCodecCounters() {
return videoCodecCounters;
}
public void release() {
surface = null;
player.release();
/**
* @return The {@link CodecCounters} for audio, or null if there is no audio component to the
* current media.
*/
public CodecCounters getAudioCodecCounters() {
return audioCodecCounters;
}
/**
* Sets a listener to receive video events.
*
* @param listener The listener.
*/
public void setVideoListener(VideoListener listener) {
videoListener = listener;
}
/**
* Sets a listener to receive debug events.
*
* @param listener The listener.
*/
public void setDebugListener(DebugListener listener) {
debugListener = listener;
}
/**
* Sets a listener to receive caption events.
*
* @param listener The listener.
*/
public void setCaptionListener(CaptionListener listener) {
captionListener = listener;
}
/**
* Sets a listener to receive metadata events.
*
* @param listener The listener.
*/
public void setMetadataListener(Id3MetadataListener listener) {
id3MetadataListener = listener;
}
// ExoPlayer implementation
public void addListener(EventListener listener) {
player.addListener(listener);
}
public void removeListener(EventListener listener) {
player.removeListener(listener);
}
@Override
public int getPlaybackState() {
return player.getPlaybackState();
}
@Override
public Format getFormat() {
return videoFormat;
public void setSource(SampleSource source) {
player.setSource(source);
}
@Override
public BandwidthMeter getBandwidthMeter() {
return bandwidthMeter;
public void setPlayWhenReady(boolean playWhenReady) {
player.setPlayWhenReady(playWhenReady);
}
@Override
public CodecCounters getCodecCounters() {
return videoCodecCounters;
public boolean getPlayWhenReady() {
return player.getPlayWhenReady();
}
@Override
public boolean isPlayWhenReadyCommitted() {
return player.isPlayWhenReadyCommitted();
}
@Override
public void seekTo(long positionMs) {
player.seekTo(positionMs);
}
@Override
public void stop() {
player.stop();
}
@Override
public void release() {
surface = null;
player.release();
}
@Override
public void sendMessage(ExoPlayerComponent target, int messageType, Object message) {
player.sendMessage(target, messageType, message);
}
@Override
public void blockingSendMessage(ExoPlayerComponent target, int messageType, Object message) {
player.blockingSendMessage(target, messageType, message);
}
@Override
public long getDuration() {
return player.getDuration();
}
@Override
@ -223,127 +302,17 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
return player.getCurrentPosition();
}
public long getDuration() {
return player.getDuration();
@Override
public long getBufferedPosition() {
return player.getBufferedPosition();
}
@Override
public int getBufferedPercentage() {
return player.getBufferedPercentage();
}
public boolean getPlayWhenReady() {
return player.getPlayWhenReady();
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int state) {
for (Listener listener : listeners) {
listener.onStateChanged(playWhenReady, state);
}
}
@Override
public void onPlayerError(ExoPlaybackException exception) {
for (Listener listener : listeners) {
listener.onError(exception);
}
}
@Override
public void onTracksChanged(TrackInfo trackInfo) {
this.trackInfo = trackInfo;
for (Listener listener : listeners) {
listener.onTracksChanged(trackInfo);
}
}
@Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) {
for (Listener listener : listeners) {
listener.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
}
}
@Override
public void onDroppedFrames(int count, long elapsed) {
if (infoListener != null) {
infoListener.onDroppedFrames(count, elapsed);
}
}
@Override
public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
if (infoListener != null) {
infoListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
}
@Override
public void onAudioCodecCounters(CodecCounters counters) {
// Do nothing.
}
@Override
public void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs,
long initializationDurationMs) {
if (infoListener != null) {
infoListener.onAudioDecoderInitialized(decoderName, initializedTimestampMs,
initializationDurationMs);
}
}
@Override
public void onAudioInputFormatChanged(Format format) {
// Do nothing.
}
@Override
public void onCues(List<Cue> cues) {
if (captionListener != null) {
captionListener.onCues(cues);
}
}
@Override
public void onMetadata(List<Id3Frame> id3Frames) {
if (id3MetadataListener != null) {
id3MetadataListener.onId3Metadata(id3Frames);
}
}
@Override
public void onPlayWhenReadyCommitted() {
// Do nothing.
}
@Override
public void onDrawnToSurface(Surface surface) {
// Do nothing.
}
@Override
public void onVideoCodecCounters(CodecCounters counters) {
this.videoCodecCounters = counters;
}
@Override
public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs,
long initializationDurationMs) {
if (infoListener != null) {
infoListener.onVideoDecoderInitialized(decoderName, initializedTimestampMs,
initializationDurationMs);
}
}
@Override
public void onVideoInputFormatChanged(Format format) {
videoFormat = format;
}
public int getRendererType(int index) {
return renderers[index].getTrackType();
}
// Internal methods.
private void pushSurface(boolean blockForSurfacePush) {
for (TrackRenderer renderer : renderers) {
@ -361,19 +330,19 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
ArrayList<TrackRenderer> renderersList) {
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000,
drmSessionManager, false, mainHandler, this, 50);
drmSessionManager, false, mainHandler, componentListener, 50);
renderersList.add(videoRenderer);
TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(MediaCodecSelector.DEFAULT,
drmSessionManager, true, mainHandler, this, AudioCapabilities.getCapabilities(context),
AudioManager.STREAM_MUSIC);
drmSessionManager, true, mainHandler, componentListener,
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
renderersList.add(audioRenderer);
TrackRenderer textRenderer = new TextTrackRenderer(this, mainHandler.getLooper());
TrackRenderer textRenderer = new TextTrackRenderer(componentListener, mainHandler.getLooper());
renderersList.add(textRenderer);
MetadataTrackRenderer<List<Id3Frame>> id3Renderer = new MetadataTrackRenderer<>(new Id3Parser(),
this, mainHandler.getLooper());
componentListener, mainHandler.getLooper());
renderersList.add(id3Renderer);
}
@ -386,7 +355,8 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
Class.forName("com.google.android.exoplayer.ext.vp9.LibvpxVideoTrackRenderer");
Constructor<?> constructor = clazz.getConstructor(boolean.class, Handler.class,
VideoTrackRendererEventListener.class, int.class);
renderersList.add((TrackRenderer) constructor.newInstance(true, mainHandler, this, 50));
renderersList.add((TrackRenderer) constructor.newInstance(true, mainHandler,
componentListener, 50));
Log.i(TAG, "Loaded LibvpxVideoTrackRenderer.");
} catch (Exception e) {
// Expected if the app was built without the extension.
@ -395,7 +365,9 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
try {
Class<?> clazz =
Class.forName("com.google.android.exoplayer.ext.opus.LibopusAudioTrackRenderer");
renderersList.add((TrackRenderer) clazz.newInstance());
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioTrackRendererEventListener.class);
renderersList.add((TrackRenderer) constructor.newInstance(mainHandler, componentListener));
Log.i(TAG, "Loaded LibopusAudioTrackRenderer.");
} catch (Exception e) {
// Expected if the app was built without the extension.
@ -404,7 +376,9 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
try {
Class<?> clazz =
Class.forName("com.google.android.exoplayer.ext.flac.LibflacAudioTrackRenderer");
renderersList.add((TrackRenderer) clazz.newInstance());
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioTrackRendererEventListener.class);
renderersList.add((TrackRenderer) constructor.newInstance(mainHandler, componentListener));
Log.i(TAG, "Loaded LibflacAudioTrackRenderer.");
} catch (Exception e) {
// Expected if the app was built without the extension.
@ -413,11 +387,121 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
try {
Class<?> clazz =
Class.forName("com.google.android.exoplayer.ext.ffmpeg.FfmpegAudioTrackRenderer");
renderersList.add((TrackRenderer) clazz.newInstance());
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioTrackRendererEventListener.class);
renderersList.add((TrackRenderer) constructor.newInstance(mainHandler, componentListener));
Log.i(TAG, "Loaded FfmpegAudioTrackRenderer.");
} catch (Exception e) {
// Expected if the app was built without the extension.
}
}
private final class ComponentListener implements VideoTrackRendererEventListener,
AudioTrackRendererEventListener, TextRenderer, MetadataRenderer<List<Id3Frame>> {
// VideoTrackRendererEventListener implementation
@Override
public void onVideoEnabled(CodecCounters counters) {
videoCodecCounters = counters;
}
@Override
public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs,
long initializationDurationMs) {
if (debugListener != null) {
debugListener.onVideoDecoderInitialized(decoderName, initializedTimestampMs,
initializationDurationMs);
}
}
@Override
public void onVideoInputFormatChanged(Format format) {
videoFormat = format;
}
@Override
public void onDroppedFrames(int count, long elapsed) {
if (debugListener != null) {
debugListener.onDroppedFrames(count, elapsed);
}
}
@Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) {
if (videoListener != null) {
videoListener.onVideoSizeChanged(width, height, unappliedRotationDegrees,
pixelWidthHeightRatio);
}
}
@Override
public void onDrawnToSurface(Surface surface) {
if (videoListener != null) {
videoListener.onDrawnToSurface(surface);
}
}
@Override
public void onVideoDisabled() {
videoFormat = null;
videoCodecCounters = null;
}
// AudioTrackRendererEventListener implementation
@Override
public void onAudioEnabled(CodecCounters counters) {
audioCodecCounters = counters;
}
@Override
public void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs,
long initializationDurationMs) {
if (debugListener != null) {
debugListener.onAudioDecoderInitialized(decoderName, initializedTimestampMs,
initializationDurationMs);
}
}
@Override
public void onAudioInputFormatChanged(Format format) {
audioFormat = format;
}
@Override
public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs,
long elapsedSinceLastFeedMs) {
if (debugListener != null) {
debugListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
}
@Override
public void onAudioDisabled() {
audioFormat = null;
audioCodecCounters = null;
}
// TextRenderer implementation
@Override
public void onCues(List<Cue> cues) {
if (captionListener != null) {
captionListener.onCues(cues);
}
}
// MetadataRenderer implementation
@Override
public void onMetadata(List<Id3Frame> id3Frames) {
if (id3MetadataListener != null) {
id3MetadataListener.onId3Metadata(id3Frames);
}
}
}
}

View File

@ -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.
@ -92,6 +92,11 @@ public interface VideoTrackRendererEventListener {
*/
void onDrawnToSurface(Surface surface);
/**
* Invoked when the renderer is disabled.
*/
void onVideoDisabled();
/**
* Dispatches events to a {@link VideoTrackRendererEventListener}.
*/
@ -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();
}
});
}
}
}
}

View File

@ -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;

View File

@ -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,32 +66,49 @@ 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() + ")";
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;
}
private String getQualityString() {
Format format = debuggable.getFormat();
return format == null ? "id:? br:? h:?"
: "id:" + format.id + " br:" + format.bitrate + " h:" + format.height;
return text;
}
private String getBandwidthString() {
BandwidthMeter bandwidthMeter = debuggable.getBandwidthMeter();
BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
if (bandwidthMeter == null
|| bandwidthMeter.getBitrateEstimate() == BandwidthMeter.NO_ESTIMATE) {
return " bw:?";
@ -117,9 +117,50 @@ public final class DebugTextViewHelper implements Runnable {
}
}
private String getVideoCodecCountersString() {
CodecCounters codecCounters = debuggable.getCodecCounters();
return codecCounters == null ? "" : codecCounters.getDebugString();
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;
}
// 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.
}
}