Clean up renderer event listeners.

- Don't report errors to listeners if playback will immediately
  fail. Doing so is redundant, since such errors are immediately
  reported through ExoPlayer's listener as a result of playback
  failure. We were also reporting these errors inconsistently
  across renderers.
- Reduce code duplication through EventDispatcher classes.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=122519976
This commit is contained in:
olly 2016-05-17 06:49:22 -07:00 committed by Oliver Woodman
parent bfee449ed8
commit a16a333df2
11 changed files with 318 additions and 516 deletions

View File

@ -22,10 +22,8 @@ import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.TrackSelection; import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.demo.player.DemoPlayer; import com.google.android.exoplayer.demo.player.DemoPlayer;
import android.media.MediaCodec.CryptoException;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
@ -128,6 +126,28 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
// DemoPlayer.InfoListener // DemoPlayer.InfoListener
@Override
public void onAudioDecoderInitialized(String decoderName, long elapsedRealtimeMs,
long initializationDurationMs) {
Log.d(TAG, "audioDecoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
}
@Override
public void onVideoDecoderInitialized(String decoderName, long elapsedRealtimeMs,
long initializationDurationMs) {
Log.d(TAG, "videoDecoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
}
@Override
public void onAudioFormatEnabled(Format format, int trigger, long mediaTimeMs) {
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + format.id + ", " + trigger + "]");
}
@Override
public void onVideoFormatEnabled(Format format, int trigger, long mediaTimeMs) {
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + format.id + ", " + trigger + "]");
}
@Override @Override
public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) { public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) {
Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes + ", " Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes + ", "
@ -151,18 +171,6 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
// Do nothing. // Do nothing.
} }
@Override
public void onVideoFormatEnabled(Format format, int trigger, long mediaTimeMs) {
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + format.id + ", "
+ Integer.toString(trigger) + "]");
}
@Override
public void onAudioFormatEnabled(Format format, int trigger, long mediaTimeMs) {
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + format.id + ", "
+ Integer.toString(trigger) + "]");
}
// DemoPlayer.InternalErrorListener // DemoPlayer.InternalErrorListener
@Override @Override
@ -170,48 +178,17 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
printInternalError("loadError", e); printInternalError("loadError", e);
} }
@Override
public void onRendererInitializationError(Exception e) {
printInternalError("rendererInitError", e);
}
@Override @Override
public void onDrmSessionManagerError(Exception e) { public void onDrmSessionManagerError(Exception e) {
printInternalError("drmSessionManagerError", e); printInternalError("drmSessionManagerError", e);
} }
@Override
public void onDecoderInitializationError(Exception e) {
printInternalError("decoderInitializationError", e);
}
@Override
public void onAudioTrackInitializationError(AudioTrack.InitializationException e) {
printInternalError("audioTrackInitializationError", e);
}
@Override
public void onAudioTrackWriteError(AudioTrack.WriteException e) {
printInternalError("audioTrackWriteError", e);
}
@Override @Override
public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
printInternalError("audioTrackUnderrun [" + bufferSize + ", " + bufferSizeMs + ", " printInternalError("audioTrackUnderrun [" + bufferSize + ", " + bufferSizeMs + ", "
+ elapsedSinceLastFeedMs + "]", null); + elapsedSinceLastFeedMs + "]", null);
} }
@Override
public void onCryptoError(CryptoException e) {
printInternalError("cryptoError", e);
}
@Override
public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
long initializationDurationMs) {
Log.d(TAG, "decoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
}
private void printInternalError(String type, Exception e) { private void printInternalError(String type, Exception e) {
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e); Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.demo.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.AudioTrackRendererEventListener;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.CodecCounters; import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.DefaultTrackSelectionPolicy; import com.google.android.exoplayer.DefaultTrackSelectionPolicy;
@ -31,7 +32,6 @@ import com.google.android.exoplayer.SingleSampleSource;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.VideoTrackRendererEventListener; import com.google.android.exoplayer.VideoTrackRendererEventListener;
import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener; import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager; import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.extractor.ExtractorSampleSource;
@ -50,7 +50,6 @@ import com.google.android.exoplayer.util.PlayerControl;
import android.content.Context; import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodec.CryptoException;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.Surface; import android.view.Surface;
@ -67,7 +66,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.EventListener, public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.EventListener,
ChunkTrackStreamEventListener, ExtractorSampleSource.EventListener, ChunkTrackStreamEventListener, ExtractorSampleSource.EventListener,
SingleSampleSource.EventListener, DefaultBandwidthMeter.EventListener, SingleSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener, VideoTrackRendererEventListener, AudioTrackRendererEventListener,
StreamingDrmSessionManager.EventListener, TextRenderer, MetadataRenderer<List<Id3Frame>>, StreamingDrmSessionManager.EventListener, TextRenderer, MetadataRenderer<List<Id3Frame>>,
DebugTextViewHelper.Provider { DebugTextViewHelper.Provider {
@ -91,12 +90,7 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
* will be invoked. * will be invoked.
*/ */
public interface InternalErrorListener { public interface InternalErrorListener {
void onRendererInitializationError(Exception e);
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
void onAudioTrackWriteError(AudioTrack.WriteException e);
void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
void onDecoderInitializationError(Exception e);
void onCryptoError(CryptoException e);
void onLoadError(int sourceId, IOException e); void onLoadError(int sourceId, IOException e);
void onDrmSessionManagerError(Exception e); void onDrmSessionManagerError(Exception e);
} }
@ -105,16 +99,18 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
* A listener for debugging information. * A listener for debugging information.
*/ */
public interface InfoListener { public interface InfoListener {
void onVideoFormatEnabled(Format format, int trigger, long mediaTimeMs); void onAudioDecoderInitialized(String decoderName, long elapsedRealtimeMs,
long initializationDurationMs);
void onVideoDecoderInitialized(String decoderName, long elapsedRealtimeMs,
long initializationDurationMs);
void onAudioFormatEnabled(Format format, int trigger, long mediaTimeMs); void onAudioFormatEnabled(Format format, int trigger, long mediaTimeMs);
void onVideoFormatEnabled(Format format, int trigger, long mediaTimeMs);
void onDroppedFrames(int count, long elapsed); void onDroppedFrames(int count, long elapsed);
void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate); void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate);
void onLoadStarted(int sourceId, long length, int type, int trigger, Format format, void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
long mediaStartTimeMs, long mediaEndTimeMs); long mediaStartTimeMs, long mediaEndTimeMs);
void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format, void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs); long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs);
void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
long initializationDurationMs);
} }
/** /**
@ -354,27 +350,6 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
} }
} }
@Override
public void onDecoderInitializationError(Exception e) {
if (internalErrorListener != null) {
internalErrorListener.onDecoderInitializationError(e);
}
}
@Override
public void onAudioTrackInitializationError(AudioTrack.InitializationException e) {
if (internalErrorListener != null) {
internalErrorListener.onAudioTrackInitializationError(e);
}
}
@Override
public void onAudioTrackWriteError(AudioTrack.WriteException e) {
if (internalErrorListener != null) {
internalErrorListener.onAudioTrackWriteError(e);
}
}
@Override @Override
public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
if (internalErrorListener != null) { if (internalErrorListener != null) {
@ -388,17 +363,11 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
} }
@Override @Override
public void onCryptoError(CryptoException e) { public void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs,
if (internalErrorListener != null) {
internalErrorListener.onCryptoError(e);
}
}
@Override
public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
long initializationDurationMs) { long initializationDurationMs) {
if (infoListener != null) { if (infoListener != null) {
infoListener.onDecoderInitialized(decoderName, elapsedRealtimeMs, initializationDurationMs); infoListener.onAudioDecoderInitialized(decoderName, initializedTimestampMs,
initializationDurationMs);
} }
} }
@ -438,6 +407,15 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
this.videoCodecCounters = counters; this.videoCodecCounters = counters;
} }
@Override
public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs,
long initializationDurationMs) {
if (infoListener != null) {
infoListener.onVideoDecoderInitialized(decoderName, initializedTimestampMs,
initializationDurationMs);
}
}
@Override @Override
public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format, public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
long mediaStartTimeMs, long mediaEndTimeMs) { long mediaStartTimeMs, long mediaEndTimeMs) {

View File

@ -25,6 +25,7 @@ import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.VideoTrackRendererEventListener; import com.google.android.exoplayer.VideoTrackRendererEventListener;
import com.google.android.exoplayer.VideoTrackRendererEventListener.EventDispatcher;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -56,8 +57,7 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
public final CodecCounters codecCounters = new CodecCounters(); public final CodecCounters codecCounters = new CodecCounters();
private final boolean scaleToFit; private final boolean scaleToFit;
private final Handler eventHandler; private final EventDispatcher eventDispatcher;
private final VideoTrackRendererEventListener eventListener;
private final int maxDroppedFrameCountToNotify; private final int maxDroppedFrameCountToNotify;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
@ -103,12 +103,11 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
public LibvpxVideoTrackRenderer(boolean scaleToFit, Handler eventHandler, public LibvpxVideoTrackRenderer(boolean scaleToFit, Handler eventHandler,
VideoTrackRendererEventListener eventListener, int maxDroppedFrameCountToNotify) { VideoTrackRendererEventListener eventListener, int maxDroppedFrameCountToNotify) {
this.scaleToFit = scaleToFit; this.scaleToFit = scaleToFit;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify; this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify;
previousWidth = -1; previousWidth = -1;
previousHeight = -1; previousHeight = -1;
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
outputMode = VpxDecoder.OUTPUT_MODE_NONE; outputMode = VpxDecoder.OUTPUT_MODE_NONE;
} }
@ -156,7 +155,7 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE); decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE);
decoder.setOutputMode(outputMode); decoder.setOutputMode(outputMode);
long codecInitializedTimestamp = SystemClock.elapsedRealtime(); long codecInitializedTimestamp = SystemClock.elapsedRealtime();
notifyDecoderInitialized(decoder.getName(), codecInitializedTimestamp, eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp); codecInitializedTimestamp - codecInitializingTimestamp);
codecCounters.codecInitCount++; codecCounters.codecInitCount++;
} }
@ -208,7 +207,7 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
codecCounters.maxConsecutiveDroppedOutputBufferCount = Math.max(consecutiveDroppedFrameCount, codecCounters.maxConsecutiveDroppedOutputBufferCount = Math.max(consecutiveDroppedFrameCount,
codecCounters.maxConsecutiveDroppedOutputBufferCount); codecCounters.maxConsecutiveDroppedOutputBufferCount);
if (droppedFrameCount == maxDroppedFrameCountToNotify) { if (droppedFrameCount == maxDroppedFrameCountToNotify) {
notifyAndResetDroppedFrameCount(); maybeNotifyDroppedFrameCount();
} }
outputBuffer.release(); outputBuffer.release();
outputBuffer = null; outputBuffer = null;
@ -233,12 +232,12 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
private void renderBuffer() { private void renderBuffer() {
codecCounters.renderedOutputBufferCount++; codecCounters.renderedOutputBufferCount++;
consecutiveDroppedFrameCount = 0; consecutiveDroppedFrameCount = 0;
notifyIfVideoSizeChanged(outputBuffer.width, outputBuffer.height); maybeNotifyVideoSizeChanged(outputBuffer.width, outputBuffer.height);
if (outputBuffer.mode == VpxDecoder.OUTPUT_MODE_RGB && surface != null) { if (outputBuffer.mode == VpxDecoder.OUTPUT_MODE_RGB && surface != null) {
renderRgbFrame(outputBuffer, scaleToFit); renderRgbFrame(outputBuffer, scaleToFit);
if (!drawnToSurface) { if (!drawnToSurface) {
drawnToSurface = true; drawnToSurface = true;
notifyDrawnToSurface(surface); eventDispatcher.drawnToSurface(surface);
} }
outputBuffer.release(); outputBuffer.release();
} else if (outputBuffer.mode == VpxDecoder.OUTPUT_MODE_YUV && outputBufferRenderer != null) { } else if (outputBuffer.mode == VpxDecoder.OUTPUT_MODE_YUV && outputBufferRenderer != null) {
@ -334,7 +333,7 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
@Override @Override
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException { protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
notifyVideoCodecCounters(); eventDispatcher.codecCounters(codecCounters);
} }
@Override @Override
@ -345,7 +344,7 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
@Override @Override
protected void onStopped() { protected void onStopped() {
notifyAndResetDroppedFrameCount(); maybeNotifyDroppedFrameCount();
} }
@Override @Override
@ -410,70 +409,21 @@ public final class LibvpxVideoTrackRenderer extends TrackRenderer {
} }
} }
private void notifyIfVideoSizeChanged(final int width, final int height) { private void maybeNotifyVideoSizeChanged(final int width, final int height) {
if (previousWidth == -1 || previousHeight == -1 || previousWidth != width if (previousWidth != width || previousHeight != height) {
|| previousHeight != height) {
previousWidth = width; previousWidth = width;
previousHeight = height; previousHeight = height;
if (eventHandler != null && eventListener != null) { eventDispatcher.videoSizeChanged(width, height, 0, 1);
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onVideoSizeChanged(width, height, 0, 1);
}
});
}
} }
} }
private void notifyAndResetDroppedFrameCount() { private void maybeNotifyDroppedFrameCount() {
if (eventHandler != null && eventListener != null && droppedFrameCount > 0) { if (droppedFrameCount > 0) {
long now = SystemClock.elapsedRealtime(); long now = SystemClock.elapsedRealtime();
final int countToNotify = droppedFrameCount; long elapsedMs = now - droppedFrameAccumulationStartTimeMs;
final long elapsedToNotify = now - droppedFrameAccumulationStartTimeMs; eventDispatcher.droppedFrameCount(droppedFrameCount, elapsedMs);
droppedFrameCount = 0; droppedFrameCount = 0;
droppedFrameAccumulationStartTimeMs = now; droppedFrameAccumulationStartTimeMs = now;
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDroppedFrames(countToNotify, elapsedToNotify);
}
});
}
}
private void notifyDrawnToSurface(final Surface surface) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDrawnToSurface(surface);
}
});
}
}
private void notifyDecoderInitialized(final String decoderName,
final long initializedTimestamp, final long initializationDuration) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDecoderInitialized(decoderName, initializedTimestamp,
initializationDuration);
}
});
}
}
private void notifyVideoCodecCounters() {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onVideoCodecCounters(codecCounters);
}
});
} }
} }

View File

@ -16,25 +16,33 @@
package com.google.android.exoplayer; package com.google.android.exoplayer;
import com.google.android.exoplayer.audio.AudioTrack; import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.util.Assertions;
import android.os.Handler;
import android.os.SystemClock;
/** /**
* Optional interface definition for a callback to be notified of audio {@link TrackRenderer} * Interface definition for a callback to be notified of audio {@link TrackRenderer} events.
* events.
*/ */
public interface AudioTrackRendererEventListener extends TrackRendererEventListener { public interface AudioTrackRendererEventListener {
/**
* Invoked when an {@link AudioTrack} fails to initialize.
*
* @param e The corresponding exception.
*/
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
/** /**
* Invoked when an {@link AudioTrack} write fails. * Invoked to pass the codec counters when the renderer is enabled.
* *
* @param e The corresponding exception. * @param counters CodecCounters object used by the renderer.
*/ */
void onAudioTrackWriteError(AudioTrack.WriteException e); void onAudioCodecCounters(CodecCounters counters);
/**
* Invoked when a decoder is created.
*
* @param decoderName The decoder that was created.
* @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization
* finished.
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
*/
void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs,
long initializationDurationMs);
/** /**
* Invoked when an {@link AudioTrack} underrun occurs. * Invoked when an {@link AudioTrack} underrun occurs.
@ -48,9 +56,54 @@ public interface AudioTrackRendererEventListener extends TrackRendererEventListe
void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
/** /**
* Invoked to pass the codec counters when the renderer is enabled. * Dispatches events to a {@link AudioTrackRendererEventListener}.
*
* @param counters CodecCounters object used by the renderer.
*/ */
void onAudioCodecCounters(CodecCounters counters); final class EventDispatcher {
private final Handler handler;
private final AudioTrackRendererEventListener listener;
public EventDispatcher(Handler handler, AudioTrackRendererEventListener listener) {
this.handler = listener != null ? Assertions.checkNotNull(handler) : null;
this.listener = listener;
}
public void codecCounters(final CodecCounters codecCounters) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onAudioCodecCounters(codecCounters);
}
});
}
}
public void decoderInitialized(final String decoderName,
final long initializedTimestampMs, final long initializationDurationMs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onAudioDecoderInitialized(decoderName, initializedTimestampMs,
initializationDurationMs);
}
});
}
}
public void audioTrackUnderrun(final int bufferSize, final long bufferSizeMs,
final long elapsedSinceLastFeedMs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
});
}
}
}
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer; package com.google.android.exoplayer;
import com.google.android.exoplayer.AudioTrackRendererEventListener.EventDispatcher;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioTrack; import com.google.android.exoplayer.audio.AudioTrack;
@ -40,15 +41,6 @@ import java.nio.ByteBuffer;
@TargetApi(16) @TargetApi(16)
public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implements MediaClock { public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implements MediaClock {
/**
* Interface definition for a callback to be notified of {@link MediaCodecAudioTrackRenderer}
* events.
*/
public interface EventListener extends MediaCodecTrackRenderer.EventListener,
AudioTrackRendererEventListener {
// No extra methods
}
/** /**
* The type of a message that can be passed to an instance of this class via * The type of a message that can be passed to an instance of this class via
* {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object * {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object
@ -65,7 +57,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
*/ */
public static final int MSG_SET_PLAYBACK_PARAMS = 2; public static final int MSG_SET_PLAYBACK_PARAMS = 2;
private final EventListener eventListener; private final EventDispatcher eventDispatcher;
private final AudioTrack audioTrack; private final AudioTrack audioTrack;
private boolean passthroughEnabled; private boolean passthroughEnabled;
@ -107,7 +99,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
* @param eventListener A listener of events. 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.
*/ */
public MediaCodecAudioTrackRenderer(MediaCodecSelector mediaCodecSelector, Handler eventHandler, public MediaCodecAudioTrackRenderer(MediaCodecSelector mediaCodecSelector, Handler eventHandler,
EventListener eventListener) { AudioTrackRendererEventListener eventListener) {
this(mediaCodecSelector, null, true, eventHandler, eventListener); this(mediaCodecSelector, null, true, eventHandler, eventListener);
} }
@ -126,7 +118,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
*/ */
public MediaCodecAudioTrackRenderer(MediaCodecSelector mediaCodecSelector, public MediaCodecAudioTrackRenderer(MediaCodecSelector mediaCodecSelector,
DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys, DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys,
Handler eventHandler, EventListener eventListener) { Handler eventHandler, AudioTrackRendererEventListener eventListener) {
this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler,
eventListener, null, AudioManager.STREAM_MUSIC); eventListener, null, AudioManager.STREAM_MUSIC);
} }
@ -149,13 +141,12 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
*/ */
public MediaCodecAudioTrackRenderer(MediaCodecSelector mediaCodecSelector, public MediaCodecAudioTrackRenderer(MediaCodecSelector mediaCodecSelector,
DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys, DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys,
Handler eventHandler, EventListener eventListener, AudioCapabilities audioCapabilities, Handler eventHandler, AudioTrackRendererEventListener eventListener,
int streamType) { AudioCapabilities audioCapabilities, int streamType) {
super(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, super(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
eventListener); audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
this.eventListener = eventListener; audioTrack = new AudioTrack(audioCapabilities, streamType);
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET; eventDispatcher = new EventDispatcher(eventHandler, eventListener);
this.audioTrack = new AudioTrack(audioCapabilities, streamType);
} }
@Override @Override
@ -235,6 +226,12 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
return this; return this;
} }
@Override
protected void onCodecInitialized(String name, long initializedTimestampMs,
long initializationDurationMs) {
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
}
@Override @Override
protected void onInputFormatChanged(FormatHolder holder) throws ExoPlaybackException { protected void onInputFormatChanged(FormatHolder holder) throws ExoPlaybackException {
super.onInputFormatChanged(holder); super.onInputFormatChanged(holder);
@ -275,7 +272,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
@Override @Override
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException { protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
super.onEnabled(formats, joining); super.onEnabled(formats, joining);
notifyAudioCodecCounters(); eventDispatcher.codecCounters(codecCounters);
} }
@Override @Override
@ -357,7 +354,6 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
} }
audioTrackHasData = false; audioTrackHasData = false;
} catch (AudioTrack.InitializationException e) { } catch (AudioTrack.InitializationException e) {
notifyAudioTrackInitializationError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
if (getState() == TrackRenderer.STATE_STARTED) { if (getState() == TrackRenderer.STATE_STARTED) {
@ -371,7 +367,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs; long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
long bufferSizeUs = audioTrack.getBufferSizeUs(); long bufferSizeUs = audioTrack.getBufferSizeUs();
long bufferSizeMs = bufferSizeUs == C.UNSET_TIME_US ? -1 : bufferSizeUs / 1000; long bufferSizeMs = bufferSizeUs == C.UNSET_TIME_US ? -1 : bufferSizeUs / 1000;
notifyAudioTrackUnderrun(audioTrack.getBufferSize(), bufferSizeMs, elapsedSinceLastFeedMs); eventDispatcher.audioTrackUnderrun(audioTrack.getBufferSize(), bufferSizeMs,
elapsedSinceLastFeedMs);
} }
} }
@ -380,7 +377,6 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
handleBufferResult = audioTrack.handleBuffer(buffer, bufferPresentationTimeUs); handleBufferResult = audioTrack.handleBuffer(buffer, bufferPresentationTimeUs);
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime(); lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
} catch (AudioTrack.WriteException e) { } catch (AudioTrack.WriteException e) {
notifyAudioTrackWriteError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
@ -424,49 +420,4 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
} }
} }
private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackInitializationError(e);
}
});
}
}
private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackWriteError(e);
}
});
}
}
private void notifyAudioTrackUnderrun(final int bufferSize, final long bufferSizeMs,
final long elapsedSinceLastFeedMs) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
});
}
}
private void notifyAudioCodecCounters() {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioCodecCounters(codecCounters);
}
});
}
}
} }

View File

@ -27,7 +27,6 @@ import android.media.MediaCodec;
import android.media.MediaCodec.CodecException; import android.media.MediaCodec.CodecException;
import android.media.MediaCodec.CryptoException; import android.media.MediaCodec.CryptoException;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
@ -41,20 +40,6 @@ import java.util.List;
@TargetApi(16) @TargetApi(16)
public abstract class MediaCodecTrackRenderer extends TrackRenderer { public abstract class MediaCodecTrackRenderer extends TrackRenderer {
/**
* Interface definition for a callback to be notified of {@link MediaCodecTrackRenderer} events.
*/
public interface EventListener extends TrackRendererEventListener {
/**
* Invoked when a decoder operation raises a {@link CryptoException}.
*
* @param e The corresponding exception.
*/
void onCryptoError(CryptoException e);
}
/** /**
* Thrown when a failure occurs instantiating a decoder. * Thrown when a failure occurs instantiating a decoder.
*/ */
@ -167,8 +152,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final List<Long> decodeOnlyPresentationTimestamps; private final List<Long> decodeOnlyPresentationTimestamps;
private final MediaCodec.BufferInfo outputBufferInfo; private final MediaCodec.BufferInfo outputBufferInfo;
private final EventListener eventListener;
protected final Handler eventHandler;
private Format format; private Format format;
private MediaCodec codec; private MediaCodec codec;
@ -204,19 +187,13 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
* begin in parallel with key acquisition. This parameter specifies whether the renderer is * begin in parallel with key acquisition. This parameter specifies whether the renderer is
* permitted to play clear regions of encrypted media files before {@code drmSessionManager} * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media. * has obtained the keys necessary to decrypt encrypted regions of the media.
* @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.
*/ */
public MediaCodecTrackRenderer(MediaCodecSelector mediaCodecSelector, public MediaCodecTrackRenderer(MediaCodecSelector mediaCodecSelector,
DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys, DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys) {
Handler eventHandler, EventListener eventListener) {
Assertions.checkState(Util.SDK_INT >= 16); Assertions.checkState(Util.SDK_INT >= 16);
this.mediaCodecSelector = Assertions.checkNotNull(mediaCodecSelector); this.mediaCodecSelector = Assertions.checkNotNull(mediaCodecSelector);
this.drmSessionManager = drmSessionManager; this.drmSessionManager = drmSessionManager;
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
codecCounters = new CodecCounters(); codecCounters = new CodecCounters();
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
@ -311,13 +288,13 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
try { try {
decoderInfo = getDecoderInfo(mediaCodecSelector, format, requiresSecureDecoder); decoderInfo = getDecoderInfo(mediaCodecSelector, format, requiresSecureDecoder);
} catch (DecoderQueryException e) { } catch (DecoderQueryException e) {
notifyAndThrowDecoderInitError(new DecoderInitializationException(format, e, throwDecoderInitError(new DecoderInitializationException(format, e, requiresSecureDecoder,
requiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR)); DecoderInitializationException.DECODER_QUERY_ERROR));
} }
if (decoderInfo == null) { if (decoderInfo == null) {
notifyAndThrowDecoderInitError(new DecoderInitializationException(format, null, throwDecoderInitError(new DecoderInitializationException(format, null, requiresSecureDecoder,
requiresSecureDecoder, DecoderInitializationException.NO_SUITABLE_DECODER_ERROR)); DecoderInitializationException.NO_SUITABLE_DECODER_ERROR));
} }
String codecName = decoderInfo.name; String codecName = decoderInfo.name;
@ -339,13 +316,13 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
codec.start(); codec.start();
TraceUtil.endSection(); TraceUtil.endSection();
long codecInitializedTimestamp = SystemClock.elapsedRealtime(); long codecInitializedTimestamp = SystemClock.elapsedRealtime();
notifyDecoderInitialized(codecName, codecInitializedTimestamp, onCodecInitialized(codecName, codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp); codecInitializedTimestamp - codecInitializingTimestamp);
inputBuffers = codec.getInputBuffers(); inputBuffers = codec.getInputBuffers();
outputBuffers = codec.getOutputBuffers(); outputBuffers = codec.getOutputBuffers();
} catch (Exception e) { } catch (Exception e) {
notifyAndThrowDecoderInitError(new DecoderInitializationException(format, e, throwDecoderInitError(new DecoderInitializationException(format, e, requiresSecureDecoder,
requiresSecureDecoder, codecName)); codecName));
} }
codecHotswapDeadlineMs = getState() == TrackRenderer.STATE_STARTED codecHotswapDeadlineMs = getState() == TrackRenderer.STATE_STARTED
? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) : -1; ? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) : -1;
@ -354,9 +331,8 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
codecCounters.codecInitCount++; codecCounters.codecInitCount++;
} }
private void notifyAndThrowDecoderInitError(DecoderInitializationException e) private void throwDecoderInitError(DecoderInitializationException e)
throws ExoPlaybackException { throws ExoPlaybackException {
notifyDecoderInitializationError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
@ -575,7 +551,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
inputIndex = -1; inputIndex = -1;
} }
} catch (CryptoException e) { } catch (CryptoException e) {
notifyCryptoError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
return false; return false;
@ -613,7 +588,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReconfigurationState = RECONFIGURATION_STATE_NONE;
codecCounters.inputBufferCount++; codecCounters.inputBufferCount++;
} catch (CryptoException e) { } catch (CryptoException e) {
notifyCryptoError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
return true; return true;
@ -647,6 +621,21 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
&& (bufferEncrypted || !playClearSamplesWithoutKeys); && (bufferEncrypted || !playClearSamplesWithoutKeys);
} }
/**
* Invoked when a {@link MediaCodec} has been created and configured.
* <p>
* The default implementation is a no-op.
*
* @param name The name of the codec that was initialized.
* @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization
* finished.
* @param initializationDurationMs The time taken to initialize the codec in milliseconds.
*/
protected void onCodecInitialized(String name, long initializedTimestampMs,
long initializationDurationMs) {
// Do nothing.
}
/** /**
* Invoked when a new format is read from the upstream {@link SampleSource}. * Invoked when a new format is read from the upstream {@link SampleSource}.
* *
@ -878,41 +867,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
} }
} }
private void notifyDecoderInitializationError(final DecoderInitializationException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDecoderInitializationError(e);
}
});
}
}
private void notifyCryptoError(final CryptoException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onCryptoError(e);
}
});
}
}
private void notifyDecoderInitialized(final String decoderName,
final long initializedTimestamp, final long initializationDuration) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDecoderInitialized(decoderName, initializedTimestamp,
initializationDuration);
}
});
}
}
private boolean shouldSkipOutputBuffer(long presentationTimeUs) { private boolean shouldSkipOutputBuffer(long presentationTimeUs) {
// We avoid using decodeOnlyPresentationTimestamps.remove(presentationTimeUs) because it would // We avoid using decodeOnlyPresentationTimestamps.remove(presentationTimeUs) because it would
// box presentationTimeUs, creating a Long object that would need to be garbage collected. // box presentationTimeUs, creating a Long object that would need to be garbage collected.

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer; package com.google.android.exoplayer;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.VideoTrackRendererEventListener.EventDispatcher;
import com.google.android.exoplayer.drm.DrmSessionManager; import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.TraceUtil; import com.google.android.exoplayer.util.TraceUtil;
@ -40,15 +41,6 @@ import java.nio.ByteBuffer;
@TargetApi(16) @TargetApi(16)
public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
/**
* Interface definition for a callback to be notified of {@link MediaCodecVideoTrackRenderer}
* events.
*/
public interface EventListener extends MediaCodecTrackRenderer.EventListener,
VideoTrackRendererEventListener {
// No extra methods
}
private static final String TAG = "MediaCodecVideoRenderer"; private static final String TAG = "MediaCodecVideoRenderer";
// TODO: Use MediaFormat constants if these get exposed through the API. See // TODO: Use MediaFormat constants if these get exposed through the API. See
@ -59,7 +51,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
private static final String KEY_CROP_TOP = "crop-top"; private static final String KEY_CROP_TOP = "crop-top";
private final VideoFrameReleaseTimeHelper frameReleaseTimeHelper; private final VideoFrameReleaseTimeHelper frameReleaseTimeHelper;
private final EventListener eventListener; private final EventDispatcher eventDispatcher;
private final long allowedJoiningTimeMs; private final long allowedJoiningTimeMs;
private final int videoScalingMode; private final int videoScalingMode;
private final int maxDroppedFrameCountToNotify; private final int maxDroppedFrameCountToNotify;
@ -122,11 +114,11 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
* null if delivery of events is not required. * 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 eventListener A listener of events. May be null if delivery of events is not required.
* @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between * @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between
* invocations of {@link EventListener#onDroppedFrames(int, long)}. * invocations of {@link VideoTrackRendererEventListener#onDroppedFrames(int, long)}.
*/ */
public MediaCodecVideoTrackRenderer(Context context, MediaCodecSelector mediaCodecSelector, public MediaCodecVideoTrackRenderer(Context context, MediaCodecSelector mediaCodecSelector,
int videoScalingMode, long allowedJoiningTimeMs, Handler eventHandler, int videoScalingMode, long allowedJoiningTimeMs, Handler eventHandler,
EventListener eventListener, int maxDroppedFrameCountToNotify) { VideoTrackRendererEventListener eventListener, int maxDroppedFrameCountToNotify) {
this(context, mediaCodecSelector, videoScalingMode, allowedJoiningTimeMs, null, false, this(context, mediaCodecSelector, videoScalingMode, allowedJoiningTimeMs, null, false,
eventHandler, eventListener, maxDroppedFrameCountToNotify); eventHandler, eventListener, maxDroppedFrameCountToNotify);
} }
@ -142,26 +134,25 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
* content is not required. * content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to * For example a media file may start with a short clear region so as to allow playback to
* begin in parallel with key acquisision. This parameter specifies whether the renderer is * begin in parallel with key acquisition. This parameter specifies whether the renderer is
* permitted to play clear regions of encrypted media files before {@code drmSessionManager} * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media. * has obtained the keys necessary to decrypt encrypted regions of the media.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 eventListener A listener of events. May be null if delivery of events is not required.
* @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between * @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between
* invocations of {@link EventListener#onDroppedFrames(int, long)}. * invocations of {@link VideoTrackRendererEventListener#onDroppedFrames(int, long)}.
*/ */
public MediaCodecVideoTrackRenderer(Context context, MediaCodecSelector mediaCodecSelector, public MediaCodecVideoTrackRenderer(Context context, MediaCodecSelector mediaCodecSelector,
int videoScalingMode, long allowedJoiningTimeMs, DrmSessionManager drmSessionManager, int videoScalingMode, long allowedJoiningTimeMs, DrmSessionManager drmSessionManager,
boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener, boolean playClearSamplesWithoutKeys, Handler eventHandler,
int maxDroppedFrameCountToNotify) { VideoTrackRendererEventListener eventListener, int maxDroppedFrameCountToNotify) {
super(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, super(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
eventListener);
this.frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context);
this.videoScalingMode = videoScalingMode; this.videoScalingMode = videoScalingMode;
this.allowedJoiningTimeMs = allowedJoiningTimeMs; this.allowedJoiningTimeMs = allowedJoiningTimeMs;
this.eventListener = eventListener;
this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify; this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify;
frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround(); deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround();
joiningDeadlineMs = -1; joiningDeadlineMs = -1;
currentWidth = -1; currentWidth = -1;
@ -227,8 +218,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
adaptiveMaxWidth = Math.max(adaptiveMaxWidth, format.width); adaptiveMaxWidth = Math.max(adaptiveMaxWidth, format.width);
adaptiveMaxHeight = Math.max(adaptiveMaxHeight, format.height); adaptiveMaxHeight = Math.max(adaptiveMaxHeight, format.height);
} }
if (adaptiveMaxWidth == Format.NO_VALUE if (adaptiveMaxWidth == Format.NO_VALUE || adaptiveMaxHeight == Format.NO_VALUE) {
|| adaptiveMaxHeight == Format.NO_VALUE) {
Log.w(TAG, "Maximum dimensions unknown. Assuming 1920x1080."); Log.w(TAG, "Maximum dimensions unknown. Assuming 1920x1080.");
adaptiveMaxWidth = 1920; adaptiveMaxWidth = 1920;
adaptiveMaxHeight = 1080; adaptiveMaxHeight = 1080;
@ -238,7 +228,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
joiningDeadlineMs = SystemClock.elapsedRealtime() + allowedJoiningTimeMs; joiningDeadlineMs = SystemClock.elapsedRealtime() + allowedJoiningTimeMs;
} }
frameReleaseTimeHelper.enable(); frameReleaseTimeHelper.enable();
notifyVideoCodecCounters(); eventDispatcher.codecCounters(codecCounters);
} }
@Override @Override
@ -332,6 +322,12 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
codec.configure(getFrameworkMediaFormat(format), surface, crypto, 0); codec.configure(getFrameworkMediaFormat(format), surface, crypto, 0);
} }
@Override
protected void onCodecInitialized(String name, long initializedTimestampMs,
long initializationDurationMs) {
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
}
@Override @Override
protected void onInputFormatChanged(FormatHolder holder) throws ExoPlaybackException { protected void onInputFormatChanged(FormatHolder holder) throws ExoPlaybackException {
super.onInputFormatChanged(holder); super.onInputFormatChanged(holder);
@ -559,65 +555,34 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
return frameworkMediaFormat; return frameworkMediaFormat;
} }
private void maybeNotifyVideoSizeChanged() { private void maybeNotifyDrawnToSurface() {
if (eventHandler == null || eventListener == null if (!reportedDrawnToSurface) {
|| (lastReportedWidth == currentWidth && lastReportedHeight == currentHeight eventDispatcher.drawnToSurface(surface);
&& lastReportedUnappliedRotationDegrees == currentUnappliedRotationDegrees reportedDrawnToSurface = true;
&& lastReportedPixelWidthHeightRatio == currentPixelWidthHeightRatio)) {
return;
} }
// Make final copies to ensure the runnable reports the correct values.
final int currentWidth = this.currentWidth;
final int currentHeight = this.currentHeight;
final int currentUnappliedRotationDegrees = this.currentUnappliedRotationDegrees;
final float currentPixelWidthHeightRatio = this.currentPixelWidthHeightRatio;
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onVideoSizeChanged(currentWidth, currentHeight,
currentUnappliedRotationDegrees, currentPixelWidthHeightRatio);
}
});
// Update the last reported values.
lastReportedWidth = currentWidth;
lastReportedHeight = currentHeight;
lastReportedUnappliedRotationDegrees = currentUnappliedRotationDegrees;
lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio;
} }
private void maybeNotifyDrawnToSurface() { private void maybeNotifyVideoSizeChanged() {
if (eventHandler == null || eventListener == null || reportedDrawnToSurface) { if (lastReportedWidth != currentWidth || lastReportedHeight != currentHeight
return; || lastReportedUnappliedRotationDegrees != currentUnappliedRotationDegrees
|| lastReportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) {
eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees,
currentPixelWidthHeightRatio);
lastReportedWidth = currentWidth;
lastReportedHeight = currentHeight;
lastReportedUnappliedRotationDegrees = currentUnappliedRotationDegrees;
lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio;
} }
// Make a final copy to ensure the runnable reports the correct surface.
final Surface surface = this.surface;
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDrawnToSurface(surface);
}
});
// Record that we have reported that the surface has been drawn to.
reportedDrawnToSurface = true;
} }
private void maybeNotifyDroppedFrameCount() { private void maybeNotifyDroppedFrameCount() {
if (eventHandler == null || eventListener == null || droppedFrameCount == 0) { if (droppedFrameCount > 0) {
return; long now = SystemClock.elapsedRealtime();
long elapsedMs = now - droppedFrameAccumulationStartTimeMs;
eventDispatcher.droppedFrameCount(droppedFrameCount, elapsedMs);
droppedFrameCount = 0;
droppedFrameAccumulationStartTimeMs = now;
} }
long now = SystemClock.elapsedRealtime();
// Make final copies to ensure the runnable reports the correct values.
final int countToNotify = droppedFrameCount;
final long elapsedToNotify = now - droppedFrameAccumulationStartTimeMs;
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDroppedFrames(countToNotify, elapsedToNotify);
}
});
// Reset the dropped frame tracking.
droppedFrameCount = 0;
droppedFrameAccumulationStartTimeMs = now;
} }
/** /**
@ -638,15 +603,4 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
return Util.SDK_INT <= 22 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER); return Util.SDK_INT <= 22 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER);
} }
private void notifyVideoCodecCounters() {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onVideoCodecCounters(codecCounters);
}
});
}
}
} }

View File

@ -1,41 +0,0 @@
/*
* 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;
/**
* Optional interface definition for a callback to be notified of {@link TrackRenderer} events.
*/
public interface TrackRendererEventListener {
/**
* Invoked when a decoder fails to initialize.
*
* @param e The corresponding exception.
*/
void onDecoderInitializationError(Exception e);
/**
* Invoked when a decoder is successfully created.
*
* @param decoderName The decoder that was configured and created.
* @param elapsedRealtimeMs {@code elapsedRealtime} timestamp of when the initialization
* finished.
* @param initializationDurationMs Amount of time taken to initialize the decoder.
*/
void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
long initializationDurationMs);
}

View File

@ -15,14 +15,35 @@
*/ */
package com.google.android.exoplayer; package com.google.android.exoplayer;
import com.google.android.exoplayer.util.Assertions;
import android.os.Handler;
import android.os.SystemClock;
import android.view.Surface; import android.view.Surface;
import android.view.TextureView; import android.view.TextureView;
/** /**
* Optional interface definition for a callback to be notified of video {@link TrackRenderer} * Interface definition for a callback to be notified of video {@link TrackRenderer} events.
* events.
*/ */
public interface VideoTrackRendererEventListener extends TrackRendererEventListener { public interface VideoTrackRendererEventListener {
/**
* Invoked to pass the codec counters when the renderer is enabled.
*
* @param counters CodecCounters object used by the renderer.
*/
void onVideoCodecCounters(CodecCounters counters);
/**
* Invoked when a decoder is created.
*
* @param decoderName The decoder that was created.
* @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization
* finished.
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
*/
void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs,
long initializationDurationMs);
/** /**
* Invoked to report the number of frames dropped by the renderer. Dropped frames are reported * Invoked to report the number of frames dropped by the renderer. Dropped frames are reported
@ -30,12 +51,12 @@ public interface VideoTrackRendererEventListener extends TrackRendererEventListe
* reaches a specified threshold whilst the renderer is started. * reaches a specified threshold whilst the renderer is started.
* *
* @param count The number of dropped frames. * @param count The number of dropped frames.
* @param elapsed The duration in milliseconds over which the frames were dropped. This * @param elapsedMs The duration in milliseconds over which the frames were dropped. This
* duration is timed from when the renderer was started or from when dropped frames were * duration is timed from when the renderer was started or from when dropped frames were
* last reported (whichever was more recent), and not from when the first of the reported * last reported (whichever was more recent), and not from when the first of the reported
* drops occurred. * drops occurred.
*/ */
void onDroppedFrames(int count, long elapsed); void onDroppedFrames(int count, long elapsedMs);
/** /**
* Invoked each time there's a change in the size of the video being rendered. * Invoked each time there's a change in the size of the video being rendered.
@ -65,10 +86,77 @@ public interface VideoTrackRendererEventListener extends TrackRendererEventListe
void onDrawnToSurface(Surface surface); void onDrawnToSurface(Surface surface);
/** /**
* Invoked to pass the codec counters when the renderer is enabled. * Dispatches events to a {@link VideoTrackRendererEventListener}.
*
* @param counters CodecCounters object used by the renderer.
*/ */
void onVideoCodecCounters(CodecCounters counters); final class EventDispatcher {
private final Handler handler;
private final VideoTrackRendererEventListener listener;
public EventDispatcher(Handler handler, VideoTrackRendererEventListener listener) {
this.handler = listener != null ? Assertions.checkNotNull(handler) : null;
this.listener = listener;
}
public void codecCounters(final CodecCounters codecCounters) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onVideoCodecCounters(codecCounters);
}
});
}
}
public void decoderInitialized(final String decoderName,
final long initializedTimestampMs, final long initializationDurationMs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onVideoDecoderInitialized(decoderName, initializedTimestampMs,
initializationDurationMs);
}
});
}
}
public void droppedFrameCount(final int droppedFrameCount, final long elapsedMs) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onDroppedFrames(droppedFrameCount, elapsedMs);
}
});
}
}
public void videoSizeChanged(final int width, final int height,
final int unappliedRotationDegrees, final float pixelWidthHeightRatio) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onVideoSizeChanged(width, height, unappliedRotationDegrees,
pixelWidthHeightRatio);
}
});
}
}
public void drawnToSurface(final Surface surface) {
if (listener != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onDrawnToSurface(surface);
}
});
}
}
}
} }

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.os.Handler; import android.os.Handler;
@ -114,7 +115,7 @@ public interface ChunkTrackStreamEventListener {
private final int sourceId; private final int sourceId;
public EventDispatcher(Handler handler, ChunkTrackStreamEventListener listener, int sourceId) { public EventDispatcher(Handler handler, ChunkTrackStreamEventListener listener, int sourceId) {
this.handler = listener != null ? handler : null; this.handler = listener != null ? Assertions.checkNotNull(handler) : null;
this.listener = listener; this.listener = listener;
this.sourceId = sourceId; this.sourceId = sourceId;
} }

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer.extensions; package com.google.android.exoplayer.extensions;
import com.google.android.exoplayer.AudioTrackRendererEventListener; import com.google.android.exoplayer.AudioTrackRendererEventListener;
import com.google.android.exoplayer.AudioTrackRendererEventListener.EventDispatcher;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.CodecCounters; import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.DecoderInputBuffer; import com.google.android.exoplayer.DecoderInputBuffer;
@ -46,8 +47,7 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
public final CodecCounters codecCounters = new CodecCounters(); public final CodecCounters codecCounters = new CodecCounters();
private final Handler eventHandler; private final EventDispatcher eventDispatcher;
private final AudioTrackRendererEventListener eventListener;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private Format inputFormat; private Format inputFormat;
@ -78,10 +78,9 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
*/ */
public AudioDecoderTrackRenderer(Handler eventHandler, public AudioDecoderTrackRenderer(Handler eventHandler,
AudioTrackRendererEventListener eventListener) { AudioTrackRendererEventListener eventListener) {
this.eventHandler = eventHandler; eventDispatcher = new EventDispatcher(eventHandler, eventListener);
this.eventListener = eventListener; audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET; audioTrack = new AudioTrack();
this.audioTrack = new AudioTrack();
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
} }
@ -108,7 +107,7 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
long codecInitializingTimestamp = SystemClock.elapsedRealtime(); long codecInitializingTimestamp = SystemClock.elapsedRealtime();
decoder = createDecoder(inputFormat); decoder = createDecoder(inputFormat);
long codecInitializedTimestamp = SystemClock.elapsedRealtime(); long codecInitializedTimestamp = SystemClock.elapsedRealtime();
notifyDecoderInitialized(decoder.getName(), codecInitializedTimestamp, eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp); codecInitializedTimestamp - codecInitializingTimestamp);
codecCounters.codecInitCount++; codecCounters.codecInitCount++;
} catch (AudioDecoderException e) { } catch (AudioDecoderException e) {
@ -120,13 +119,8 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
try { try {
while (drainOutputBuffer()) {} while (drainOutputBuffer()) {}
while (feedInputBuffer()) {} while (feedInputBuffer()) {}
} catch (AudioTrack.InitializationException e) { } catch (AudioTrack.InitializationException | AudioTrack.WriteException
notifyAudioTrackInitializationError(e); | AudioDecoderException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
} catch (AudioTrack.WriteException e) {
notifyAudioTrackWriteError(e);
throw ExoPlaybackException.createForRenderer(e, getIndex());
} catch (AudioDecoderException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
codecCounters.ensureUpdated(); codecCounters.ensureUpdated();
@ -197,7 +191,8 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs; long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
long bufferSizeUs = audioTrack.getBufferSizeUs(); long bufferSizeUs = audioTrack.getBufferSizeUs();
long bufferSizeMs = bufferSizeUs == C.UNSET_TIME_US ? -1 : bufferSizeUs / 1000; long bufferSizeMs = bufferSizeUs == C.UNSET_TIME_US ? -1 : bufferSizeUs / 1000;
notifyAudioTrackUnderrun(audioTrack.getBufferSize(), bufferSizeMs, elapsedSinceLastFeedMs); eventDispatcher.audioTrackUnderrun(audioTrack.getBufferSize(), bufferSizeMs,
elapsedSinceLastFeedMs);
} }
} }
@ -311,7 +306,7 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
@Override @Override
protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException { protected void onEnabled(Format[] formats, boolean joining) throws ExoPlaybackException {
notifyAudioCodecCounters(); eventDispatcher.codecCounters(codecCounters);
} }
@Override @Override
@ -360,62 +355,4 @@ public abstract class AudioDecoderTrackRenderer extends TrackRenderer implements
} }
} }
private void notifyDecoderInitialized(final String decoderName,
final long initializedTimestamp, final long initializationDuration) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDecoderInitialized(decoderName, initializedTimestamp,
initializationDuration);
}
});
}
}
private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackInitializationError(e);
}
});
}
}
private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackWriteError(e);
}
});
}
}
private void notifyAudioTrackUnderrun(final int bufferSize, final long bufferSizeMs,
final long elapsedSinceLastFeedMs) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
});
}
}
private void notifyAudioCodecCounters() {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioCodecCounters(codecCounters);
}
});
}
}
} }