From 1469f11aa3432369fbd687f9fb28fc35291e1c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20A=CC=8Akerfeldt?= Date: Wed, 13 May 2015 10:41:22 +0200 Subject: [PATCH 01/20] Support setting properties on MediaDrm --- .../drm/StreamingDrmSessionManager.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java b/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java index 2855678b5f..3187aa4788 100644 --- a/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java +++ b/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java @@ -213,6 +213,18 @@ public class StreamingDrmSessionManager implements DrmSessionManager { return mediaDrm.getPropertyString(key); } + /** + * Provides access to {@link MediaDrm#setPropertyString(String, String)}. + *

+ * This method may be called when the manager is in any state. + * + * @param key The property to write. + * @param value The value to write. + */ + public final void setPropertyString(String key, String value) { + mediaDrm.setPropertyString(key, value); + } + /** * Provides access to {@link MediaDrm#getPropertyByteArray(String)}. *

@@ -225,6 +237,18 @@ public class StreamingDrmSessionManager implements DrmSessionManager { return mediaDrm.getPropertyByteArray(key); } + /** + * Provides access to {@link MediaDrm#setPropertyByteArray(String, byte[])}. + *

+ * This method may be called when the manager is in any state. + * + * @param key The property to write. + * @param value The value to write. + */ + public final void setPropertyByteArray(String key, byte[] value) { + mediaDrm.setPropertyByteArray(key, value); + } + @Override public void open(DrmInitData drmInitData) { if (++openCount != 1) { From b340d47165ebaec360e5a2a05c56dfd23f7c16cc Mon Sep 17 00:00:00 2001 From: ood_tsen Date: Mon, 18 May 2015 20:24:59 +0800 Subject: [PATCH 02/20] support content uri (e.q. content://xxx) --- .../exoplayer/upstream/ContentDataSource.java | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 library/src/main/java/com/google/android/exoplayer/upstream/ContentDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/ContentDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/ContentDataSource.java new file mode 100644 index 0000000000..dd7b68d41d --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/upstream/ContentDataSource.java @@ -0,0 +1,156 @@ +/* + * 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 android.content.ContentResolver; +import android.content.Context; +import android.content.res.AssetFileDescriptor; + +import com.google.android.exoplayer.C; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * This calll is support content uri and file uri (e.q. content:// , file://). + * {@link DataSource}. + */ +public final class ContentDataSource implements UriDataSource { + static private final String TAG = "ContentDataSource"; + /** + * Thrown when IOException is encountered during local asset read operation. + */ + public static class ContentDataSourceException extends IOException { + + public ContentDataSourceException(IOException cause) { + super(cause); + } + + } + + private final TransferListener listener; + + private InputStream inputStream; + private Context context; + private String uri; + private long bytesRemaining; + private boolean opened; + + /** + * Constructs a new {@link DataSource} that retrieves data from a content provider. + */ + public ContentDataSource(Context context) { + this(context, null); + } + + /** + * Constructs a new {@link DataSource} that retrieves data from a content provider. + * + * @param listener An optional listener. Specify {@code null} for no listener. + */ + public ContentDataSource(Context context, TransferListener listener) { + this.context = context; + this.listener = listener; + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + try { + uri = dataSpec.uri.toString(); + inputStream = new FileInputStream(getFileDescriptor(context, dataSpec)); + inputStream.skip(dataSpec.position); + bytesRemaining = dataSpec.length == C.LENGTH_UNBOUNDED ? inputStream.available() + : dataSpec.length; + if (bytesRemaining < 0) { + throw new IOException(); + } + } catch (IOException e) { + throw new IOException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(); + } + + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + if (bytesRemaining == 0) { + return -1; + } else { + int bytesRead = 0; + try { + bytesRead = inputStream.read(buffer, offset, (int) Math.min(bytesRemaining, readLength)); + } catch (IOException e) { + throw new IOException(e); + } + + if (bytesRead > 0) { + bytesRemaining -= bytesRead; + if (listener != null) { + listener.onBytesTransferred(bytesRead); + } + } + + return bytesRead; + } + } + + @Override + public String getUri() { + return uri; + } + + @Override + public void close() throws ContentDataSourceException { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + throw new ContentDataSourceException(e); + } finally { + inputStream = null; + uri = null; + + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(); + } + } + } + } + } + + /** + * query the fileDescriptor from conten resolver. + * + */ + private static FileDescriptor getFileDescriptor(Context context, DataSpec dataSpec) throws IOException { + try { + ContentResolver resolver = context.getContentResolver(); + AssetFileDescriptor fd = resolver.openAssetFileDescriptor(dataSpec.uri, "r"); + return fd.getFileDescriptor(); + } catch (IOException e) { + throw new IOException(e); + } + } +} From b9688742c672c02291c58845ee846bf34e4b46c7 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 13:50:31 +0100 Subject: [PATCH 03/20] Add changelog to track high level changes of each release --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..36f03fb47f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release notes # + +## Current dev (from 1.3.1) ## + +* \ No newline at end of file From d0ea014ba3dae62ec0d124034de1cd7d71f549e4 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 13:54:59 +0100 Subject: [PATCH 04/20] Tweak documentation --- README.md | 12 +++++------- CHANGELOG.md => RELEASENOTES.md | 0 2 files changed, 5 insertions(+), 7 deletions(-) rename CHANGELOG.md => RELEASENOTES.md (100%) diff --git a/README.md b/README.md index c6935075ac..f1499b23dd 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,16 @@ Read news, hints and tips on the [news][] page. [news]: https://google.github.io/ExoPlayer/news.html -## Developer guide ## +## Documentation ## -The [developer guide][] provides a wealth of information to help you get +* The [developer guide][] provides a wealth of information to help you get started. +* The [class reference][] documents the ExoPlayer library classes. +* The [release notes][] document the major changes in each release. [developer guide]: https://google.github.io/ExoPlayer/guide.html - -## Reference documentation ## - -The [class reference][] documents the ExoPlayer library classes. - [class reference]: https://google.github.io/ExoPlayer/doc/reference +[release notes]: https://github.com/google/ExoPlayer/blob/dev/RELEASENOTES.md ## Project branches ## diff --git a/CHANGELOG.md b/RELEASENOTES.md similarity index 100% rename from CHANGELOG.md rename to RELEASENOTES.md From 35737bb9895936e80c1495122410913810e5a609 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 13:56:13 +0100 Subject: [PATCH 05/20] Tweak documentation 2 --- RELEASENOTES.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 36f03fb47f..d86d1f0b81 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,9 @@ # Release notes # -## Current dev (from 1.3.1) ## +### Current dev branch (from r1.3.1) ### -* \ No newline at end of file +* TBC + +### r1.3.1 ### + +* No notes provided. From 9f53ea81169f8e1f680a35450dad78d58f16b034 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 13:56:44 +0100 Subject: [PATCH 06/20] Tweak documentation 3 --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d86d1f0b81..4bd15a2881 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,6 @@ # Release notes # -### Current dev branch (from r1.3.1) ### +### Latest dev branch (from r1.3.1) ### * TBC From 0dfc1d3bb799ee2c48b51f9b6b32b0e350161b4d Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:00:49 +0100 Subject: [PATCH 07/20] Don't sample the timestamp/latency for AC-3 passthrough playback. --- .../com/google/android/exoplayer/audio/AudioTrack.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 96271a2774..a5bc408751 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 @@ -574,6 +574,7 @@ public final class AudioTrack { submittedBytes = 0; temporaryBufferSize = 0; startMediaTimeUs = START_NOT_SET; + latencyUs = 0; resetSyncParams(); int playState = audioTrack.getPlayState(); if (playState == android.media.AudioTrack.PLAYSTATE_PLAYING) { @@ -647,9 +648,10 @@ public final class AudioTrack { } } - if (systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) { - // Don't use AudioTrack.getTimestamp() on AC-3 tracks, as it gives an incorrect timestamp. - audioTimestampSet = !isAc3 && audioTrackUtil.updateTimestamp(); + // Don't sample the timestamp and latency if this is an AC-3 passthrough AudioTrack, as the + // returned values cause audio/video synchronization to be incorrect. + if (!isAc3 && systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) { + audioTimestampSet = audioTrackUtil.updateTimestamp(); if (audioTimestampSet) { // Perform sanity checks on the timestamp. long audioTimestampUs = audioTrackUtil.getTimestampNanoTime() / 1000; From dd7a79688396843f40bdb507e3c7ea00e27e966b Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:01:59 +0100 Subject: [PATCH 08/20] Make constant public --- .../android/exoplayer/upstream/DefaultBandwidthMeter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java index f2c7ec49b1..8af7bf9250 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java @@ -28,7 +28,7 @@ import android.os.Handler; */ public class DefaultBandwidthMeter implements BandwidthMeter { - private static final int DEFAULT_MAX_WEIGHT = 2000; + public static final int DEFAULT_MAX_WEIGHT = 2000; private final Handler eventHandler; private final EventListener eventListener; From d03fb105165f0724b8e576f84b85503c169b233d Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:02:40 +0100 Subject: [PATCH 09/20] Remove readBitsLong, use readBits instead --- .../android/exoplayer/extractor/ts/TsExtractor.java | 6 +++--- .../android/exoplayer/util/ParsableBitArray.java | 12 +----------- 2 files changed, 4 insertions(+), 14 deletions(-) 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 670f3ea8ec..6b83eff070 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 @@ -532,11 +532,11 @@ public final class TsExtractor implements Extractor, SeekMap { timeUs = 0; if (ptsFlag) { pesScratch.skipBits(4); // '0010' - long pts = pesScratch.readBitsLong(3) << 30; + long pts = (long) pesScratch.readBits(3) << 30; pesScratch.skipBits(1); // marker_bit - pts |= pesScratch.readBitsLong(15) << 15; + pts |= pesScratch.readBits(15) << 15; pesScratch.skipBits(1); // marker_bit - pts |= pesScratch.readBitsLong(15); + pts |= pesScratch.readBits(15); pesScratch.skipBits(1); // marker_bit timeUs = ptsToTimeUs(pts); } diff --git a/library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java b/library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java index 98e4cc7160..b8dd63228a 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java +++ b/library/src/main/java/com/google/android/exoplayer/util/ParsableBitArray.java @@ -99,21 +99,11 @@ public final class ParsableBitArray { * @return An integer whose bottom n bits hold the read data. */ public int readBits(int n) { - return (int) readBitsLong(n); - } - - /** - * Reads up to 64 bits. - * - * @param n The number of bits to read. - * @return A long whose bottom n bits hold the read data. - */ - public long readBitsLong(int n) { if (n == 0) { return 0; } - long retval = 0; + int retval = 0; // While n >= 8, read whole bytes. while (n >= 8) { From cd6c5c39848faeaecd14693fbe3f6bb25128fb84 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:03:39 +0100 Subject: [PATCH 10/20] Fix CBR seeking when XING header is present. When a XING header is present but not usable (due to missing fields), CBR seeking can be used instead. It relies on the bitrate. The bitrate from the unusable XING header is not correct, which leads to incorrect seeking. Also fix VBRI seeking by setting the correct offset on the frame to parse. Few people seem to use that format, but I have found two very short truncated samples which were falling back to the CBR case before but are using VBRI with this change. --- .../exoplayer/extractor/mp3/Mp3Extractor.java | 106 +++++++++++++++--- .../exoplayer/extractor/mp3/VbriSeeker.java | 29 +++-- .../exoplayer/extractor/mp3/XingSeeker.java | 40 ++----- 3 files changed, 115 insertions(+), 60 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java index 5842b19ce3..e7a30b170c 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java @@ -45,6 +45,9 @@ public final class Mp3Extractor implements Extractor { private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); private static final String[] MIME_TYPE_BY_LAYER = new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG}; + private static final int XING_HEADER = Util.getIntegerCodeForString("Xing"); + private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); + private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); /** * Theoretical maximum frame size for an MPEG audio stream, which occurs when playing a Layer 2 @@ -239,21 +242,7 @@ public final class Mp3Extractor implements Extractor { // The input buffer read position is now synchronized. inputBuffer.returnToMark(); if (seeker == null) { - ParsableByteArray frame = - inputBuffer.getParsableByteArray(extractorInput, synchronizedHeader.frameSize); - seeker = XingSeeker.create(synchronizedHeader, frame, headerPosition, - extractorInput.getLength()); - if (seeker == null) { - seeker = VbriSeeker.create(synchronizedHeader, frame, headerPosition); - } - if (seeker == null) { - inputBuffer.returnToMark(); - seeker = new ConstantBitrateSeeker(headerPosition, synchronizedHeader.bitrate * 1000, - extractorInput.getLength()); - } else { - // Discard the frame that was parsed for seeking metadata. - inputBuffer.mark(); - } + setupSeeker(extractorInput, headerPosition); extractorOutput.seekMap(seeker); trackOutput.format(MediaFormat.createAudioFormat( MIME_TYPE_BY_LAYER[synchronizedHeader.layerIndex], MAX_FRAME_SIZE_BYTES, @@ -264,6 +253,93 @@ public final class Mp3Extractor implements Extractor { return headerPosition; } + /** + * Sets {@link #seeker} to seek using metadata from {@link #inputBuffer}, which should have its + * position set to the start of the first frame in the stream. On returning, + * {@link #inputBuffer}'s position and mark will be set to the start of the first frame of audio. + * + * @param extractorInput Source of data for {@link #inputBuffer}. + * @param headerPosition Position (byte offset) of the synchronized header in the stream. + * @throws IOException Thrown if there was an error reading from the stream. Not expected if the + * next two frames were already read during synchronization. + * @throws InterruptedException Thrown if reading from the stream was interrupted. Not expected if + * the next two frames were already read during synchronization. + */ + private void setupSeeker(ExtractorInput extractorInput, long headerPosition) + throws IOException, InterruptedException { + // Try to set up seeking based on a XING or VBRI header. + if (parseSeekerFrame(extractorInput, headerPosition, extractorInput.getLength())) { + // Discard the parsed header so we start reading from the first audio frame. + inputBuffer.mark(); + if (seeker != null) { + return; + } + + // If there was a header but it was not usable, synchronize to the next frame so we don't + // use an invalid bitrate for CBR seeking. This read is guaranteed to succeed if the frame was + // already read during synchronization. + inputBuffer.read(extractorInput, scratch.data, 0, 4); + scratch.setPosition(0); + headerPosition += synchronizedHeader.frameSize; + MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader); + } + + inputBuffer.returnToMark(); + seeker = new ConstantBitrateSeeker(headerPosition, synchronizedHeader.bitrate * 1000, + extractorInput.getLength()); + } + + /** + * Consumes the frame at {@link #inputBuffer}'s current position, advancing it to the next frame. + * The mark is not modified. {@link #seeker} will be assigned based on seeking metadata in the + * frame. If there is no seeking metadata, returns {@code false} and sets {@link #seeker} to null. + * If seeking metadata is present and unusable, returns {@code true} and sets {@link #seeker} to + * null. Otherwise, returns {@code true} and assigns {@link #seeker}. + */ + private boolean parseSeekerFrame(ExtractorInput extractorInput, long headerPosition, + long inputLength) throws IOException, InterruptedException { + // Read the first frame so it can be parsed for seeking metadata. + inputBuffer.mark(); + seeker = null; + ParsableByteArray frame = + inputBuffer.getParsableByteArray(extractorInput, synchronizedHeader.frameSize); + + // Check if there is a XING header. + int xingBase; + if ((synchronizedHeader.version & 1) == 1) { + // MPEG 1. + if (synchronizedHeader.channels != 1) { + xingBase = 32; + } else { + xingBase = 17; + } + } else { + // MPEG 2 or 2.5. + if (synchronizedHeader.channels != 1) { + xingBase = 17; + } else { + xingBase = 9; + } + } + frame.setPosition(4 + xingBase); + int headerData = frame.readInt(); + if (headerData == XING_HEADER || headerData == INFO_HEADER) { + seeker = XingSeeker.create(synchronizedHeader, frame, headerPosition, inputLength); + return true; + } + + // Check if there is a VBRI header. + frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes. + headerData = frame.readInt(); + if (headerData == VBRI_HEADER) { + seeker = VbriSeeker.create(synchronizedHeader, frame, headerPosition); + return true; + } + + // Neither header is present. + return false; + } + /** Returns the reading position of {@code bufferingInput} relative to the extractor's stream. */ private static long getPosition(ExtractorInput extractorInput, BufferingInput bufferingInput) { return extractorInput.getPosition() - bufferingInput.getAvailableByteCount(); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java index 4413edaaa7..9d096607cb 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java @@ -23,23 +23,20 @@ import com.google.android.exoplayer.util.Util; */ /* package */ final class VbriSeeker implements Mp3Extractor.Seeker { - private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); - /** - * If {@code frame} contains a VBRI header and it is usable for seeking, returns a - * {@link VbriSeeker} for seeking in the containing stream. Otherwise, returns {@code null}, which - * indicates that the information in the frame was not a VBRI header, or was unusable for seeking. + * Returns a {@link VbriSeeker} for seeking in the stream, if required information is present. + * Returns {@code null} if not. On returning, {@code frame}'s position is not specified so the + * caller should reset it. + * + * @param mpegAudioHeader The MPEG audio header associated with the frame. + * @param frame The data in this audio frame, with its position set to immediately after the + * 'VBRI' tag. + * @param position The position (byte offset) of the start of this frame in the stream. + * @return A {@link VbriSeeker} for seeking in the stream, or {@code null} if the required + * information is not present. */ - public static VbriSeeker create( - MpegAudioHeader mpegAudioHeader, ParsableByteArray frame, long position) { - long basePosition = position + mpegAudioHeader.frameSize; - - // Read the VBRI header. - frame.skipBytes(32); - int headerData = frame.readInt(); - if (headerData != VBRI_HEADER) { - return null; - } + public static VbriSeeker create(MpegAudioHeader mpegAudioHeader, ParsableByteArray frame, + long position) { frame.skipBytes(10); int numFrames = frame.readInt(); if (numFrames <= 0) { @@ -83,7 +80,7 @@ import com.google.android.exoplayer.util.Util; segmentIndex++; } - return new VbriSeeker(timesUs, offsets, basePosition, durationUs); + return new VbriSeeker(timesUs, offsets, position + mpegAudioHeader.frameSize, durationUs); } private final long[] timesUs; 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 bbc3ae4c8a..7ddb006705 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 @@ -24,13 +24,18 @@ import com.google.android.exoplayer.util.Util; */ /* package */ final class XingSeeker implements Mp3Extractor.Seeker { - private static final int XING_HEADER = Util.getIntegerCodeForString("Xing"); - private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); - /** - * If {@code frame} contains a XING header and it is usable for seeking, returns a - * {@link XingSeeker} for seeking in the containing stream. Otherwise, returns {@code null}, which - * indicates that the information in the frame was not a XING header, or was unusable for seeking. + * Returns a {@link XingSeeker} for seeking in the stream, if required information is present. + * Returns {@code null} if not. On returning, {@code frame}'s position is not specified so the + * caller should reset it. + * + * @param mpegAudioHeader The MPEG audio header associated with the frame. + * @param frame The data in this audio frame, with its position set to immediately after the + * 'XING' or 'INFO' tag. + * @param position The position (byte offset) of the start of this frame in the stream. + * @param inputLength The length of the stream in bytes. + * @return A {@link XingSeeker} for seeking in the stream, or {@code null} if the required + * information is not present. */ public static XingSeeker create(MpegAudioHeader mpegAudioHeader, ParsableByteArray frame, long position, long inputLength) { @@ -38,29 +43,6 @@ import com.google.android.exoplayer.util.Util; int sampleRate = mpegAudioHeader.sampleRate; long firstFramePosition = position + mpegAudioHeader.frameSize; - // Skip to the XING header. - int xingBase; - if ((mpegAudioHeader.version & 1) == 1) { - // MPEG 1. - if (mpegAudioHeader.channels != 1) { - xingBase = 32; - } else { - xingBase = 17; - } - } else { - // MPEG 2 or 2.5. - if (mpegAudioHeader.channels != 1) { - xingBase = 17; - } else { - xingBase = 9; - } - } - frame.skipBytes(4 + xingBase); - int headerData = frame.readInt(); - if (headerData != XING_HEADER && headerData != INFO_HEADER) { - return null; - } - int flags = frame.readInt(); // Frame count, size and table of contents are required to use this header. if ((flags & 0x07) != 0x07) { From 672906060ca64763f7209cddbf932e865db3bb19 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:05:08 +0100 Subject: [PATCH 11/20] Fix missing ID3 events When I moved the id3Reader instantiation out of the while loop (below where it is now) it was no longer guarded by the streamTypes.get(streamType) deduping check. This brings back an equivalent check. --- .../exoplayer/extractor/ts/TsExtractor.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) 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 6b83eff070..684ada760f 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 @@ -52,16 +52,17 @@ public final class TsExtractor implements Extractor, SeekMap { private static final long MAX_PTS = 0x1FFFFFFFFL; private final ParsableByteArray tsPacketBuffer; - private final SparseBooleanArray streamTypes; - private final SparseBooleanArray allowedPassthroughStreamTypes; - private final SparseArray tsPayloadReaders; // Indexed by pid - private final long firstSampleTimestampUs; private final ParsableBitArray tsScratch; + private final long firstSampleTimestampUs; + /* package */ final SparseBooleanArray streamTypes; + /* package */ final SparseBooleanArray allowedPassthroughStreamTypes; + /* package */ final SparseArray tsPayloadReaders; // Indexed by pid // Accessed only by the loading thread. private ExtractorOutput output; private long timestampOffsetUs; private long lastPts; + /* package */ Id3Reader id3Reader; public TsExtractor() { this(0, null); @@ -307,9 +308,11 @@ public final class TsExtractor implements Extractor, SeekMap { // Skip the descriptors. data.skipBytes(programInfoLength); - // Setup an ID3 track regardless of whether there's a corresponding entry, in case one - // appears intermittently during playback. See b/20261500. - Id3Reader id3Reader = new Id3Reader(output.track(TS_STREAM_TYPE_ID3)); + if (id3Reader == null) { + // Setup an ID3 track regardless of whether there's a corresponding entry, in case one + // appears intermittently during playback. See b/20261500. + id3Reader = new Id3Reader(output.track(TS_STREAM_TYPE_ID3)); + } int entriesSize = sectionLength - 9 /* Size of the rest of the fields before descriptors */ - programInfoLength - 4 /* CRC size */; From 059b80c1ab798c85e43280a768f239c6d1c7286a Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:05:50 +0100 Subject: [PATCH 12/20] Improve DataSource implementations: 1. Make DefaultUriDataSource robust against exceptions through from close(). 2. Make AssetDataSource handle file:///android_asset/ URIs. --- .../exoplayer/upstream/AssetDataSource.java | 25 ++++++++++++++----- .../upstream/DefaultUriDataSource.java | 7 ++++-- .../exoplayer/upstream/FileDataSource.java | 2 +- .../exoplayer/upstream/HttpDataSource.java | 2 +- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/AssetDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/AssetDataSource.java index 77bff359e1..28a1cd3cab 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/AssetDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/AssetDataSource.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer.upstream; import com.google.android.exoplayer.C; +import com.google.android.exoplayer.util.Assertions; import android.content.res.AssetManager; @@ -24,9 +25,9 @@ import java.io.IOException; import java.io.InputStream; /** - * A local asset {@link DataSource}. + * A local asset {@link UriDataSource}. */ -public final class AssetDataSource implements DataSource { +public final class AssetDataSource implements UriDataSource { /** * Thrown when IOException is encountered during local asset read operation. @@ -42,6 +43,7 @@ public final class AssetDataSource implements DataSource { private final AssetManager assetManager; private final TransferListener listener; + private String uriString; private InputStream assetInputStream; private long bytesRemaining; private boolean opened; @@ -66,10 +68,16 @@ public final class AssetDataSource implements DataSource { @Override public long open(DataSpec dataSpec) throws AssetDataSourceException { try { - // Lose the '/' prefix in the path or else AssetManager won't find our file - assetInputStream = assetManager.open(dataSpec.uri.getPath().substring(1), - AssetManager.ACCESS_RANDOM); - assetInputStream.skip(dataSpec.position); + uriString = dataSpec.uri.toString(); + String path = dataSpec.uri.getPath(); + if (path.startsWith("/android_asset/")) { + path = path.substring(15); + } else if (path.startsWith("/")) { + path = path.substring(1); + } + assetInputStream = assetManager.open(path, AssetManager.ACCESS_RANDOM); + long skipped = assetInputStream.skip(dataSpec.position); + Assertions.checkState(skipped == dataSpec.position); bytesRemaining = dataSpec.length == C.LENGTH_UNBOUNDED ? assetInputStream.available() : dataSpec.length; if (bytesRemaining < 0) { @@ -110,6 +118,11 @@ public final class AssetDataSource implements DataSource { } } + @Override + public String getUri() { + return uriString; + } + @Override public void close() throws AssetDataSourceException { if (assetInputStream != null) { diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultUriDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultUriDataSource.java index fa225bf266..0a71523419 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultUriDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultUriDataSource.java @@ -100,8 +100,11 @@ public final class DefaultUriDataSource implements UriDataSource { @Override public void close() throws IOException { if (dataSource != null) { - dataSource.close(); - dataSource = null; + try { + dataSource.close(); + } finally { + dataSource = null; + } } } 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 bd890b38d2..68dd7e2a0c 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 @@ -22,7 +22,7 @@ import java.io.IOException; import java.io.RandomAccessFile; /** - * A local file {@link DataSource}. + * A local file {@link UriDataSource}. */ public final class FileDataSource implements UriDataSource { diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/HttpDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/HttpDataSource.java index 2944616a09..6009a837a5 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/HttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/HttpDataSource.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.Map; /** - * An HTTP specific extension to {@link DataSource}. + * An HTTP specific extension to {@link UriDataSource}. */ public interface HttpDataSource extends UriDataSource { From f474afbf5e0a0b3f22307eac2f93f1135d4c6828 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:06:48 +0100 Subject: [PATCH 13/20] Fix handling of widths/heights when choosing formats. - Make HlsPlaylistParser treat non-positive dimensions as unknown. - Make HlsPlaylistParser parse floating point resolutions, because technically that's how they're spec'd. - Make VideoFormatSelectorUtil treat non-position dimensions as unknown. Issue: #461 --- .../exoplayer/chunk/VideoFormatSelectorUtil.java | 6 +++--- .../android/exoplayer/hls/HlsParserUtil.java | 4 ++-- .../android/exoplayer/hls/HlsPlaylistParser.java | 14 +++++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.java b/library/src/main/java/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.java index de156ea0db..03adf45819 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.java @@ -108,7 +108,7 @@ public final class VideoFormatSelectorUtil { // Keep track of the number of pixels of the selected format whose resolution is the // smallest to exceed the maximum size at which it can be displayed within the viewport. // We'll discard formats of higher resolution in a second pass. - if (format.width != -1 && format.height != -1) { + if (format.width > 0 && format.height > 0) { Point maxVideoSizeInViewport = getMaxVideoSizeInViewport(orientationMayChange, viewportWidth, viewportHeight, format.width, format.height); int videoPixels = format.width * format.height; @@ -126,7 +126,7 @@ public final class VideoFormatSelectorUtil { // viewport. for (int i = selectedIndexList.size() - 1; i >= 0; i--) { Format format = formatWrappers.get(i).getFormat(); - if (format.width != -1 && format.height != -1 + if (format.width > 0 && format.height > 0 && format.width * format.height > maxVideoPixelsToRetain) { selectedIndexList.remove(i); } @@ -150,7 +150,7 @@ public final class VideoFormatSelectorUtil { // Filtering format because it's HD. return false; } - if (format.width != -1 && format.height != -1) { + if (format.width > 0 && format.height > 0) { // TODO: Use MediaCodecUtil.isSizeAndRateSupportedV21 on API levels >= 21 if we know the // mimeType of the media samples within the container. Remove the assumption that we're // dealing with H.264. diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsParserUtil.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsParserUtil.java index 7e5dd64367..2ddfdd081b 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsParserUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsParserUtil.java @@ -51,7 +51,7 @@ import java.util.regex.Pattern; public static String parseOptionalStringAttr(String line, Pattern pattern) { Matcher matcher = pattern.matcher(line); - if (matcher.find() && matcher.groupCount() == 1) { + if (matcher.find()) { return matcher.group(1); } return null; @@ -59,7 +59,7 @@ import java.util.regex.Pattern; public static boolean parseOptionalBooleanAttr(String line, Pattern pattern) { Matcher matcher = pattern.matcher(line); - if (matcher.find() && matcher.groupCount() == 1) { + if (matcher.find()) { return BOOLEAN_YES.equals(matcher.group(1)); } return false; diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java index 316f41a44d..4d0b163cb0 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java @@ -72,7 +72,7 @@ public final class HlsPlaylistParser implements UriLoadable.Parser private static final Pattern CODECS_ATTR_REGEX = Pattern.compile(CODECS_ATTR + "=\"(.+?)\""); private static final Pattern RESOLUTION_ATTR_REGEX = - Pattern.compile(RESOLUTION_ATTR + "=(\\d+x\\d+)"); + Pattern.compile(RESOLUTION_ATTR + "=(\\d+(\\.\\d+)?x\\d+(\\.\\d+)?)"); private static final Pattern MEDIA_DURATION_REGEX = Pattern.compile(MEDIA_DURATION_TAG + ":([\\d.]+),"); private static final Pattern MEDIA_SEQUENCE_REGEX = @@ -168,8 +168,16 @@ public final class HlsPlaylistParser implements UriLoadable.Parser RESOLUTION_ATTR_REGEX); if (resolutionString != null) { String[] widthAndHeight = resolutionString.split("x"); - width = Integer.parseInt(widthAndHeight[0]); - height = Integer.parseInt(widthAndHeight[1]); + width = Math.round(Float.parseFloat(widthAndHeight[0])); + if (width <= 0) { + // Width was invalid. + width = -1; + } + height = Math.round(Float.parseFloat(widthAndHeight[1])); + if (height <= 0) { + // Height was invalid. + height = -1; + } } else { width = -1; height = -1; From 675c7738c14e81f56292e0e1b7e246f1c6ce1e58 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:12:59 +0100 Subject: [PATCH 14/20] Let DefaultUriDataSource load assets. --- .../exoplayer/demo/PlayerActivity.java | 10 +- .../demo/player/DashRendererBuilder.java | 14 +-- .../demo/player/ExtractorRendererBuilder.java | 9 +- .../demo/player/HlsRendererBuilder.java | 6 +- .../SmoothStreamingRendererBuilder.java | 6 +- .../exoplayer/upstream/AssetDataSource.java | 11 +- .../upstream/DefaultUriDataSource.java | 108 ++++++++++++++---- 7 files changed, 116 insertions(+), 48 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index e824ed0115..5dd1eb8d32 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -233,19 +233,19 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, audioCapabilities); case DemoUtil.TYPE_M4A: // There are no file format differences between M4A and MP4. case DemoUtil.TYPE_MP4: - return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, new Mp4Extractor()); case DemoUtil.TYPE_MP3: - return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, new Mp3Extractor()); case DemoUtil.TYPE_TS: - return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, new TsExtractor(0, audioCapabilities)); case DemoUtil.TYPE_AAC: - return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, new AdtsExtractor()); case DemoUtil.TYPE_WEBM: - return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView, + return new ExtractorRendererBuilder(this, userAgent, contentUri, debugTextView, new WebmExtractor()); default: throw new IllegalStateException("Unsupported type: " + contentType); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java index 2b0de68c3d..bde74928a0 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java @@ -130,7 +130,7 @@ public class DashRendererBuilder implements RendererBuilder, this.player = player; this.callback = callback; MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser(); - manifestDataSource = new DefaultUriDataSource(userAgent, null); + manifestDataSource = new DefaultUriDataSource(context, userAgent); manifestFetcher = new ManifestFetcher(url, manifestDataSource, parser); manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); @@ -232,10 +232,10 @@ public class DashRendererBuilder implements RendererBuilder, videoRenderer = null; debugRenderer = null; } else { - DataSource videoDataSource = new DefaultUriDataSource(userAgent, bandwidthMeter); - ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, videoAdaptationSetIndex, - videoRepresentationIndices, videoDataSource, new AdaptiveEvaluator(bandwidthMeter), - LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset); + DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); + ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, + videoAdaptationSetIndex, videoRepresentationIndices, videoDataSource, + new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS, elapsedRealtimeOffset); ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, mainHandler, player, DemoPlayer.TYPE_VIDEO); @@ -249,7 +249,7 @@ public class DashRendererBuilder implements RendererBuilder, List audioChunkSourceList = new ArrayList(); List audioTrackNameList = new ArrayList(); if (audioAdaptationSet != null) { - DataSource audioDataSource = new DefaultUriDataSource(userAgent, bandwidthMeter); + DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator(); List audioRepresentations = audioAdaptationSet.representations; List codecs = new ArrayList(); @@ -304,7 +304,7 @@ public class DashRendererBuilder implements RendererBuilder, } // Build the text chunk sources. - DataSource textDataSource = new DefaultUriDataSource(userAgent, bandwidthMeter); + DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); FormatEvaluator textEvaluator = new FormatEvaluator.FixedEvaluator(); List textChunkSourceList = new ArrayList(); List textTrackNameList = new ArrayList(); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java index c9cff47697..3437678e04 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DefaultUriDataSource; +import android.content.Context; import android.media.MediaCodec; import android.net.Uri; import android.widget.TextView; @@ -36,13 +37,15 @@ public class ExtractorRendererBuilder implements RendererBuilder { private static final int BUFFER_SIZE = 10 * 1024 * 1024; + private final Context context; private final String userAgent; private final Uri uri; private final TextView debugTextView; private final Extractor extractor; - public ExtractorRendererBuilder(String userAgent, Uri uri, TextView debugTextView, - Extractor extractor) { + public ExtractorRendererBuilder(Context context, String userAgent, Uri uri, + TextView debugTextView, Extractor extractor) { + this.context = context; this.userAgent = userAgent; this.uri = uri; this.debugTextView = debugTextView; @@ -52,7 +55,7 @@ public class ExtractorRendererBuilder implements RendererBuilder { @Override public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { // Build the video and audio renderers. - DataSource dataSource = new DefaultUriDataSource(userAgent, null); + DataSource dataSource = new DefaultUriDataSource(context, userAgent); ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, extractor, 2, BUFFER_SIZE); MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java index 171433c0d8..8eb762e218 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java @@ -76,8 +76,8 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback playlistFetcher = - new ManifestFetcher(url, new DefaultUriDataSource(userAgent, null), parser); + ManifestFetcher playlistFetcher = new ManifestFetcher(url, + new DefaultUriDataSource(context, userAgent), parser); playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this); } @@ -103,7 +103,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback + *

  • http(s): For fetching data over HTTP and HTTPS (e.g. https://www.something.com/media.mp4). + *
  • file: For fetching data from a local file (e.g. file:///path/to/media/media.mp4). + *
  • asset: For fetching data from an asset in the application's apk (e.g. asset:///media.mp4). + * */ public final class DefaultUriDataSource implements UriDataSource { - private static final String FILE_URI_SCHEME = "file"; + /** + * Thrown when a {@link DefaultUriDataSource} is opened for a URI with an unsupported scheme. + */ + public static final class UnsupportedSchemeException extends IOException { + + /** + * The unsupported scheme. + */ + public final String scheme; + + /** + * @param scheme The unsupported scheme. + */ + public UnsupportedSchemeException(String scheme) { + super("Unsupported URI scheme: " + scheme); + this.scheme = scheme; + } + + } + + private static final String SCHEME_HTTP = "http"; + private static final String SCHEME_HTTPS = "https"; + private static final String SCHEME_FILE = "file"; + private static final String SCHEME_ASSET = "asset"; - private final UriDataSource fileDataSource; private final UriDataSource httpDataSource; + private final UriDataSource fileDataSource; + private final UriDataSource assetDataSource; /** * {@code null} if no data source is open. Otherwise, equal to {@link #fileDataSource} if the open @@ -36,54 +68,86 @@ public final class DefaultUriDataSource implements UriDataSource { private UriDataSource dataSource; /** - * Constructs a new data source that delegates to a {@link FileDataSource} for file URIs and a - * {@link DefaultHttpDataSource} for other URIs. + * Constructs a new instance. *

    * 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. + * using {@link #DefaultUriDataSource(Context, TransferListener, String, boolean)} and passing + * {@code true} as the final argument. * + * @param context A context. * @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); + public DefaultUriDataSource(Context context, String userAgent) { + this(context, null, userAgent, false); } /** - * Constructs a new data source that delegates to a {@link FileDataSource} for file URIs and a - * {@link DefaultHttpDataSource} for other URIs. + * Constructs a new instance. + *

    + * 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 {@link #DefaultUriDataSource(Context, TransferListener, String, boolean)} and passing + * {@code true} as the final argument. * + * @param context A context. + * @param listener An optional {@link TransferListener}. + * @param userAgent The User-Agent string that should be used when requesting remote data. + */ + public DefaultUriDataSource(Context context, TransferListener listener, String userAgent) { + this(context, listener, userAgent, false); + } + + /** + * Constructs a new instance, optionally configured to follow cross-protocol redirects. + * + * @param context A context. + * @param listener An optional {@link TransferListener}. * @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, + public DefaultUriDataSource(Context context, TransferListener listener, String userAgent, boolean allowCrossProtocolRedirects) { - this(new FileDataSource(transferListener), - new DefaultHttpDataSource(userAgent, null, transferListener, + this(context, listener, + new DefaultHttpDataSource(userAgent, null, listener, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, allowCrossProtocolRedirects)); } /** - * Constructs a new data source using {@code fileDataSource} for file URIs, and - * {@code httpDataSource} for non-file URIs. + * Constructs a new instance, using a provided {@link HttpDataSource} for fetching remote data. * - * @param fileDataSource {@link UriDataSource} to use for file URIs. + * @param context A context. + * @param listener An optional {@link TransferListener}. * @param httpDataSource {@link UriDataSource} to use for non-file URIs. */ - public DefaultUriDataSource(UriDataSource fileDataSource, UriDataSource httpDataSource) { - this.fileDataSource = Assertions.checkNotNull(fileDataSource); + public DefaultUriDataSource(Context context, TransferListener listener, + UriDataSource httpDataSource) { this.httpDataSource = Assertions.checkNotNull(httpDataSource); + this.fileDataSource = new FileDataSource(listener); + this.assetDataSource = new AssetDataSource(context, listener); } @Override public long open(DataSpec dataSpec) throws IOException { Assertions.checkState(dataSource == null); - dataSource = FILE_URI_SCHEME.equals(dataSpec.uri.getScheme()) ? fileDataSource : httpDataSource; + // Choose the correct source for the scheme. + String scheme = dataSpec.uri.getScheme(); + if (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme)) { + dataSource = httpDataSource; + } else if (SCHEME_FILE.equals(scheme)) { + if (dataSpec.uri.getPath().startsWith("/android_asset/")) { + dataSource = assetDataSource; + } else { + dataSource = fileDataSource; + } + } else if (SCHEME_ASSET.equals(scheme)) { + dataSource = assetDataSource; + } else { + throw new UnsupportedSchemeException(scheme); + } + // Open the source and return. return dataSource.open(dataSpec); } From a9c977a79e0be0cf40903aaa89ce7e8514de777d Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:13:31 +0100 Subject: [PATCH 15/20] Fix Mp3Extractor synchronization when loading retries. Before preparation, and when seeking, Mp3Extractor did not handle retrying: - synchronizedHeader was set before the header was known to be valid, which means that after seeing one valid frame header and then failing to read, the synchronization would be treated as complete. - The input buffer would keep data loaded during synchronization but on the next call to synchronize when retrying it was not returned to the mark position to re-parse the data. This change fixes these issues. --- .../exoplayer/extractor/mp3/Mp3Extractor.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java index e7a30b170c..0b82bede91 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java @@ -175,6 +175,15 @@ public final class Mp3Extractor implements Extractor { } private long synchronize(ExtractorInput extractorInput) throws IOException, InterruptedException { + if (extractorInput.getPosition() == 0) { + // Before preparation completes, retrying loads from the start, so clear any buffered data. + inputBuffer.reset(); + } else { + // After preparation completes, retrying resumes loading from the old position, so return to + // the start of buffered data to parse it again. + inputBuffer.returnToMark(); + } + long startPosition = getPosition(extractorInput, inputBuffer); // Skip any ID3 header at the start of the file. @@ -198,6 +207,7 @@ public final class Mp3Extractor implements Extractor { inputBuffer.mark(); long headerPosition = startPosition; int validFrameCount = 0; + int candidateSynchronizedHeaderData = 0; while (true) { if (headerPosition - startPosition >= MAX_BYTES_TO_SEARCH) { throw new ParserException("Searched too many bytes while resynchronizing."); @@ -210,11 +220,11 @@ public final class Mp3Extractor implements Extractor { scratch.setPosition(0); int headerData = scratch.readInt(); int frameSize; - if ((synchronizedHeaderData != 0 - && (headerData & HEADER_MASK) != (synchronizedHeaderData & HEADER_MASK)) + if ((candidateSynchronizedHeaderData != 0 + && (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK)) || (frameSize = MpegAudioHeader.getFrameSize(headerData)) == -1) { validFrameCount = 0; - synchronizedHeaderData = 0; + candidateSynchronizedHeaderData = 0; // Try reading a header starting at the next byte. inputBuffer.returnToMark(); @@ -226,7 +236,7 @@ public final class Mp3Extractor implements Extractor { if (validFrameCount == 0) { MpegAudioHeader.populateHeader(headerData, synchronizedHeader); - synchronizedHeaderData = headerData; + candidateSynchronizedHeaderData = headerData; } // The header was valid and matching (if appropriate). Check another or end synchronization. @@ -241,6 +251,7 @@ public final class Mp3Extractor implements Extractor { // The input buffer read position is now synchronized. inputBuffer.returnToMark(); + synchronizedHeaderData = candidateSynchronizedHeaderData; if (seeker == null) { setupSeeker(extractorInput, headerPosition); extractorOutput.seekMap(seeker); From 6ae97ced3aeb9678567453899fb1b4fb6de17ff0 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:14:53 +0100 Subject: [PATCH 16/20] Support content:// URIs, and some cleanup/consistency tweaks. - There's definitely potential for more code sharing in these classes, but deferring for now. - Also made no-scheme default to file://, and allowed smoothstreaming URLs to be specified with or without the /Manifest suffix. --- .../SmoothStreamingRendererBuilder.java | 6 +- .../exoplayer/upstream/AssetDataSource.java | 21 +++---- .../exoplayer/upstream/ContentDataSource.java | 56 ++++++++----------- .../upstream/DefaultUriDataSource.java | 13 ++++- .../exoplayer/upstream/FileDataSource.java | 9 ++- 5 files changed, 53 insertions(+), 52 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java index 87d10e6d1b..ec4f35b019 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java @@ -92,8 +92,12 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { this.player = player; this.callback = callback; + String manifestUrl = url; + if (!manifestUrl.endsWith("/Manifest")) { + manifestUrl += "/Manifest"; + } SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser(); - manifestFetcher = new ManifestFetcher(url + "/Manifest", + manifestFetcher = new ManifestFetcher(manifestUrl, new DefaultHttpDataSource(userAgent, null), parser); manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this); } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/AssetDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/AssetDataSource.java index 858c674872..84642df5a0 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/AssetDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/AssetDataSource.java @@ -31,7 +31,7 @@ import java.io.InputStream; public final class AssetDataSource implements UriDataSource { /** - * Thrown when IOException is encountered during local asset read operation. + * Thrown when an {@link IOException} is encountered reading a local asset. */ public static final class AssetDataSourceException extends IOException { @@ -45,7 +45,7 @@ public final class AssetDataSource implements UriDataSource { private final TransferListener listener; private String uriString; - private InputStream assetInputStream; + private InputStream inputStream; private long bytesRemaining; private boolean opened; @@ -76,10 +76,11 @@ public final class AssetDataSource implements UriDataSource { } else if (path.startsWith("/")) { path = path.substring(1); } - assetInputStream = assetManager.open(path, AssetManager.ACCESS_RANDOM); - long skipped = assetInputStream.skip(dataSpec.position); + uriString = dataSpec.uri.toString(); + inputStream = assetManager.open(path, AssetManager.ACCESS_RANDOM); + long skipped = inputStream.skip(dataSpec.position); Assertions.checkState(skipped == dataSpec.position); - bytesRemaining = dataSpec.length == C.LENGTH_UNBOUNDED ? assetInputStream.available() + bytesRemaining = dataSpec.length == C.LENGTH_UNBOUNDED ? inputStream.available() : dataSpec.length; if (bytesRemaining < 0) { throw new EOFException(); @@ -102,8 +103,7 @@ public final class AssetDataSource implements UriDataSource { } else { int bytesRead = 0; try { - bytesRead = assetInputStream.read(buffer, offset, - (int) Math.min(bytesRemaining, readLength)); + bytesRead = inputStream.read(buffer, offset, (int) Math.min(bytesRemaining, readLength)); } catch (IOException e) { throw new AssetDataSourceException(e); } @@ -126,13 +126,14 @@ public final class AssetDataSource implements UriDataSource { @Override public void close() throws AssetDataSourceException { - if (assetInputStream != null) { + uriString = null; + if (inputStream != null) { try { - assetInputStream.close(); + inputStream.close(); } catch (IOException e) { throw new AssetDataSourceException(e); } finally { - assetInputStream = null; + inputStream = null; if (opened) { opened = false; if (listener != null) { diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/ContentDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/ContentDataSource.java index dd7b68d41d..6102bc2b2c 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/ContentDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/ContentDataSource.java @@ -15,25 +15,25 @@ */ package com.google.android.exoplayer.upstream; +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.util.Assertions; + import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; -import com.google.android.exoplayer.C; - -import java.io.FileDescriptor; +import java.io.EOFException; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; /** - * This calll is support content uri and file uri (e.q. content:// , file://). - * {@link DataSource}. + * A content URI {@link UriDataSource}. */ public final class ContentDataSource implements UriDataSource { - static private final String TAG = "ContentDataSource"; + /** - * Thrown when IOException is encountered during local asset read operation. + * Thrown when an {@link IOException} is encountered reading from a content URI. */ public static class ContentDataSourceException extends IOException { @@ -43,11 +43,11 @@ public final class ContentDataSource implements UriDataSource { } + private final ContentResolver resolver; private final TransferListener listener; private InputStream inputStream; - private Context context; - private String uri; + private String uriString; private long bytesRemaining; private boolean opened; @@ -64,23 +64,25 @@ public final class ContentDataSource implements UriDataSource { * @param listener An optional listener. Specify {@code null} for no listener. */ public ContentDataSource(Context context, TransferListener listener) { - this.context = context; + this.resolver = context.getContentResolver(); this.listener = listener; } @Override - public long open(DataSpec dataSpec) throws IOException { + public long open(DataSpec dataSpec) throws ContentDataSourceException { try { - uri = dataSpec.uri.toString(); - inputStream = new FileInputStream(getFileDescriptor(context, dataSpec)); - inputStream.skip(dataSpec.position); + uriString = dataSpec.uri.toString(); + AssetFileDescriptor assetFd = resolver.openAssetFileDescriptor(dataSpec.uri, "r"); + inputStream = new FileInputStream(assetFd.getFileDescriptor()); + long skipped = inputStream.skip(dataSpec.position); + Assertions.checkState(skipped == dataSpec.position); bytesRemaining = dataSpec.length == C.LENGTH_UNBOUNDED ? inputStream.available() : dataSpec.length; if (bytesRemaining < 0) { - throw new IOException(); + throw new EOFException(); } } catch (IOException e) { - throw new IOException(e); + throw new ContentDataSourceException(e); } opened = true; @@ -92,7 +94,7 @@ public final class ContentDataSource implements UriDataSource { } @Override - public int read(byte[] buffer, int offset, int readLength) throws IOException { + public int read(byte[] buffer, int offset, int readLength) throws ContentDataSourceException { if (bytesRemaining == 0) { return -1; } else { @@ -100,7 +102,7 @@ public final class ContentDataSource implements UriDataSource { try { bytesRead = inputStream.read(buffer, offset, (int) Math.min(bytesRemaining, readLength)); } catch (IOException e) { - throw new IOException(e); + throw new ContentDataSourceException(e); } if (bytesRead > 0) { @@ -116,11 +118,12 @@ public final class ContentDataSource implements UriDataSource { @Override public String getUri() { - return uri; + return uriString; } @Override public void close() throws ContentDataSourceException { + uriString = null; if (inputStream != null) { try { inputStream.close(); @@ -128,8 +131,6 @@ public final class ContentDataSource implements UriDataSource { throw new ContentDataSourceException(e); } finally { inputStream = null; - uri = null; - if (opened) { opened = false; if (listener != null) { @@ -140,17 +141,4 @@ public final class ContentDataSource implements UriDataSource { } } - /** - * query the fileDescriptor from conten resolver. - * - */ - private static FileDescriptor getFileDescriptor(Context context, DataSpec dataSpec) throws IOException { - try { - ContentResolver resolver = context.getContentResolver(); - AssetFileDescriptor fd = resolver.openAssetFileDescriptor(dataSpec.uri, "r"); - return fd.getFileDescriptor(); - } catch (IOException e) { - throw new IOException(e); - } - } } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultUriDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultUriDataSource.java index a2a683343f..e3cbb7b84d 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultUriDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultUriDataSource.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer.upstream; import com.google.android.exoplayer.util.Assertions; import android.content.Context; +import android.text.TextUtils; import java.io.IOException; @@ -26,8 +27,11 @@ import java.io.IOException; * *

    */ public final class DefaultUriDataSource implements UriDataSource { @@ -56,10 +60,12 @@ public final class DefaultUriDataSource implements UriDataSource { private static final String SCHEME_HTTPS = "https"; private static final String SCHEME_FILE = "file"; private static final String SCHEME_ASSET = "asset"; + private static final String SCHEME_CONTENT = "content"; private final UriDataSource httpDataSource; private final UriDataSource fileDataSource; private final UriDataSource assetDataSource; + private final UriDataSource contentDataSource; /** * {@code null} if no data source is open. Otherwise, equal to {@link #fileDataSource} if the open @@ -127,6 +133,7 @@ public final class DefaultUriDataSource implements UriDataSource { this.httpDataSource = Assertions.checkNotNull(httpDataSource); this.fileDataSource = new FileDataSource(listener); this.assetDataSource = new AssetDataSource(context, listener); + this.contentDataSource = new ContentDataSource(context, listener); } @Override @@ -136,7 +143,7 @@ public final class DefaultUriDataSource implements UriDataSource { String scheme = dataSpec.uri.getScheme(); if (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme)) { dataSource = httpDataSource; - } else if (SCHEME_FILE.equals(scheme)) { + } else if (SCHEME_FILE.equals(scheme) || TextUtils.isEmpty(scheme)) { if (dataSpec.uri.getPath().startsWith("/android_asset/")) { dataSource = assetDataSource; } else { @@ -144,6 +151,8 @@ public final class DefaultUriDataSource implements UriDataSource { } } else if (SCHEME_ASSET.equals(scheme)) { dataSource = assetDataSource; + } else if (SCHEME_CONTENT.equals(scheme)) { + dataSource = contentDataSource; } else { throw new UnsupportedSchemeException(scheme); } 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 68dd7e2a0c..784914a7ef 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 @@ -40,7 +40,7 @@ public final class FileDataSource implements UriDataSource { private final TransferListener listener; private RandomAccessFile file; - private String uri; + private String uriString; private long bytesRemaining; private boolean opened; @@ -63,7 +63,7 @@ public final class FileDataSource implements UriDataSource { @Override public long open(DataSpec dataSpec) throws FileDataSourceException { try { - uri = dataSpec.uri.toString(); + uriString = dataSpec.uri.toString(); file = new RandomAccessFile(dataSpec.uri.getPath(), "r"); file.seek(dataSpec.position); bytesRemaining = dataSpec.length == C.LENGTH_UNBOUNDED ? file.length() - dataSpec.position @@ -108,11 +108,12 @@ public final class FileDataSource implements UriDataSource { @Override public String getUri() { - return uri; + return uriString; } @Override public void close() throws FileDataSourceException { + uriString = null; if (file != null) { try { file.close(); @@ -120,8 +121,6 @@ public final class FileDataSource implements UriDataSource { throw new FileDataSourceException(e); } finally { file = null; - uri = null; - if (opened) { opened = false; if (listener != null) { From f88283d614c348edef2251c41251dfadc29505b7 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:20:27 +0100 Subject: [PATCH 17/20] Update release notes --- RELEASENOTES.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4bd15a2881..5e5d037062 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,7 +2,13 @@ ### Latest dev branch (from r1.3.1) ### -* TBC +* DataSource improvements: DefaultUriDataSource now handles `file://`, `asset://`, `http://`, + `https://` and `content://` URIs automatically. It also handles `file:///android_asset/*` URIs, + and file paths like `/path/to/media.mp4` where the scheme is not specified (`file://` is assumed + in this case). +* HLS: Fix for some ID3 events being dropped. +* HLS: Correctly handle 0x0 and floating point RESOLUTION tags. +* Mp3Extractor: robustness improvements. ### r1.3.1 ### From e55448924e65907d60f2839f36c33518137409a8 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:22:23 +0100 Subject: [PATCH 18/20] Update release notes 2 --- RELEASENOTES.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5e5d037062..128d83c77d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,10 +2,9 @@ ### Latest dev branch (from r1.3.1) ### -* DataSource improvements: DefaultUriDataSource now handles `file://`, `asset://`, `http://`, - `https://` and `content://` URIs automatically. It also handles `file:///android_asset/*` URIs, - and file paths like `/path/to/media.mp4` where the scheme is not specified (`file://` is assumed - in this case). +* DataSource improvements: `DefaultUriDataSource` now handles http://, https://, file://, asset:// + and content:// URIs automatically. It also handles `file:///android_asset/*` URIs, and file paths + like `/path/to/media.mp4` where the scheme is omitted. * HLS: Fix for some ID3 events being dropped. * HLS: Correctly handle 0x0 and floating point RESOLUTION tags. * Mp3Extractor: robustness improvements. From c86a5378c754b0223209b5fbd6227503078961be Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:23:30 +0100 Subject: [PATCH 19/20] Update release notes 3 --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 128d83c77d..630b67176b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,8 +3,8 @@ ### Latest dev branch (from r1.3.1) ### * DataSource improvements: `DefaultUriDataSource` now handles http://, https://, file://, asset:// - and content:// URIs automatically. It also handles `file:///android_asset/*` URIs, and file paths - like `/path/to/media.mp4` where the scheme is omitted. + and content:// URIs automatically. It also handles file:///android_asset/* URIs, and file paths + like /path/to/media.mp4 where the scheme is omitted. * HLS: Fix for some ID3 events being dropped. * HLS: Correctly handle 0x0 and floating point RESOLUTION tags. * Mp3Extractor: robustness improvements. From 763d68f26c641e345112c07551038488e089af2f Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 19 May 2015 14:37:29 +0100 Subject: [PATCH 20/20] Bump to 1.3.2 --- RELEASENOTES.md | 2 +- demo/src/main/AndroidManifest.xml | 4 ++-- library/build.gradle | 2 +- .../com/google/android/exoplayer/ExoPlayerLibraryInfo.java | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 630b67176b..1dfc9ac394 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,6 @@ # Release notes # -### Latest dev branch (from r1.3.1) ### +### r1.3.2 ### * DataSource improvements: `DefaultUriDataSource` now handles http://, https://, file://, asset:// and content:// URIs automatically. It also handles file:///android_asset/* URIs, and file paths diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index f774c2aacc..2c0158c23f 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -16,8 +16,8 @@ diff --git a/library/build.gradle b/library/build.gradle index da79b37a4c..674bf50de1 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -74,7 +74,7 @@ publish { userOrg = 'google' groupId = 'com.google.android.exoplayer' artifactId = 'exoplayer' - version = 'r1.3.1' + version = 'r1.3.2' description = 'The ExoPlayer library.' website = 'https://github.com/google/ExoPlayer' } diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java index 5b1eb77536..af7decdbd8 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerLibraryInfo.java @@ -26,7 +26,7 @@ public class ExoPlayerLibraryInfo { /** * The version of the library, expressed as a string. */ - public static final String VERSION = "1.3.1"; + public static final String VERSION = "1.3.2"; /** * The version of the library, expressed as an integer. @@ -34,7 +34,7 @@ public class ExoPlayerLibraryInfo { * Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the * corresponding integer version 001002003. */ - public static final int VERSION_INT = 001003001; + public static final int VERSION_INT = 001003002; /** * Whether the library was compiled with {@link com.google.android.exoplayer.util.Assertions}