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 5e28f36d1b..7904977dee 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -174,7 +174,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { this.eventHandler = eventHandler; this.eventListener = eventListener; codecCounters = new CodecCounters(); - sampleHolder = new SampleHolder(false); + sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED); formatHolder = new MediaFormatHolder(); decodeOnlyPresentationTimestamps = new HashSet(); outputBufferInfo = new MediaCodec.BufferInfo(); diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java index 31ec2ae9f1..9da62a59d2 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java @@ -76,7 +76,7 @@ public class MediaCodecUtil { for (int i = 0; i < numberOfCodecs; i++) { MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); String codecName = info.getName(); - if (!info.isEncoder() && isOmxCodec(codecName)) { + if (!info.isEncoder() && codecName.startsWith("OMX.") && !codecName.endsWith(".secure")) { String[] supportedTypes = info.getSupportedTypes(); for (int j = 0; j < supportedTypes.length; j++) { String supportedType = supportedTypes[j]; @@ -91,10 +91,6 @@ public class MediaCodecUtil { return null; } - private static boolean isOmxCodec(String name) { - return name.startsWith("OMX."); - } - private static boolean isAdaptive(CodecCapabilities capabilities) { if (Util.SDK_INT >= 19) { return isAdaptiveV19(capabilities); diff --git a/library/src/main/java/com/google/android/exoplayer/SampleHolder.java b/library/src/main/java/com/google/android/exoplayer/SampleHolder.java index 6518b06ac5..43308bc40b 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleHolder.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleHolder.java @@ -23,10 +23,19 @@ import java.nio.ByteBuffer; public final class SampleHolder { /** - * Whether a {@link SampleSource} is permitted to replace {@link #data} if its current value is - * null or of insufficient size to hold the sample. + * Disallows buffer replacement. */ - public final boolean allowDataBufferReplacement; + public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0; + + /** + * Allows buffer replacement using {@link ByteBuffer#allocate(int)}. + */ + public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1; + + /** + * Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}. + */ + public static final int BUFFER_REPLACEMENT_MODE_DIRECT = 2; public final CryptoInfo cryptoInfo; @@ -57,12 +66,34 @@ public final class SampleHolder { */ public boolean decodeOnly; + private final int bufferReplacementMode; + /** - * @param allowDataBufferReplacement See {@link #allowDataBufferReplacement}. + * @param bufferReplacementMode Determines the behavior of {@link #replaceBuffer(int)}. One of + * {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and + * {@link #BUFFER_REPLACEMENT_MODE_DIRECT}. */ - public SampleHolder(boolean allowDataBufferReplacement) { + public SampleHolder(int bufferReplacementMode) { this.cryptoInfo = new CryptoInfo(); - this.allowDataBufferReplacement = allowDataBufferReplacement; + this.bufferReplacementMode = bufferReplacementMode; + } + + /** + * Attempts to replace {@link #data} with a {@link ByteBuffer} of the specified capacity. + * + * @param capacity The capacity of the replacement buffer, in bytes. + * @return True if the buffer was replaced. False otherwise. + */ + public boolean replaceBuffer(int capacity) { + switch (bufferReplacementMode) { + case BUFFER_REPLACEMENT_MODE_NORMAL: + data = ByteBuffer.allocate(capacity); + return true; + case BUFFER_REPLACEMENT_MODE_DIRECT: + data = ByteBuffer.allocateDirect(capacity); + return true; + } + return false; } } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java index e0b9f91ad0..f097d9ee32 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java @@ -22,7 +22,6 @@ import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.util.Assertions; -import java.nio.ByteBuffer; import java.util.Map; import java.util.UUID; @@ -97,9 +96,8 @@ public class SingleSampleMediaChunk extends MediaChunk { if (headerData != null) { sampleSize += headerData.length; } - if (holder.allowDataBufferReplacement && - (holder.data == null || holder.data.capacity() < sampleSize)) { - holder.data = ByteBuffer.allocate(sampleSize); + if (holder.data == null || holder.data.capacity() < sampleSize) { + holder.replaceBuffer(sampleSize); } int bytesRead; if (holder.data != null) { diff --git a/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java index 0fc1e1b200..e6bf68f30a 100644 --- a/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/parser/mp4/FragmentedMp4Extractor.java @@ -751,9 +751,12 @@ public final class FragmentedMp4Extractor implements Extractor { parseSenc(senc.data, out); } - LeafAtom uuid = traf.getLeafAtomOfType(Atom.TYPE_uuid); - if (uuid != null) { - parseUuid(uuid.data, out, extendedTypeScratch); + int childrenSize = traf.children.size(); + for (int i = 0; i < childrenSize; i++) { + Atom atom = traf.children.get(i); + if (atom.type == Atom.TYPE_uuid) { + parseUuid(((LeafAtom) atom).data, out, extendedTypeScratch); + } } } @@ -1066,21 +1069,20 @@ public final class FragmentedMp4Extractor implements Extractor { if (out == null) { return RESULT_NEED_SAMPLE_HOLDER; } - ByteBuffer outputData = out.data; out.timeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L; out.flags = 0; if (fragmentRun.sampleIsSyncFrameTable[sampleIndex]) { out.flags |= MediaExtractor.SAMPLE_FLAG_SYNC; lastSyncSampleIndex = sampleIndex; } - if (out.allowDataBufferReplacement && (out.data == null || out.data.capacity() < sampleSize)) { - outputData = ByteBuffer.allocate(sampleSize); - out.data = outputData; + if (out.data == null || out.data.capacity() < sampleSize) { + out.replaceBuffer(sampleSize); } if (fragmentRun.definesEncryptionData) { readSampleEncryptionData(fragmentRun.sampleEncryptionData, out); } + ByteBuffer outputData = out.data; if (outputData == null) { inputStream.skip(sampleSize); out.size = 0; diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java index a0e0b962b3..7a44d1d960 100644 --- a/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java @@ -347,13 +347,11 @@ public final class WebmExtractor implements Extractor { throw new IllegalStateException("Lacing mode " + lacing + " not supported"); } - ByteBuffer outputData = sampleHolder.data; - if (sampleHolder.allowDataBufferReplacement - && (sampleHolder.data == null || sampleHolder.data.capacity() < sampleHolder.size)) { - outputData = ByteBuffer.allocate(sampleHolder.size); - sampleHolder.data = outputData; + if (sampleHolder.data == null || sampleHolder.data.capacity() < sampleHolder.size) { + sampleHolder.replaceBuffer(sampleHolder.size); } + ByteBuffer outputData = sampleHolder.data; if (outputData == null) { reader.skipBytes(inputStream, sampleHolder.size); sampleHolder.size = 0; diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java index cafdb89f25..38958aa0b3 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java +++ b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java @@ -55,7 +55,7 @@ public class SubtitleParserHelper implements Handler.Callback { * Flushes the helper, canceling the current parsing operation, if there is one. */ public synchronized void flush() { - sampleHolder = new SampleHolder(true); + sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); parsing = false; result = null; error = null; diff --git a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java index f7f38d986a..26ed0145c4 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java @@ -21,7 +21,6 @@ import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.util.Assertions; -import com.google.android.exoplayer.util.VerboseLogUtil; import android.annotation.TargetApi; import android.os.Handler; @@ -29,7 +28,6 @@ import android.os.Handler.Callback; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.util.Log; import java.io.IOException; @@ -54,8 +52,6 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { } - private static final String TAG = "TextTrackRenderer"; - private static final int MSG_UPDATE_OVERLAY = 0; private final Handler textRendererHandler; @@ -145,14 +141,13 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { @Override protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + currentPositionUs = positionUs; try { source.continueBuffering(positionUs); } catch (IOException e) { throw new ExoPlaybackException(e); } - currentPositionUs = positionUs; - if (parserHelper.isParsing()) { return; } @@ -188,7 +183,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { // We don't have a subtitle. Try and read the next one from the source, and if we succeed then // sync and set textRendererNeedsUpdate. - if (subtitle == null) { + if (!inputStreamEnded && subtitle == null) { try { SampleHolder sampleHolder = parserHelper.getSampleHolder(); int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false); @@ -215,12 +210,12 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { @Override protected void onDisabled() { - source.disable(trackIndex); subtitle = null; parserThread.quit(); parserThread = null; parserHelper = null; clearTextRenderer(); + source.disable(trackIndex); } @Override @@ -268,20 +263,18 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { private void updateTextRenderer(long positionUs) { String text = subtitle.getText(positionUs); - log("updateTextRenderer; text=: " + text); if (textRendererHandler != null) { textRendererHandler.obtainMessage(MSG_UPDATE_OVERLAY, text).sendToTarget(); } else { - invokeTextRenderer(text); + invokeRendererInternal(text); } } private void clearTextRenderer() { - log("clearTextRenderer"); if (textRendererHandler != null) { textRendererHandler.obtainMessage(MSG_UPDATE_OVERLAY, null).sendToTarget(); } else { - invokeTextRenderer(null); + invokeRendererInternal(null); } } @@ -289,20 +282,14 @@ public class TextTrackRenderer extends TrackRenderer implements Callback { public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_OVERLAY: - invokeTextRenderer((String) msg.obj); + invokeRendererInternal((String) msg.obj); return true; } return false; } - private void invokeTextRenderer(String text) { + private void invokeRendererInternal(String text) { textRenderer.onText(text); } - private void log(String logMessage) { - if (VerboseLogUtil.isTagEnabled(TAG)) { - Log.v(TAG, logMessage); - } - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/FileDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/FileDataSource.java index a08cacb982..ec9a3b9ade 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/FileDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/FileDataSource.java @@ -36,8 +36,27 @@ public final class FileDataSource implements DataSource { } + private final TransferListener listener; + private RandomAccessFile file; private long bytesRemaining; + private boolean opened; + + /** + * Constructs a new {@link DataSource} that retrieves data from a file. + */ + public FileDataSource() { + this(null); + } + + /** + * Constructs a new {@link DataSource} that retrieves data from a file. + * + * @param listener An optional listener. Specify {@code null} for no listener. + */ + public FileDataSource(TransferListener listener) { + this.listener = listener; + } @Override public long open(DataSpec dataSpec) throws FileDataSourceException { @@ -46,10 +65,16 @@ public final class FileDataSource implements DataSource { file.seek(dataSpec.position); bytesRemaining = dataSpec.length == C.LENGTH_UNBOUNDED ? file.length() - dataSpec.position : dataSpec.length; - return bytesRemaining; } catch (IOException e) { throw new FileDataSourceException(e); } + + opened = true; + if (listener != null) { + listener.onTransferStart(); + } + + return bytesRemaining; } @Override @@ -63,7 +88,14 @@ public final class FileDataSource implements DataSource { } catch (IOException e) { throw new FileDataSourceException(e); } - bytesRemaining -= bytesRead; + + if (bytesRead > 0) { + bytesRemaining -= bytesRead; + if (listener != null) { + listener.onBytesTransferred(bytesRead); + } + } + return bytesRead; } } @@ -75,8 +107,16 @@ public final class FileDataSource implements DataSource { file.close(); } catch (IOException e) { throw new FileDataSourceException(e); + } finally { + file = null; + + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(); + } + } } - file = null; } } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/UriDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/UriDataSource.java new file mode 100644 index 0000000000..1243576b17 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/upstream/UriDataSource.java @@ -0,0 +1,85 @@ +/* + * 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.upstream; + +import com.google.android.exoplayer.util.Assertions; + +import java.io.IOException; + +/** + * A data source that fetches data from a local or remote {@link DataSpec}. + */ +public final class UriDataSource implements DataSource { + + private static final String FILE_URI_SCHEME = "file"; + + private final DataSource fileDataSource; + private final DataSource httpDataSource; + + /** + * {@code null} if no data source is open. Otherwise, equal to {@link #fileDataSource} if the open + * data source is a file, or {@link #httpDataSource} otherwise. + */ + private DataSource dataSource; + + /** + * Constructs a new data source that delegates to a {@link FileDataSource} for file URIs and an + * {@link HttpDataSource} for other URIs. + * + * @param userAgent The User-Agent string that should be used when requesting remote data. + * @param transferListener An optional listener. + */ + public UriDataSource(String userAgent, TransferListener transferListener) { + this(new FileDataSource(transferListener), + new HttpDataSource(userAgent, null, transferListener)); + } + + /** + * Constructs a new data source using {@code fileDataSource} for file URIs, and + * {@code httpDataSource} for non-file URIs. + * + * @param fileDataSource {@link DataSource} to use for file URIs. + * @param httpDataSource {@link DataSource} to use for non-file URIs. + */ + public UriDataSource(DataSource fileDataSource, DataSource httpDataSource) { + this.fileDataSource = Assertions.checkNotNull(fileDataSource); + this.httpDataSource = Assertions.checkNotNull(httpDataSource); + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + Assertions.checkState(dataSource == null); + + dataSource = dataSpec.uri.getScheme().equals(FILE_URI_SCHEME) ? fileDataSource : httpDataSource; + return dataSource.open(dataSpec); + } + + @Override + public void close() throws IOException { + Assertions.checkNotNull(dataSource); + + dataSource.close(); + dataSource = null; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + Assertions.checkNotNull(dataSource); + + return dataSource.read(buffer, offset, readLength); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/util/ManifestFetcher.java b/library/src/main/java/com/google/android/exoplayer/util/ManifestFetcher.java index 423b1d0204..c72a856919 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/ManifestFetcher.java +++ b/library/src/main/java/com/google/android/exoplayer/util/ManifestFetcher.java @@ -24,8 +24,8 @@ import android.util.Pair; import java.io.IOException; import java.io.InputStream; -import java.net.HttpURLConnection; import java.net.URL; +import java.net.URLConnection; import java.util.concurrent.CancellationException; /** @@ -282,10 +282,10 @@ public class ManifestFetcher implements Loader.Callback { @Override public void load() throws IOException, InterruptedException { - String inputEncoding = null; + String inputEncoding; InputStream inputStream = null; try { - HttpURLConnection connection = configureHttpConnection(new URL(manifestUrl)); + URLConnection connection = configureConnection(new URL(manifestUrl)); inputStream = connection.getInputStream(); inputEncoding = connection.getContentEncoding(); result = parser.parse(inputStream, inputEncoding, contentId, @@ -297,8 +297,8 @@ public class ManifestFetcher implements Loader.Callback { } } - private HttpURLConnection configureHttpConnection(URL url) throws IOException { - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + private URLConnection configureConnection(URL url) throws IOException { + URLConnection connection = url.openConnection(); connection.setConnectTimeout(TIMEOUT_MILLIS); connection.setReadTimeout(TIMEOUT_MILLIS); connection.setDoOutput(false); 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 63d5220ac1..e3443467f3 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 @@ -32,6 +32,8 @@ public class MimeTypes { public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4"; public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm"; + public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; + public static final String AUDIO_EC3 = BASE_TYPE_AUDIO + "/eac3"; public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt";