diff --git a/demo/build.gradle b/demo/build.gradle index a91c1ab2ca..c8db67dba5 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -19,7 +19,7 @@ android { defaultConfig { minSdkVersion 16 - targetSdkVersion 19 + targetSdkVersion 21 } buildTypes { release { diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index a9385058aa..f590303052 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -25,7 +25,7 @@ - + - + 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 d61f044b42..bcbb39a1fe 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java @@ -336,7 +336,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM, audioSessionId); checkAudioTrackInitialized(); } - audioTrack.setStereoVolume(volume, volume); + setVolume(volume); if (getState() == TrackRenderer.STATE_STARTED) { audioTrackResumeSystemTimeUs = System.nanoTime() / 1000; audioTrack.play(); @@ -519,7 +519,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { } if (systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) { - audioTimestampSet = audioTimestampCompat.initTimestamp(audioTrack); + audioTimestampSet = audioTimestampCompat.update(audioTrack); if (audioTimestampSet) { // Perform sanity checks on the timestamp. long audioTimestampUs = audioTimestampCompat.getNanoTime() / 1000; @@ -637,42 +637,52 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { } } - // Copy {@code buffer} into {@code temporaryBuffer}. - // TODO: Bypass this copy step on versions of Android where [redacted] is implemented. - if (temporaryBuffer == null || temporaryBuffer.length < bufferInfo.size) { - temporaryBuffer = new byte[bufferInfo.size]; - } - buffer.position(bufferInfo.offset); - buffer.get(temporaryBuffer, 0, bufferInfo.size); - temporaryBufferOffset = 0; temporaryBufferSize = bufferInfo.size; + buffer.position(bufferInfo.offset); + if (Util.SDK_INT < 21) { + // Copy {@code buffer} into {@code temporaryBuffer}. + if (temporaryBuffer == null || temporaryBuffer.length < bufferInfo.size) { + temporaryBuffer = new byte[bufferInfo.size]; + } + buffer.get(temporaryBuffer, 0, bufferInfo.size); + temporaryBufferOffset = 0; + } } if (audioTrack == null) { initAudioTrack(); } - // TODO: Don't bother doing this once [redacted] is fixed. - // Work out how many bytes we can write without the risk of blocking. - int bytesPending = (int) (submittedBytes - getPlaybackHeadPosition() * frameSize); - int bytesToWrite = bufferSize - bytesPending; - - if (bytesToWrite > 0) { - bytesToWrite = Math.min(temporaryBufferSize, bytesToWrite); - audioTrack.write(temporaryBuffer, temporaryBufferOffset, bytesToWrite); - temporaryBufferOffset += bytesToWrite; - temporaryBufferSize -= bytesToWrite; - submittedBytes += bytesToWrite; - if (temporaryBufferSize == 0) { - codec.releaseOutputBuffer(bufferIndex, false); - codecCounters.renderedOutputBufferCount++; - return true; + int bytesWritten = 0; + if (Util.SDK_INT < 21) { + // Work out how many bytes we can write without the risk of blocking. + int bytesPending = (int) (submittedBytes - getPlaybackHeadPosition() * frameSize); + int bytesToWrite = bufferSize - bytesPending; + if (bytesToWrite > 0) { + bytesToWrite = Math.min(temporaryBufferSize, bytesToWrite); + bytesWritten = audioTrack.write(temporaryBuffer, temporaryBufferOffset, bytesToWrite); + temporaryBufferOffset += bytesWritten; } + } else { + bytesWritten = writeNonBlockingV21(audioTrack, buffer, temporaryBufferSize); + } + + temporaryBufferSize -= bytesWritten; + submittedBytes += bytesWritten; + if (temporaryBufferSize == 0) { + codec.releaseOutputBuffer(bufferIndex, false); + codecCounters.renderedOutputBufferCount++; + return true; } return false; } + @TargetApi(21) + private int writeNonBlockingV21(AudioTrack audioTrack, ByteBuffer buffer, int size) { + return audioTrack.write(buffer, size, AudioTrack.WRITE_NON_BLOCKING); + } + /** * {@link AudioTrack#getPlaybackHeadPosition()} returns a value intended to be interpreted as * an unsigned 32 bit integer, which also wraps around periodically. This method returns the @@ -709,10 +719,24 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { private void setVolume(float volume) { this.volume = volume; if (audioTrack != null) { - audioTrack.setStereoVolume(volume, volume); + if (Util.SDK_INT >= 21) { + setVolumeV21(audioTrack, volume); + } else { + setVolumeV3(audioTrack, volume); + } } } + @TargetApi(21) + private static void setVolumeV21(AudioTrack audioTrack, float volume) { + audioTrack.setVolume(volume); + } + + @SuppressWarnings("deprecation") + private static void setVolumeV3(AudioTrack audioTrack, float volume) { + audioTrack.setStereoVolume(volume, volume); + } + private void notifyAudioTrackInitializationError(final AudioTrackInitializationException e) { if (eventHandler != null && eventListener != null) { eventHandler.post(new Runnable() { @@ -732,7 +756,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { /** * Returns true if the audioTimestamp was retrieved from the audioTrack. */ - boolean initTimestamp(AudioTrack audioTrack); + boolean update(AudioTrack audioTrack); long getNanoTime(); @@ -746,7 +770,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { private static final class NoopAudioTimestampCompat implements AudioTimestampCompat { @Override - public boolean initTimestamp(AudioTrack audioTrack) { + public boolean update(AudioTrack audioTrack) { return false; } @@ -778,7 +802,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { } @Override - public boolean initTimestamp(AudioTrack audioTrack) { + public boolean update(AudioTrack audioTrack) { return audioTrack.getTimestamp(audioTimestamp); } 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 a19be59df1..5f912ee52e 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer; import com.google.android.exoplayer.drm.DrmSessionManager; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.TraceUtil; +import com.google.android.exoplayer.util.Util; import android.annotation.TargetApi; import android.media.MediaCodec; @@ -93,7 +94,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { private final int maxDroppedFrameCountToNotify; private Surface surface; - private boolean drawnToSurface; + private boolean reportedDrawnToSurface; private boolean renderedFirstFrame; private long joiningDeadlineUs; private long droppedFrameAccumulationStartTimeMs; @@ -270,7 +271,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { @Override protected void onStopped() { joiningDeadlineUs = -1; - notifyAndResetDroppedFrameCount(); + maybeNotifyDroppedFrameCount(); super.onStopped(); } @@ -303,7 +304,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { return; } this.surface = surface; - this.drawnToSurface = false; + this.reportedDrawnToSurface = false; int state = getState(); if (state == TrackRenderer.STATE_ENABLED || state == TrackRenderer.STATE_STARTED) { releaseCodec(); @@ -370,24 +371,37 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { } if (!renderedFirstFrame) { - renderOutputBuffer(codec, bufferIndex); + renderOutputBufferImmediate(codec, bufferIndex); renderedFirstFrame = true; return true; } - if (getState() == TrackRenderer.STATE_STARTED && earlyUs < 30000) { - if (earlyUs > 11000) { - // We're a little too early to render the frame. Sleep until the frame can be rendered. - // Note: The 11ms threshold was chosen fairly arbitrarily. - try { - // Subtracting 10000 rather than 11000 ensures that the sleep time will be at least 1ms. - Thread.sleep((earlyUs - 10000) / 1000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + if (getState() != TrackRenderer.STATE_STARTED) { + return false; + } + + if (Util.SDK_INT >= 21) { + // Let the underlying framework time the release. + if (earlyUs < 50000) { + renderOutputBufferTimedV21(codec, bufferIndex, System.nanoTime() + (earlyUs * 1000L)); + return true; + } + } else { + // We need to time the release ourselves. + if (earlyUs < 30000) { + if (earlyUs > 11000) { + // We're a little too early to render the frame. Sleep until the frame can be rendered. + // Note: The 11ms threshold was chosen fairly arbitrarily. + try { + // Subtracting 10000 rather than 11000 ensures the sleep time will be at least 1ms. + Thread.sleep((earlyUs - 10000) / 1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + renderOutputBufferImmediate(codec, bufferIndex); + return true; } - renderOutputBuffer(codec, bufferIndex); - return true; } // We're either not playing, or it's not time to render the frame yet. @@ -408,65 +422,84 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { codecCounters.droppedOutputBufferCount++; droppedFrameCount++; if (droppedFrameCount == maxDroppedFrameCountToNotify) { - notifyAndResetDroppedFrameCount(); + maybeNotifyDroppedFrameCount(); } } - private void renderOutputBuffer(MediaCodec codec, int bufferIndex) { - if (lastReportedWidth != currentWidth || lastReportedHeight != currentHeight - || lastReportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) { - lastReportedWidth = currentWidth; - lastReportedHeight = currentHeight; - lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio; - notifyVideoSizeChanged(currentWidth, currentHeight, currentPixelWidthHeightRatio); - } - TraceUtil.beginSection("renderVideoBuffer"); + private void renderOutputBufferImmediate(MediaCodec codec, int bufferIndex) { + maybeNotifyVideoSizeChanged(); + TraceUtil.beginSection("renderVideoBufferImmediate"); codec.releaseOutputBuffer(bufferIndex, true); TraceUtil.endSection(); codecCounters.renderedOutputBufferCount++; - if (!drawnToSurface) { - drawnToSurface = true; - notifyDrawnToSurface(surface); - } + maybeNotifyDrawnToSurface(); } - private void notifyVideoSizeChanged(final int width, final int height, - final float pixelWidthHeightRatio) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onVideoSizeChanged(width, height, pixelWidthHeightRatio); - } - }); - } + @TargetApi(21) + private void renderOutputBufferTimedV21(MediaCodec codec, int bufferIndex, long nanoTime) { + maybeNotifyVideoSizeChanged(); + TraceUtil.beginSection("releaseOutputBufferTimed"); + codec.releaseOutputBuffer(bufferIndex, nanoTime); + TraceUtil.endSection(); + codecCounters.renderedOutputBufferCount++; + maybeNotifyDrawnToSurface(); } - private void notifyDrawnToSurface(final Surface surface) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onDrawnToSurface(surface); - } - }); + private void maybeNotifyVideoSizeChanged() { + if (eventHandler == null || eventListener == null + || (lastReportedWidth == currentWidth && lastReportedHeight == currentHeight + && 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 float currentPixelWidthHeightRatio = this.currentPixelWidthHeightRatio; + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onVideoSizeChanged(currentWidth, currentHeight, currentPixelWidthHeightRatio); + } + }); + // Update the last reported values. + lastReportedWidth = currentWidth; + lastReportedHeight = currentHeight; + lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio; } - private void notifyAndResetDroppedFrameCount() { - if (eventHandler != null && eventListener != null && droppedFrameCount > 0) { - long now = SystemClock.elapsedRealtime(); - final int countToNotify = droppedFrameCount; - final long elapsedToNotify = now - droppedFrameAccumulationStartTimeMs; - droppedFrameCount = 0; - droppedFrameAccumulationStartTimeMs = now; - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onDroppedFrames(countToNotify, elapsedToNotify); - } - }); + private void maybeNotifyDrawnToSurface() { + if (eventHandler == null || eventListener == null || reportedDrawnToSurface) { + return; } + // 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() { + if (eventHandler == null || eventListener == null || droppedFrameCount == 0) { + return; + } + 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; } } diff --git a/library/src/main/java/com/google/android/exoplayer/text/CaptionStyleCompat.java b/library/src/main/java/com/google/android/exoplayer/text/CaptionStyleCompat.java index 60cc8ab129..3e406b5854 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/CaptionStyleCompat.java +++ b/library/src/main/java/com/google/android/exoplayer/text/CaptionStyleCompat.java @@ -104,14 +104,19 @@ public final class CaptionStyleCompat { /** * Creates a {@link CaptionStyleCompat} equivalent to a provided {@link CaptionStyle}. * - * @param style A {@link CaptionStyle}. + * @param captionStyle A {@link CaptionStyle}. * @return The equivalent {@link CaptionStyleCompat}. */ @TargetApi(19) - public static CaptionStyleCompat createFromCaptionStyle(CaptionStyle style) { - int windowColor = Util.SDK_INT >= 21 ? getWindowColorV21(style) : Color.TRANSPARENT; - return new CaptionStyleCompat(style.foregroundColor, style.backgroundColor, windowColor, - style.edgeType, style.edgeColor, style.getTypeface()); + public static CaptionStyleCompat createFromCaptionStyle( + CaptioningManager.CaptionStyle captionStyle) { + if (Util.SDK_INT >= 21) { + return createFromCaptionStyleV21(captionStyle); + } else { + // Note - Any caller must be on at least API level 19 of greater (because CaptionStyle did + // not exist in earlier API levels). + return createFromCaptionStyleV19(captionStyle); + } } /** @@ -132,11 +137,24 @@ public final class CaptionStyleCompat { this.typeface = typeface; } - @SuppressWarnings("unused") + @TargetApi(19) + private static CaptionStyleCompat createFromCaptionStyleV19( + CaptioningManager.CaptionStyle captionStyle) { + return new CaptionStyleCompat( + captionStyle.foregroundColor, captionStyle.backgroundColor, Color.TRANSPARENT, + captionStyle.edgeType, captionStyle.edgeColor, captionStyle.getTypeface()); + } + @TargetApi(21) - private static int getWindowColorV21(CaptioningManager.CaptionStyle captionStyle) { - // TODO: Uncomment when building against API level 21. - return Color.TRANSPARENT; //captionStyle.windowColor; + private static CaptionStyleCompat createFromCaptionStyleV21( + CaptioningManager.CaptionStyle captionStyle) { + return new CaptionStyleCompat( + captionStyle.hasForegroundColor() ? captionStyle.foregroundColor : DEFAULT.foregroundColor, + captionStyle.hasBackgroundColor() ? captionStyle.backgroundColor : DEFAULT.backgroundColor, + captionStyle.hasWindowColor() ? captionStyle.windowColor : DEFAULT.windowColor, + captionStyle.hasEdgeType() ? captionStyle.edgeType : DEFAULT.edgeType, + captionStyle.hasEdgeColor() ? captionStyle.edgeColor : DEFAULT.edgeColor, + captionStyle.getTypeface()); } } diff --git a/library/src/main/project.properties b/library/src/main/project.properties index 8e4bc5fdce..b756f4487f 100644 --- a/library/src/main/project.properties +++ b/library/src/main/project.properties @@ -8,5 +8,5 @@ # project structure. # Project target. -target=android-19 +target=android-21 android.library=true