- * This renderer returns {@link TrackRenderer#STATE_IGNORE} from {@link #doPrepare()} in order to - * request that it should be ignored. {@link IllegalStateException} is thrown from all methods that - * are documented to indicate that they should not be invoked unless the renderer is prepared. + * This renderer returns {@link TrackRenderer#STATE_IGNORE} from {@link #doPrepare(long)} in order + * to request that it should be ignored. {@link IllegalStateException} is thrown from all methods + * that are documented to indicate that they should not be invoked unless the renderer is prepared. */ public class DummyTrackRenderer extends TrackRenderer { @Override - protected int doPrepare() throws ExoPlaybackException { + protected int doPrepare(long positionUs) throws ExoPlaybackException { return STATE_IGNORE; } diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java index b6ec3c12d1..1eecc358e1 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java @@ -101,7 +101,7 @@ public interface ExoPlayer { * The default minimum duration of data that must be buffered for playback to start or resume * following a user action such as a seek. */ - public static final int DEFAULT_MIN_BUFFER_MS = 500; + public static final int DEFAULT_MIN_BUFFER_MS = 2500; /** * The default minimum duration of data that must be buffered for playback to resume diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java index bf45253b52..2c87cd7133 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java @@ -264,7 +264,7 @@ import java.util.List; boolean prepared = true; for (int i = 0; i < renderers.length; i++) { if (renderers[i].getState() == TrackRenderer.STATE_UNPREPARED) { - int state = renderers[i].prepare(); + int state = renderers[i].prepare(positionUs); if (state == TrackRenderer.STATE_UNPREPARED) { prepared = false; } diff --git a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java index 0d3fc61437..8eab665aa8 100644 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java @@ -128,7 +128,7 @@ public final class FrameworkSampleSource implements SampleSource { } @Override - public boolean prepare() throws IOException { + public boolean prepare(long positionUs) throws IOException { if (!prepared) { extractor = new MediaExtractor(); if (context != null) { 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 ba98373319..48b54db597 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java @@ -21,9 +21,7 @@ import com.google.android.exoplayer.drm.DrmSessionManager; import com.google.android.exoplayer.util.MimeTypes; import android.annotation.TargetApi; -import android.media.AudioFormat; import android.media.MediaCodec; -import android.media.MediaFormat; import android.media.audiofx.Virtualizer; import android.os.Handler; @@ -71,7 +69,6 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { private final EventListener eventListener; private final AudioTrack audioTrack; - private final int encoding; private int audioSessionId; private long currentPositionUs; @@ -124,50 +121,27 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { */ public MediaCodecAudioTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener) { - this(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener, - AudioFormat.ENCODING_PCM_16BIT); - } - - /** - * @param source The upstream source from which the renderer obtains samples. - * @param drmSessionManager For use with encrypted content. May be null if support for encrypted - * content is not required. - * @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 - * begin in parallel with key acquisision. This parameter specifies whether the renderer is - * permitted to play clear regions of encrypted media files before {@code drmSessionManager} - * 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. - * @param encoding One of the {@code AudioFormat.ENCODING_*} constants specifying the audio - * encoding. - */ - public MediaCodecAudioTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager, - boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener, - int encoding) { super(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener); this.eventListener = eventListener; this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET; this.audioTrack = new AudioTrack(); - this.encoding = encoding; } @Override protected DecoderInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) throws DecoderQueryException { - if (encoding == AudioFormat.ENCODING_AC3 || encoding == AudioFormat.ENCODING_E_AC3) { + if (MimeTypes.isPassthroughAudio(mimeType)) { return new DecoderInfo(RAW_DECODER_NAME, true); } return super.getDecoderInfo(mimeType, requiresSecureDecoder); } @Override - protected void configureCodec(MediaCodec codec, String codecName, MediaFormat format, - android.media.MediaCrypto crypto) { + protected void configureCodec(MediaCodec codec, String codecName, + android.media.MediaFormat format, android.media.MediaCrypto crypto) { if (RAW_DECODER_NAME.equals(codecName)) { // Override the MIME type used to configure the codec if we are using a passthrough decoder. - String mimeType = format.getString(MediaFormat.KEY_MIME); + String mimeType = format.getString(android.media.MediaFormat.KEY_MIME); format.setString(android.media.MediaFormat.KEY_MIME, MimeTypes.AUDIO_RAW); codec.configure(format, null, crypto, 0); format.setString(android.media.MediaFormat.KEY_MIME, mimeType); @@ -193,8 +167,13 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer { } @Override - protected void onOutputFormatChanged(MediaFormat format) { - audioTrack.reconfigure(format, encoding, 0); + protected void onOutputFormatChanged(MediaFormat inputFormat, + android.media.MediaFormat outputFormat) { + if (MimeTypes.isPassthroughAudio(inputFormat.mimeType)) { + audioTrack.reconfigure(inputFormat.getFrameworkMediaFormatV16()); + } else { + audioTrack.reconfigure(outputFormat); + } } /** diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index 364a3f4f79..6f8dcbd6be 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -243,9 +243,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { } @Override - protected int doPrepare() throws ExoPlaybackException { + protected int doPrepare(long positionUs) throws ExoPlaybackException { try { - boolean sourcePrepared = source.prepare(); + boolean sourcePrepared = source.prepare(positionUs); if (!sourcePrepared) { return TrackRenderer.STATE_UNPREPARED; } @@ -742,9 +742,11 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { *
* The default implementation is a no-op.
*
- * @param format The new output format.
+ * @param inputFormat The format of media input to the codec.
+ * @param outputFormat The new output format.
*/
- protected void onOutputFormatChanged(android.media.MediaFormat format) {
+ protected void onOutputFormatChanged(MediaFormat inputFormat,
+ android.media.MediaFormat outputFormat) {
// Do nothing.
}
@@ -818,7 +820,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- onOutputFormatChanged(codec.getOutputFormat());
+ onOutputFormatChanged(format, codec.getOutputFormat());
codecCounters.outputFormatChangedCount++;
return true;
} else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
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 b843901543..49949f91a2 100644
--- a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
+++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
@@ -381,15 +381,17 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
- protected void onOutputFormatChanged(android.media.MediaFormat format) {
- boolean hasCrop = format.containsKey(KEY_CROP_RIGHT) && format.containsKey(KEY_CROP_LEFT)
- && format.containsKey(KEY_CROP_BOTTOM) && format.containsKey(KEY_CROP_TOP);
+ protected void onOutputFormatChanged(MediaFormat inputFormat,
+ android.media.MediaFormat outputFormat) {
+ boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT)
+ && outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM)
+ && outputFormat.containsKey(KEY_CROP_TOP);
currentWidth = hasCrop
- ? format.getInteger(KEY_CROP_RIGHT) - format.getInteger(KEY_CROP_LEFT) + 1
- : format.getInteger(android.media.MediaFormat.KEY_WIDTH);
+ ? outputFormat.getInteger(KEY_CROP_RIGHT) - outputFormat.getInteger(KEY_CROP_LEFT) + 1
+ : outputFormat.getInteger(android.media.MediaFormat.KEY_WIDTH);
currentHeight = hasCrop
- ? format.getInteger(KEY_CROP_BOTTOM) - format.getInteger(KEY_CROP_TOP) + 1
- : format.getInteger(android.media.MediaFormat.KEY_HEIGHT);
+ ? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1
+ : outputFormat.getInteger(android.media.MediaFormat.KEY_HEIGHT);
}
@Override
diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSource.java b/library/src/main/java/com/google/android/exoplayer/SampleSource.java
index 9a3d40819b..b7a93e3f65 100644
--- a/library/src/main/java/com/google/android/exoplayer/SampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/SampleSource.java
@@ -57,10 +57,11 @@ public interface SampleSource {
* and formats). If insufficient data is available then the call will return {@code false} rather
* than block. The method can be called repeatedly until the return value indicates success.
*
+ * @param positionUs The player's current playback position.
* @return True if the source was prepared successfully, false otherwise.
* @throws IOException If an error occurred preparing the source.
*/
- public boolean prepare() throws IOException;
+ public boolean prepare(long positionUs) throws IOException;
/**
* Returns the number of tracks exposed by the source.
diff --git a/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java
index 8d5534e0b7..b28a2fbc9d 100644
--- a/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java
+++ b/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java
@@ -108,11 +108,12 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* Prepares the renderer. This method is non-blocking, and hence it may be necessary to call it
* more than once in order to transition the renderer into the prepared state.
*
+ * @param positionUs The player's current playback position.
* @return The current state (one of the STATE_* constants), for convenience.
*/
- /* package */ final int prepare() throws ExoPlaybackException {
+ /* package */ final int prepare(long positionUs) throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_UNPREPARED);
- state = doPrepare();
+ state = doPrepare(positionUs);
Assertions.checkState(state == TrackRenderer.STATE_UNPREPARED ||
state == TrackRenderer.STATE_PREPARED ||
state == TrackRenderer.STATE_IGNORE);
@@ -127,11 +128,12 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* This method should return quickly, and should not block if the renderer is currently unable to
* make any useful progress.
*
+ * @param positionUs The player's current playback position.
* @return The new state of the renderer. One of {@link #STATE_UNPREPARED},
* {@link #STATE_PREPARED} and {@link #STATE_IGNORE}.
* @throws ExoPlaybackException If an error occurs.
*/
- protected abstract int doPrepare() throws ExoPlaybackException;
+ protected abstract int doPrepare(long positionUs) throws ExoPlaybackException;
/**
* Enable the renderer.
diff --git a/library/src/main/java/com/google/android/exoplayer/audio/AudioTrack.java b/library/src/main/java/com/google/android/exoplayer/audio/AudioTrack.java
index 30f8f0dbf7..96271a2774 100644
--- a/library/src/main/java/com/google/android/exoplayer/audio/AudioTrack.java
+++ b/library/src/main/java/com/google/android/exoplayer/audio/AudioTrack.java
@@ -18,6 +18,7 @@ package com.google.android.exoplayer.audio;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.util.Ac3Util;
import com.google.android.exoplayer.util.Assertions;
+import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
@@ -315,24 +316,21 @@ public final class AudioTrack {
}
/**
- * Reconfigures the audio track to play back media in {@code format}. The encoding is assumed to
- * be {@link AudioFormat#ENCODING_PCM_16BIT}.
+ * Reconfigures the audio track to play back media in {@code format}, inferring a buffer size from
+ * the format.
*/
public void reconfigure(MediaFormat format) {
- reconfigure(format, AudioFormat.ENCODING_PCM_16BIT, 0);
+ reconfigure(format, 0);
}
/**
- * Reconfigures the audio track to play back media in {@code format}. Buffers passed to
- * {@link #handleBuffer} must use the specified {@code encoding}, which should be a constant from
- * {@link AudioFormat}.
+ * Reconfigures the audio track to play back media in {@code format}.
*
* @param format Specifies the channel count and sample rate to play back.
- * @param encoding The format in which audio is represented.
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to use a
* size inferred from the format.
*/
- public void reconfigure(MediaFormat format, int encoding, int specifiedBufferSize) {
+ public void reconfigure(MediaFormat format, int specifiedBufferSize) {
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int channelConfig;
switch (channelCount) {
@@ -353,8 +351,10 @@ public final class AudioTrack {
}
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+ String mimeType = format.getString(MediaFormat.KEY_MIME);
// TODO: Does channelConfig determine channelCount?
+ int encoding = MimeTypes.getEncodingForMimeType(mimeType);
boolean isAc3 = encoding == C.ENCODING_AC3 || encoding == C.ENCODING_E_AC3;
if (isInitialized() && this.sampleRate == sampleRate && this.channelConfig == channelConfig
&& !this.isAc3 && !isAc3) {
@@ -423,10 +423,21 @@ public final class AudioTrack {
return RESULT_BUFFER_CONSUMED;
}
- // As a workaround for an issue where an an AC-3 audio track continues to play data written
- // while it is paused, stop writing so its buffer empties. See [Internal: b/18899620].
- if (isAc3 && audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED) {
- return 0;
+ // Workarounds for issues with AC-3 passthrough AudioTracks on API versions 21/22:
+ if (Util.SDK_INT <= 22 && isAc3) {
+ // An AC-3 audio track continues to play data written while it is paused. Stop writing so its
+ // buffer empties. See [Internal: b/18899620].
+ if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED) {
+ return 0;
+ }
+
+ // A new AC-3 audio track's playback position continues to increase from the old track's
+ // position for a short time after is has been released. Avoid writing data until the playback
+ // head position actually returns to zero.
+ if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_STOPPED
+ && audioTrackUtil.getPlaybackHeadPosition() != 0) {
+ return 0;
+ }
}
int result = 0;
@@ -637,7 +648,8 @@ public final class AudioTrack {
}
if (systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) {
- audioTimestampSet = audioTrackUtil.updateTimestamp();
+ // Don't use AudioTrack.getTimestamp() on AC-3 tracks, as it gives an incorrect timestamp.
+ audioTimestampSet = !isAc3 && audioTrackUtil.updateTimestamp();
if (audioTimestampSet) {
// Perform sanity checks on the timestamp.
long audioTimestampUs = audioTrackUtil.getTimestampNanoTime() / 1000;
@@ -739,7 +751,7 @@ public final class AudioTrack {
private static class AudioTrackUtil {
protected android.media.AudioTrack audioTrack;
- private boolean enablePassthroughWorkaround;
+ private boolean isPassthrough;
private int sampleRate;
private long lastRawPlaybackHeadPosition;
private long rawPlaybackHeadWrapCount;
@@ -749,14 +761,11 @@ public final class AudioTrack {
* Reconfigures the audio track utility helper to use the specified {@code audioTrack}.
*
* @param audioTrack The audio track to wrap.
- * @param enablePassthroughWorkaround Whether to work around an issue where the playback head
- * position jumps back to zero on a paused passthrough/direct audio track. See
- * [Internal: b/19187573].
+ * @param isPassthrough Whether the audio track is used for passthrough (e.g. AC-3) playback.
*/
- public void reconfigure(android.media.AudioTrack audioTrack,
- boolean enablePassthroughWorkaround) {
+ public void reconfigure(android.media.AudioTrack audioTrack, boolean isPassthrough) {
this.audioTrack = audioTrack;
- this.enablePassthroughWorkaround = enablePassthroughWorkaround;
+ this.isPassthrough = isPassthrough;
lastRawPlaybackHeadPosition = 0;
rawPlaybackHeadWrapCount = 0;
passthroughWorkaroundPauseOffset = 0;
@@ -767,14 +776,14 @@ public final class AudioTrack {
/**
* Returns whether the audio track should behave as though it has pending data. This is to work
- * around an issue where AC-3 audio tracks can't be paused, so we empty their buffers when
- * paused. In this case, they should still behave as if they have pending data, otherwise
- * writing will never resume.
+ * around an issue on platform API versions 21/22 where AC-3 audio tracks can't be paused, so we
+ * empty their buffers when paused. In this case, they should still behave as if they have
+ * pending data, otherwise writing will never resume.
*
* @see #handleBuffer
*/
public boolean overrideHasPendingData() {
- return enablePassthroughWorkaround
+ return Util.SDK_INT <= 22 && isPassthrough
&& audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED
&& audioTrack.getPlaybackHeadPosition() == 0;
}
@@ -790,8 +799,16 @@ public final class AudioTrack {
*/
public long getPlaybackHeadPosition() {
long rawPlaybackHeadPosition = 0xFFFFFFFFL & audioTrack.getPlaybackHeadPosition();
- if (enablePassthroughWorkaround) {
- if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED
+ if (Util.SDK_INT <= 22 && isPassthrough) {
+ // Work around issues with passthrough/direct AudioTracks on platform API versions 21/22:
+ // - After resetting, the new AudioTrack's playback position continues to increase for a
+ // short time from the old AudioTrack's position, while in the PLAYSTATE_STOPPED state.
+ // - The playback head position jumps back to zero on paused passthrough/direct audio
+ // tracks. See [Internal: b/19187573].
+ if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_STOPPED) {
+ // Prevent detecting a wrapped position.
+ lastRawPlaybackHeadPosition = rawPlaybackHeadPosition;
+ } else if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED
&& rawPlaybackHeadPosition == 0) {
passthroughWorkaroundPauseOffset = lastRawPlaybackHeadPosition;
}
@@ -868,9 +885,8 @@ public final class AudioTrack {
}
@Override
- public void reconfigure(android.media.AudioTrack audioTrack,
- boolean enablePassthroughWorkaround) {
- super.reconfigure(audioTrack, enablePassthroughWorkaround);
+ public void reconfigure(android.media.AudioTrack audioTrack, boolean isPassthrough) {
+ super.reconfigure(audioTrack, isPassthrough);
rawTimestampFramePositionWrapCount = 0;
lastRawTimestampFramePosition = 0;
lastTimestampFramePosition = 0;
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
index bf3f26e4b1..a023a31244 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java
@@ -87,7 +87,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
private long currentLoadStartTimeMs;
private MediaFormat downstreamMediaFormat;
- private volatile Format downstreamFormat;
+ private Format downstreamFormat;
public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl,
int bufferSizeContribution, boolean frameAccurateSeeking) {
@@ -120,18 +120,8 @@ public class ChunkSampleSource implements SampleSource, Loader.Callback {
pendingResetPositionUs = NO_RESET_PENDING;
}
- /**
- * Exposes the current downstream format for debugging purposes. Can be called from any thread.
- *
- * @return The current downstream format.
- */
- @Deprecated
- public Format getFormat() {
- return downstreamFormat;
- }
-
@Override
- public boolean prepare() {
+ public boolean prepare(long positionUs) {
Assertions.checkState(state == STATE_UNPREPARED);
loader = new Loader("Loader:" + chunkSource.getTrackInfo().mimeType);
state = STATE_PREPARED;
diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java b/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java
index 2a1cc561bb..7c88ff68a0 100644
--- a/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java
+++ b/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java
@@ -84,7 +84,7 @@ public interface FormatEvaluator {
/**
* Always selects the first format.
*/
- public static class FixedEvaluator implements FormatEvaluator {
+ public static final class FixedEvaluator implements FormatEvaluator {
@Override
public void enable() {
@@ -107,7 +107,7 @@ public interface FormatEvaluator {
/**
* Selects randomly between the available formats.
*/
- public static class RandomEvaluator implements FormatEvaluator {
+ public static final class RandomEvaluator implements FormatEvaluator {
private final Random random;
@@ -145,7 +145,7 @@ public interface FormatEvaluator {
* reference implementation only. It is recommended that application developers implement their
* own adaptive evaluator to more precisely suit their use case.
*/
- public static class AdaptiveEvaluator implements FormatEvaluator {
+ public static final class AdaptiveEvaluator implements FormatEvaluator {
public static final int DEFAULT_MAX_INITIAL_BITRATE = 800000;
@@ -259,8 +259,9 @@ public interface FormatEvaluator {
/**
* Compute the ideal format ignoring buffer health.
*/
- protected Format determineIdealFormat(Format[] formats, long bitrateEstimate) {
- long effectiveBitrate = computeEffectiveBitrateEstimate(bitrateEstimate);
+ private Format determineIdealFormat(Format[] formats, long bitrateEstimate) {
+ long effectiveBitrate = bitrateEstimate == BandwidthMeter.NO_ESTIMATE
+ ? maxInitialBitrate : (long) (bitrateEstimate * bandwidthFraction);
for (int i = 0; i < formats.length; i++) {
Format format = formats[i];
if (format.bitrate <= effectiveBitrate) {
@@ -271,14 +272,6 @@ public interface FormatEvaluator {
return formats[formats.length - 1];
}
- /**
- * Apply overhead factor, or default value in absence of estimate.
- */
- protected long computeEffectiveBitrateEstimate(long bitrateEstimate) {
- return bitrateEstimate == BandwidthMeter.NO_ESTIMATE
- ? maxInitialBitrate : (long) (bitrateEstimate * bandwidthFraction);
- }
-
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
index 515a94716a..226c1237c3 100644
--- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java
@@ -419,7 +419,9 @@ public class DashChunkSource implements ChunkSource {
(ChunkIndex) initializationChunk.getSeekMap(),
initializationChunk.dataSpec.uri.toString());
}
- if (initializationChunk.hasDrmInitData()) {
+ // The null check avoids overwriting drmInitData obtained from the manifest with drmInitData
+ // obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
+ if (drmInitData == null && initializationChunk.hasDrmInitData()) {
drmInitData = initializationChunk.getDrmInitData();
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
index 6b6664793c..e509ffeefe 100644
--- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
+++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java
@@ -24,10 +24,12 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
+import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.UriUtil;
import com.google.android.exoplayer.util.Util;
import android.text.TextUtils;
+import android.util.Base64;
import org.xml.sax.helpers.DefaultHandler;
import org.xmlpull.v1.XmlPullParser;
@@ -41,6 +43,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -270,11 +273,27 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
protected ContentProtection parseContentProtection(XmlPullParser xpp)
throws XmlPullParserException, IOException {
String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri");
- return buildContentProtection(schemeIdUri);
+ UUID uuid = null;
+ byte[] data = null;
+ do {
+ xpp.next();
+ // The cenc:pssh element is defined in 23001-7:2015
+ if (isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) {
+ byte[] decodedData = Base64.decode(xpp.getText(), Base64.DEFAULT);
+ ParsableByteArray psshAtom = new ParsableByteArray(decodedData);
+ psshAtom.skipBytes(12);
+ uuid = new UUID(psshAtom.readLong(), psshAtom.readLong());
+ int dataSize = psshAtom.readInt();
+ data = new byte[dataSize];
+ psshAtom.readBytes(data, 0, dataSize);
+ }
+ } while (!isEndTag(xpp, "ContentProtection"));
+
+ return buildContentProtection(schemeIdUri, uuid, data);
}
- protected ContentProtection buildContentProtection(String schemeIdUri) {
- return new ContentProtection(schemeIdUri, null, null);
+ protected ContentProtection buildContentProtection(String schemeIdUri, UUID uuid, byte[] data) {
+ return new ContentProtection(schemeIdUri, uuid, data);
}
/**
diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
index 59bf9d113c..b84401eb76 100644
--- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
+++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java
@@ -72,6 +72,7 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
private boolean prepared;
private int enabledTrackCount;
private TrackInfo[] trackInfos;
+ private long maxTrackDurationUs;
private boolean[] pendingMediaFormat;
private boolean[] pendingDiscontinuities;
private boolean[] trackEnabledStates;
@@ -137,7 +138,7 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
}
@Override
- public boolean prepare() throws IOException {
+ public boolean prepare(long positionUs) throws IOException {
if (prepared) {
return true;
}
@@ -156,9 +157,13 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
pendingDiscontinuities = new boolean[trackCount];
pendingMediaFormat = new boolean[trackCount];
trackInfos = new TrackInfo[trackCount];
+ maxTrackDurationUs = C.UNKNOWN_TIME_US;
for (int i = 0; i < trackCount; i++) {
MediaFormat format = sampleQueues.valueAt(i).getFormat();
trackInfos[i] = new TrackInfo(format.mimeType, format.durationUs);
+ if (format.durationUs != C.UNKNOWN_TIME_US && format.durationUs > maxTrackDurationUs) {
+ maxTrackDurationUs = format.durationUs;
+ }
}
prepared = true;
return true;
@@ -448,6 +453,11 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
loadable = createLoadableFromStart();
} else {
Assertions.checkState(isPendingReset());
+ if (maxTrackDurationUs != C.UNKNOWN_TIME_US && pendingResetPositionUs >= maxTrackDurationUs) {
+ loadingFinished = true;
+ pendingResetPositionUs = NO_RESET_PENDING;
+ return;
+ }
loadable = createLoadableFromPositionUs(pendingResetPositionUs);
pendingResetPositionUs = NO_RESET_PENDING;
}
diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java
index 25fd30f89e..bbc3ae4c8a 100644
--- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java
+++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java
@@ -137,7 +137,7 @@ import com.google.android.exoplayer.util.Util;
}
long position = (long) ((1f / 256) * fx * sizeBytes) + firstFramePosition;
- return inputLength != C.LENGTH_UNBOUNDED ? Math.min(position, inputLength) : position;
+ return inputLength != C.LENGTH_UNBOUNDED ? Math.min(position, inputLength - 1) : position;
}
@Override
diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java
index 2f6fb9fc22..42c3f2e70e 100644
--- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java
+++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java
@@ -534,8 +534,18 @@ import java.util.List;
childPosition += childAtomSize;
}
- out.mediaFormat = MediaFormat.createAudioFormat(
- MimeTypes.AUDIO_AAC, sampleSize, durationUs, channelCount, sampleRate,
+ // Set the MIME type for ac-3/ec-3 atoms even if the dac3/dec3 child atom is missing.
+ String mimeType;
+ if (atomType == Atom.TYPE_ac_3) {
+ mimeType = MimeTypes.AUDIO_AC3;
+ } else if (atomType == Atom.TYPE_ec_3) {
+ mimeType = MimeTypes.AUDIO_EC3;
+ } else {
+ mimeType = MimeTypes.AUDIO_AAC;
+ }
+
+ out.mediaFormat = MediaFormat.createAudioFormat(mimeType, sampleSize, durationUs, channelCount,
+ sampleRate,
initializationData == null ? null : Collections.singletonList(initializationData));
}
diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java
index cde096dfad..670f3ea8ec 100644
--- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java
+++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java
@@ -16,6 +16,7 @@
package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
+import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.ExtractorOutput;
@@ -43,7 +44,7 @@ public final class TsExtractor implements Extractor, SeekMap {
private static final int TS_STREAM_TYPE_AAC = 0x0F;
private static final int TS_STREAM_TYPE_ATSC_AC3 = 0x81;
- private static final int TS_STREAM_TYPE_DVB_AC3 = 0x06;
+ private static final int TS_STREAM_TYPE_ATSC_E_AC3 = 0x87;
private static final int TS_STREAM_TYPE_H264 = 0x1B;
private static final int TS_STREAM_TYPE_ID3 = 0x15;
private static final int TS_STREAM_TYPE_EIA608 = 0x100; // 0xFF + 1
@@ -52,6 +53,7 @@ public final class TsExtractor implements Extractor, SeekMap {
private final ParsableByteArray tsPacketBuffer;
private final SparseBooleanArray streamTypes;
+ private final SparseBooleanArray allowedPassthroughStreamTypes;
private final SparseArray
+ * By default this implementation will not follow cross-protocol redirects (i.e. redirects from
+ * HTTP to HTTPS or vice versa). Cross-protocol redirects can be enabled by using the
+ * {@link #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean)}
+ * constructor and passing {@code true} as the final argument.
*/
public class DefaultHttpDataSource implements HttpDataSource {
+ /**
+ * The default connection timeout, in milliseconds.
+ */
public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
+ /**
+ * The default read timeout, in milliseconds.
+ */
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
+ private static final int MAX_REDIRECTS = 20; // Same limit as okhttp.
private static final String TAG = "HttpDataSource";
private static final Pattern CONTENT_RANGE_HEADER =
Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$");
private static final AtomicReference
+ * The constructed instance will not follow cross-protocol redirects (i.e. redirects from HTTP to
+ * HTTPS or vice versa) when fetching remote data. Cross-protocol redirects can be enabled by
+ * using the {@link #DefaultUriDataSource(String, TransferListener, boolean)} constructor and
+ * passing {@code true} as the final argument.
*
* @param userAgent The User-Agent string that should be used when requesting remote data.
* @param transferListener An optional listener.
*/
public DefaultUriDataSource(String userAgent, TransferListener transferListener) {
+ this(userAgent, transferListener, false);
+ }
+
+ /**
+ * Constructs a new data source that delegates to a {@link FileDataSource} for file URIs and a
+ * {@link DefaultHttpDataSource} for other URIs.
+ *
+ * @param userAgent The User-Agent string that should be used when requesting remote data.
+ * @param transferListener An optional listener.
+ * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
+ * to HTTPS and vice versa) are enabled when fetching remote data..
+ */
+ public DefaultUriDataSource(String userAgent, TransferListener transferListener,
+ boolean allowCrossProtocolRedirects) {
this(new FileDataSource(transferListener),
- new DefaultHttpDataSource(userAgent, null, transferListener));
+ new DefaultHttpDataSource(userAgent, null, transferListener,
+ DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
+ DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, allowCrossProtocolRedirects));
}
/**
diff --git a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
index 8ed514174a..3a6ea01c6f 100644
--- a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
+++ b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
@@ -15,6 +15,11 @@
*/
package com.google.android.exoplayer.util;
+import com.google.android.exoplayer.C;
+import com.google.android.exoplayer.audio.AudioCapabilities;
+
+import android.media.AudioFormat;
+
/**
* Defines common MIME types and helper methods.
*/
@@ -119,4 +124,37 @@ public class MimeTypes {
return mimeType.equals(APPLICATION_TTML);
}
+ /**
+ * Returns the output audio encoding that will result from processing input in {@code mimeType}.
+ * For non-passthrough audio formats, this is always {@link AudioFormat#ENCODING_PCM_16BIT}. For
+ * passthrough formats it will be one of {@link AudioFormat}'s other {@code ENCODING_*} constants.
+ * For non-audio formats, {@link AudioFormat#ENCODING_INVALID} will be returned.
+ *
+ * @param mimeType The MIME type of media that will be decoded (or passed through).
+ * @return The corresponding {@link AudioFormat} encoding.
+ */
+ public static int getEncodingForMimeType(String mimeType) {
+ if (AUDIO_AC3.equals(mimeType)) {
+ return C.ENCODING_AC3;
+ }
+ if (AUDIO_EC3.equals(mimeType)) {
+ return C.ENCODING_E_AC3;
+ }
+
+ // All other audio formats will be decoded to 16-bit PCM.
+ return isAudio(mimeType) ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_INVALID;
+ }
+
+ /**
+ * Returns whether the specified {@code mimeType} represents audio that can be played via
+ * passthrough if the device supports it.
+ *
+ * @param mimeType The MIME type of input media.
+ * @return Whether the audio can be played via passthrough. If this method returns {@code true},
+ * it is still necessary to check the {@link AudioCapabilities} for device support.
+ */
+ public static boolean isPassthroughAudio(String mimeType) {
+ return AUDIO_AC3.equals(mimeType) || AUDIO_EC3.equals(mimeType);
+ }
+
}
diff --git a/library/src/main/project.properties b/library/src/main/project.properties
index b756f4487f..95228a4fc6 100644
--- a/library/src/main/project.properties
+++ b/library/src/main/project.properties
@@ -8,5 +8,5 @@
# project structure.
# Project target.
-target=android-21
+target=android-22
android.library=true
diff --git a/library/src/test/AndroidManifest.xml b/library/src/test/AndroidManifest.xml
index 517161f3b9..71bb5c3a66 100644
--- a/library/src/test/AndroidManifest.xml
+++ b/library/src/test/AndroidManifest.xml
@@ -17,7 +17,7 @@