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