diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java index 146a372e8d..233ac6460b 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java @@ -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, diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index 82be26094a..d99f15f93c 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -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 cues) { subtitleLayout.setCues(cues); } - // DemoPlayer.MetadataListener implementation + // SimpleExoPlayer.MetadataListener implementation @Override public void onId3Metadata(List id3Frames) { diff --git a/demo/src/main/res/layout/player_activity.xml b/demo/src/main/res/layout/player_activity.xml index 1c8e8ba01b..e9dd9ccffd 100644 --- a/demo/src/main/res/layout/player_activity.xml +++ b/demo/src/main/res/layout/player_activity.xml @@ -48,14 +48,6 @@ android:background="#88000000" android:orientation="vertical"> - - listeners; private final SparseArray> 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); + } } }); } diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java index 5a512d8880..b9a1f3eddc 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java @@ -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. * - *
  • Registered {@link Listener}s are invoked on the thread that created the {@link ExoPlayer} - * instance.
  • + *
  • Registered {@link EventListener}s are invoked on the thread that created the + * {@link ExoPlayer} instance.
  • *
  • An internal playback thread is responsible for managing playback and invoking the * {@link TrackRenderer}s in order to load and play the media.
  • *
  • {@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. - *

    - * 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. - *

    - * 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. diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerFactory.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerFactory.java new file mode 100644 index 0000000000..b549c822b3 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerFactory.java @@ -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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java index bd95cff11f..f4c0013f1b 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java @@ -34,7 +34,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private final Handler eventHandler; private final ExoPlayerImplInternal internalPlayer; - private final CopyOnWriteArraySet listeners; + private final CopyOnWriteArraySet 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; diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java index 449969e185..224cb9546a 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java @@ -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(); diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java index 9ec45bab36..e02a8dc722 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java @@ -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; diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java similarity index 50% rename from demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java rename to library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java index 73d4396041..ec7424c4d2 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java @@ -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. + *

    + * Instances of this class can be obtained from {@link ExoPlayerFactory}. */ -public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.EventListener, - VideoTrackRendererEventListener, AudioTrackRendererEventListener, TextRenderer, - MetadataRenderer>, 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 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 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 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 cues) { - if (captionListener != null) { - captionListener.onCues(cues); - } - } - - @Override - public void onMetadata(List 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 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> 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> { + + // 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 cues) { + if (captionListener != null) { + captionListener.onCues(cues); + } + } + + // MetadataRenderer implementation + + @Override + public void onMetadata(List id3Frames) { + if (id3MetadataListener != null) { + id3MetadataListener.onId3Metadata(id3Frames); + } + } + + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/VideoTrackRendererEventListener.java b/library/src/main/java/com/google/android/exoplayer/VideoTrackRendererEventListener.java index 8ec743c51f..0d92d03568 100644 --- a/library/src/main/java/com/google/android/exoplayer/VideoTrackRendererEventListener.java +++ b/library/src/main/java/com/google/android/exoplayer/VideoTrackRendererEventListener.java @@ -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(); + } + }); + } + } + } } diff --git a/library/src/main/java/com/google/android/exoplayer/extensions/AudioDecoderTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/extensions/AudioDecoderTrackRenderer.java index f8cd7d26d9..6c3b8dbd54 100644 --- a/library/src/main/java/com/google/android/exoplayer/extensions/AudioDecoderTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/extensions/AudioDecoderTrackRenderer.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java b/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java index bcf7fb33a4..09a41b1104 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java +++ b/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java @@ -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 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. } }