diff --git a/demo/src/main/assets/media.exolist.json b/demo/src/main/assets/media.exolist.json index dd88f206c1..6fba5bd65b 100644 --- a/demo/src/main/assets/media.exolist.json +++ b/demo/src/main/assets/media.exolist.json @@ -277,18 +277,6 @@ } ] }, - { - "name": "ClearKey DASH", - "samples": [ - { - "name": "Big Buck Bunny (CENC ClearKey)", - "uri": "http://html5.cablelabs.com:8100/cenc/ck/dash.mpd", - "extension": "mpd", - "drm_scheme": "cenc", - "drm_license_url": "https://wasabeef.jp/demos/cenc-ck-dash.json" - } - ] - }, { "name": "SmoothStreaming", "samples": [ diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java b/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java index 38085dfc3a..2f343ec40e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java +++ b/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java @@ -54,8 +54,8 @@ import java.util.ArrayList; * safe to call {@link #handleBuffer(ByteBuffer, long)} after {@link #reset()} without calling * {@link #configure(String, int, int, int, int)}. *

- * Call {@link #handleEndOfStream()} to play out all data when no more input buffers will be - * provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #reset}. Call + * Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers will + * be provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #reset}. Call * {@link #release()} when the instance is no longer required. */ public final class AudioTrack { @@ -324,6 +324,8 @@ public final class AudioTrack { private ByteBuffer outputBuffer; private byte[] preV21OutputBuffer; private int preV21OutputBufferOffset; + private int drainingBufferProcessorIndex; + private boolean handledEndOfStream; private boolean playing; private int audioSessionId; @@ -366,6 +368,7 @@ public final class AudioTrack { startMediaTimeState = START_NOT_SET; streamType = C.STREAM_TYPE_DEFAULT; audioSessionId = C.AUDIO_SESSION_ID_UNSET; + drainingBufferProcessorIndex = C.INDEX_UNSET; this.bufferProcessors = new BufferProcessor[0]; outputBuffers = new ByteBuffer[0]; } @@ -762,7 +765,8 @@ public final class AudioTrack { int count = bufferProcessors.length; int index = count; while (index >= 0) { - ByteBuffer input = index > 0 ? outputBuffers[index - 1] : inputBuffer; + ByteBuffer input = index > 0 ? outputBuffers[index - 1] + : (inputBuffer != null ? inputBuffer : BufferProcessor.EMPTY_BUFFER); if (index == count) { writeBuffer(input, avSyncPresentationTimeUs); } else { @@ -851,14 +855,54 @@ public final class AudioTrack { } /** - * Ensures that the last data passed to {@link #handleBuffer(ByteBuffer, long)} is played in full. + * Plays out remaining audio. {@link #isEnded()} will return {@code true} when playback has ended. + * + * @throws WriteException If an error occurs draining data to the track. */ - public void handleEndOfStream() { - if (isInitialized()) { - // TODO: Drain buffer processors before stopping the AudioTrack. - audioTrackUtil.handleEndOfStream(getWrittenFrames()); - bytesUntilNextAvSync = 0; + public void playToEndOfStream() throws WriteException { + if (handledEndOfStream || !isInitialized()) { + return; } + + // Drain the buffer processors. + boolean bufferProcessorNeedsEndOfStream = false; + if (drainingBufferProcessorIndex == C.INDEX_UNSET) { + drainingBufferProcessorIndex = passthrough ? bufferProcessors.length : 0; + bufferProcessorNeedsEndOfStream = true; + } + while (drainingBufferProcessorIndex < bufferProcessors.length) { + BufferProcessor bufferProcessor = bufferProcessors[drainingBufferProcessorIndex]; + if (bufferProcessorNeedsEndOfStream) { + bufferProcessor.queueEndOfStream(); + } + processBuffers(C.TIME_UNSET); + if (!bufferProcessor.isEnded()) { + return; + } + bufferProcessorNeedsEndOfStream = true; + drainingBufferProcessorIndex++; + } + + // Finish writing any remaining output to the track. + if (outputBuffer != null) { + writeBuffer(outputBuffer, C.TIME_UNSET); + if (outputBuffer != null) { + return; + } + } + + // Drain the track. + audioTrackUtil.handleEndOfStream(getWrittenFrames()); + bytesUntilNextAvSync = 0; + handledEndOfStream = true; + } + + /** + * Returns whether all buffers passed to {@link #handleBuffer(ByteBuffer, long)} have been + * completely processed and played. + */ + public boolean isEnded() { + return !isInitialized() || (handledEndOfStream && !hasPendingData()); } /** @@ -1003,6 +1047,8 @@ public final class AudioTrack { bufferProcessor.flush(); outputBuffers[i] = bufferProcessor.getOutput(); } + handledEndOfStream = false; + drainingBufferProcessorIndex = C.INDEX_UNSET; avSyncHeader = null; bytesUntilNextAvSync = 0; startMediaTimeState = START_NOT_SET; diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/BufferProcessor.java b/library/src/main/java/com/google/android/exoplayer2/audio/BufferProcessor.java index 0a58785ef2..d75eeb356f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/audio/BufferProcessor.java +++ b/library/src/main/java/com/google/android/exoplayer2/audio/BufferProcessor.java @@ -84,6 +84,15 @@ public interface BufferProcessor { */ void queueInput(ByteBuffer buffer); + /** + * Queues an end of stream signal and begins draining any pending output from this processor. + * After this method has been called, {@link #queueInput(ByteBuffer)} may not be called until + * after the next call to {@link #flush()}. Calling {@link #getOutput()} will return any remaining + * output data. Multiple calls may be required to read all of the remaining output data. + * {@link #isEnded()} will return {@code true} once all remaining output data has been read. + */ + void queueEndOfStream(); + /** * Returns a buffer containing processed output data between its position and limit. The buffer * will always be a direct byte buffer with native byte order. Calling this method invalidates any @@ -93,6 +102,12 @@ public interface BufferProcessor { */ ByteBuffer getOutput(); + /** + * Returns whether this processor will return no more output from {@link #getOutput()} until it + * has been {@link #flush()}ed and more input has been queued. + */ + boolean isEnded(); + /** * Clears any state in preparation for receiving a new stream of buffers. */ diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index dc7cdf42c8..7ab9d9133a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -312,7 +312,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media @Override public boolean isEnded() { - return super.isEnded() && !audioTrack.hasPendingData(); + return super.isEnded() && audioTrack.isEnded(); } @Override @@ -361,8 +361,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } @Override - protected void onOutputStreamEnded() { - audioTrack.handleEndOfStream(); + protected void renderToEndOfStream() throws ExoPlaybackException { + try { + audioTrack.playToEndOfStream(); + } catch (AudioTrack.WriteException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/ResamplingBufferProcessor.java b/library/src/main/java/com/google/android/exoplayer2/audio/ResamplingBufferProcessor.java index 14bd58c3d8..343baf32e3 100644 --- a/library/src/main/java/com/google/android/exoplayer2/audio/ResamplingBufferProcessor.java +++ b/library/src/main/java/com/google/android/exoplayer2/audio/ResamplingBufferProcessor.java @@ -30,6 +30,7 @@ import java.nio.ByteOrder; private int encoding; private ByteBuffer buffer; private ByteBuffer outputBuffer; + private boolean inputEnded; /** * Creates a new buffer processor that converts audio data to {@link C#ENCODING_PCM_16BIT}. @@ -145,15 +146,27 @@ import java.nio.ByteOrder; return outputBuffer; } + @Override + public void queueEndOfStream() { + inputEnded = true; + } + + @SuppressWarnings("ReferenceEquality") + @Override + public boolean isEnded() { + return inputEnded && outputBuffer == EMPTY_BUFFER; + } + @Override public void flush() { outputBuffer = EMPTY_BUFFER; + inputEnded = false; } @Override public void release() { + flush(); buffer = EMPTY_BUFFER; - outputBuffer = EMPTY_BUFFER; } } diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index 3ca8c37e21..5e93aa920c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -178,6 +178,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements @Override public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { if (outputStreamEnded) { + try { + audioTrack.playToEndOfStream(); + } catch (AudioTrack.WriteException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } return; } @@ -280,7 +285,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements outputBuffer.release(); outputBuffer = null; outputStreamEnded = true; - audioTrack.handleEndOfStream(); + audioTrack.playToEndOfStream(); } return false; } @@ -388,7 +393,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements @Override public boolean isEnded() { - return outputStreamEnded && !audioTrack.hasPendingData(); + return outputStreamEnded && audioTrack.isEnded(); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 9baf974b37..cf8d766c0c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -480,6 +480,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { @Override public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { if (outputStreamEnded) { + renderToEndOfStream(); return; } if (format == null) { @@ -787,16 +788,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // Do nothing. } - /** - * Called when the output stream ends, meaning that the last output buffer has been processed and - * the {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag has been propagated through the decoder. - *

- * The default implementation is a no-op. - */ - protected void onOutputStreamEnded() { - // Do nothing. - } - /** * Called immediately before an input buffer is queued into the codec. *

@@ -1010,6 +1001,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { MediaCodec codec, ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, boolean shouldSkip) throws ExoPlaybackException; + /** + * Incrementally renders any remaining output. + *

+ * The default implementation is a no-op. + * + * @throws ExoPlaybackException Thrown if an error occurs rendering remaining output. + */ + protected void renderToEndOfStream() throws ExoPlaybackException { + // Do nothing. + } + /** * Processes an end of stream signal. * @@ -1022,7 +1024,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { maybeInitCodec(); } else { outputStreamEnded = true; - onOutputStreamEnded(); + renderToEndOfStream(); } }