To provide {@link MediaPeriod} instances for the periods in its timeline. MediaPeriods are
- * obtained by calling {@link #createPeriod(MediaPeriodId, Allocator)}, and provide a way for
- * the player to load and read the media.
+ * obtained by calling {@link #createPeriod(MediaPeriodId, Allocator, long)}, and provide a
+ * way for the player to load and read the media.
*
*
* All methods are called on the player's internal playback thread, as described in the {@link
@@ -274,9 +274,10 @@ void prepareSource(
*
* @param id The identifier of the period.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
+ * @param startPositionUs The expected start position, in microseconds.
* @return A new {@link MediaPeriod}.
*/
- MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator);
+ MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs);
/**
* Releases the period.
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java
index 573e97cb13c..cc7202f9b2d 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java
@@ -124,13 +124,13 @@ public void maybeThrowSourceInfoRefreshError() throws IOException {
}
@Override
- public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
+ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
MediaPeriod[] periods = new MediaPeriod[mediaSources.length];
int periodIndex = timelines[0].getIndexOfPeriod(id.periodUid);
for (int i = 0; i < periods.length; i++) {
MediaPeriodId childMediaPeriodId =
id.copyWithPeriodUid(timelines[i].getUidOfPeriod(periodIndex));
- periods[i] = mediaSources[i].createPeriod(childMediaPeriodId, allocator);
+ periods[i] = mediaSources[i].createPeriod(childMediaPeriodId, allocator, startPositionUs);
}
return new MergingMediaPeriod(compositeSequenceableLoaderFactory, periods);
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java
index e5b950cf2e6..ab5c5e57d9a 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java
@@ -57,6 +57,7 @@ public static final class SampleExtrasHolder {
private long largestDiscardedTimestampUs;
private long largestQueuedTimestampUs;
+ private boolean isLastSampleQueued;
private boolean upstreamKeyframeRequired;
private boolean upstreamFormatRequired;
private Format upstreamFormat;
@@ -93,6 +94,7 @@ public void reset(boolean resetUpstreamFormat) {
upstreamKeyframeRequired = true;
largestDiscardedTimestampUs = Long.MIN_VALUE;
largestQueuedTimestampUs = Long.MIN_VALUE;
+ isLastSampleQueued = false;
if (resetUpstreamFormat) {
upstreamFormat = null;
upstreamFormatRequired = true;
@@ -118,6 +120,7 @@ public long discardUpstreamSamples(int discardFromIndex) {
Assertions.checkArgument(0 <= discardCount && discardCount <= (length - readPosition));
length -= discardCount;
largestQueuedTimestampUs = Math.max(largestDiscardedTimestampUs, getLargestTimestamp(length));
+ isLastSampleQueued = discardCount == 0 && isLastSampleQueued;
if (length == 0) {
return 0;
} else {
@@ -186,6 +189,19 @@ public synchronized long getLargestQueuedTimestampUs() {
return largestQueuedTimestampUs;
}
+ /**
+ * Returns whether the last sample of the stream has knowingly been queued. A return value of
+ * {@code false} means that the last sample had not been queued or that it's unknown whether the
+ * last sample has been queued.
+ *
+ * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not
+ * considered as having been queued. Samples that were dequeued from the front of the queue are
+ * considered as having been queued.
+ */
+ public synchronized boolean isLastSampleQueued() {
+ return isLastSampleQueued;
+ }
+
/** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */
public synchronized long getFirstTimestampUs() {
return length == 0 ? Long.MIN_VALUE : timesUs[relativeFirstIndex];
@@ -224,7 +240,7 @@ public synchronized int read(FormatHolder formatHolder, DecoderInputBuffer buffe
boolean formatRequired, boolean loadingFinished, Format downstreamFormat,
SampleExtrasHolder extrasHolder) {
if (!hasNextSample()) {
- if (loadingFinished) {
+ if (loadingFinished || isLastSampleQueued) {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
} else if (upstreamFormat != null
@@ -388,7 +404,9 @@ public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlag
upstreamKeyframeRequired = false;
}
Assertions.checkState(!upstreamFormatRequired);
- commitSampleTimestamp(timeUs);
+
+ isLastSampleQueued = (sampleFlags & C.BUFFER_FLAG_LAST_SAMPLE) != 0;
+ largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs);
int relativeEndIndex = getRelativeIndex(length);
timesUs[relativeEndIndex] = timeUs;
@@ -439,10 +457,6 @@ public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlag
}
}
- public synchronized void commitSampleTimestamp(long timeUs) {
- largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs);
- }
-
/**
* Attempts to discard samples from the end of the queue to allow samples starting from the
* specified timestamp to be spliced in. Samples will not be discarded prior to the read position.
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
index ecc720c656d..0886e79d212 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
@@ -224,6 +224,15 @@ public long getLargestQueuedTimestampUs() {
return metadataQueue.getLargestQueuedTimestampUs();
}
+ /**
+ * Returns whether the last sample of the stream has knowingly been queued. A return value of
+ * {@code false} means that the last sample had not been queued or that it's unknown whether the
+ * last sample has been queued.
+ */
+ public boolean isLastSampleQueued() {
+ return metadataQueue.isLastSampleQueued();
+ }
+
/** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */
public long getFirstTimestampUs() {
return metadataQueue.getFirstTimestampUs();
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java
index 66097970c79..046672bb77a 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java
@@ -318,7 +318,7 @@ public void maybeThrowSourceInfoRefreshError() throws IOException {
}
@Override
- public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
+ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
return new SingleSampleMediaPeriod(
dataSpec,
dataSourceFactory,
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java
index 19ddbd2c542..4bf661ddc05 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java
@@ -341,7 +341,7 @@ public void prepareSourceInternal(
}
@Override
- public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
+ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
if (adPlaybackState.adGroupCount > 0 && id.isAd()) {
int adGroupIndex = id.adGroupIndex;
int adIndexInAdGroup = id.adIndexInAdGroup;
@@ -360,7 +360,8 @@ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
prepareChildSource(id, adMediaSource);
}
MediaSource mediaSource = adGroupMediaSources[adGroupIndex][adIndexInAdGroup];
- DeferredMediaPeriod deferredMediaPeriod = new DeferredMediaPeriod(mediaSource, id, allocator);
+ DeferredMediaPeriod deferredMediaPeriod =
+ new DeferredMediaPeriod(mediaSource, id, allocator, startPositionUs);
deferredMediaPeriod.setPrepareErrorListener(
new AdPrepareErrorListener(adUri, adGroupIndex, adIndexInAdGroup));
List mediaPeriods = deferredMediaPeriodByAdMediaSource.get(mediaSource);
@@ -376,7 +377,8 @@ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
}
return deferredMediaPeriod;
} else {
- DeferredMediaPeriod mediaPeriod = new DeferredMediaPeriod(contentMediaSource, id, allocator);
+ DeferredMediaPeriod mediaPeriod =
+ new DeferredMediaPeriod(contentMediaSource, id, allocator, startPositionUs);
mediaPeriod.createPeriod(id);
return mediaPeriod;
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java
index c7594995776..ab22e18358f 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java
@@ -64,11 +64,11 @@ interface Factory {
long open(DataSpec dataSpec) throws IOException;
/**
- * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at
+ * Reads up to {@code readLength} bytes of data and stores them into {@code buffer}, starting at
* index {@code offset}.
- *
- * If {@code length} is zero then 0 is returned. Otherwise, if no data is available because the
- * end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is returned.
+ *
+ *
If {@code readLength} is zero then 0 is returned. Otherwise, if no data is available because
+ * the end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is returned.
* Otherwise, the call will block until at least one byte of data has been read and the number of
* bytes read is returned.
*
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java
index 8d310015f80..63bc47504b0 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java
@@ -43,8 +43,8 @@ public final class CacheDataSink implements DataSink {
private final Cache cache;
private final long maxCacheFileSize;
private final int bufferSize;
- private final boolean syncFileDescriptor;
+ private boolean syncFileDescriptor;
private DataSpec dataSpec;
private File file;
private OutputStream outputStream;
@@ -64,18 +64,6 @@ public CacheDataSinkException(IOException cause) {
}
- /**
- * Constructs a CacheDataSink using the {@link #DEFAULT_BUFFER_SIZE}.
- *
- * @param cache The cache into which data should be written.
- * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for
- * a {@link DataSpec} whose size exceeds this value, then the data will be fragmented into
- * multiple cache files.
- */
- public CacheDataSink(Cache cache, long maxCacheFileSize) {
- this(cache, maxCacheFileSize, DEFAULT_BUFFER_SIZE, true);
- }
-
/**
* Constructs a CacheDataSink using the {@link #DEFAULT_BUFFER_SIZE}.
*
@@ -83,10 +71,9 @@ public CacheDataSink(Cache cache, long maxCacheFileSize) {
* @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for a
* {@link DataSpec} whose size exceeds this value, then the data will be fragmented into
* multiple cache files.
- * @param syncFileDescriptor Whether file descriptors are sync'd when closing output streams.
*/
- public CacheDataSink(Cache cache, long maxCacheFileSize, boolean syncFileDescriptor) {
- this(cache, maxCacheFileSize, DEFAULT_BUFFER_SIZE, syncFileDescriptor);
+ public CacheDataSink(Cache cache, long maxCacheFileSize) {
+ this(cache, maxCacheFileSize, DEFAULT_BUFFER_SIZE);
}
/**
@@ -98,23 +85,21 @@ public CacheDataSink(Cache cache, long maxCacheFileSize, boolean syncFileDescrip
* value disables buffering.
*/
public CacheDataSink(Cache cache, long maxCacheFileSize, int bufferSize) {
- this(cache, maxCacheFileSize, bufferSize, true);
+ this.cache = Assertions.checkNotNull(cache);
+ this.maxCacheFileSize = maxCacheFileSize;
+ this.bufferSize = bufferSize;
+ syncFileDescriptor = true;
}
/**
- * @param cache The cache into which data should be written.
- * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for a
- * {@link DataSpec} whose size exceeds this value, then the data will be fragmented into
- * multiple cache files.
- * @param bufferSize The buffer size in bytes for writing to a cache file. A zero or negative
- * value disables buffering.
- * @param syncFileDescriptor Whether file descriptors are sync'd when closing output streams.
+ * Sets whether file descriptors are synced when closing output streams.
+ *
+ *
This method is experimental, and will be renamed or removed in a future release. It should
+ * only be called before the renderer is used.
+ *
+ * @param syncFileDescriptor Whether file descriptors are synced when closing output streams.
*/
- public CacheDataSink(
- Cache cache, long maxCacheFileSize, int bufferSize, boolean syncFileDescriptor) {
- this.cache = Assertions.checkNotNull(cache);
- this.maxCacheFileSize = maxCacheFileSize;
- this.bufferSize = bufferSize;
+ public void experimental_setSyncFileDescriptor(boolean syncFileDescriptor) {
this.syncFileDescriptor = syncFileDescriptor;
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java b/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java
index 4bdee5ceeab..2466d5a0494 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java
@@ -29,7 +29,7 @@
* has successfully completed.
*
*
Atomic file guarantees file integrity by ensuring that a file has been completely written and
- * sync'd to disk before removing its backup. As long as the backup file exists, the original file
+ * synced to disk before removing its backup. As long as the backup file exists, the original file
* is considered to be invalid (left over from a previous attempt to write the file).
*
*
Atomic file does not confer any file locking semantics. Do not use this class when the file
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
index 40b25c2b2e4..388aa29ce94 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
@@ -1087,6 +1087,10 @@ protected CodecMaxValues getCodecMaxValues(
throws DecoderQueryException {
int maxWidth = format.width;
int maxHeight = format.height;
+ if (codecNeedsMaxVideoSizeResetWorkaround(codecInfo.name)) {
+ maxWidth = Math.max(maxWidth, 1920);
+ maxHeight = Math.max(maxHeight, 1089);
+ }
int maxInputSize = getMaxInputSize(codecInfo, format);
if (streamFormats.length == 1) {
// The single entry in streamFormats must correspond to the format for which the codec is
@@ -1274,6 +1278,18 @@ private static boolean deviceNeedsNoPostProcessWorkaround() {
return "NVIDIA".equals(Util.MANUFACTURER);
}
+ /**
+ * Returns whether the codec is known to have problems with the configuration for interlaced
+ * content and needs minimum values for the maximum video size to force reset the configuration.
+ *
+ *
See https://github.com/google/ExoPlayer/issues/5003.
+ *
+ * @param name The name of the codec.
+ */
+ private static boolean codecNeedsMaxVideoSizeResetWorkaround(String name) {
+ return "OMX.amlogic.avc.decoder.awesome".equals(name) && Util.SDK_INT <= 25;
+ }
+
/*
* TODO:
*
@@ -1322,7 +1338,8 @@ protected boolean codecNeedsSetOutputSurfaceWorkaround(String name) {
// https://github.com/google/ExoPlayer/issues/4315,
// https://github.com/google/ExoPlayer/issues/4419,
// https://github.com/google/ExoPlayer/issues/4460,
- // https://github.com/google/ExoPlayer/issues/4468.
+ // https://github.com/google/ExoPlayer/issues/4468,
+ // https://github.com/google/ExoPlayer/issues/5312.
switch (Util.DEVICE) {
case "1601":
case "1713":
@@ -1378,6 +1395,7 @@ protected boolean codecNeedsSetOutputSurfaceWorkaround(String name) {
case "HWBLN-H":
case "HWCAM-H":
case "HWVNS-H":
+ case "HWWAS-H":
case "i9031":
case "iball8735_9806":
case "Infinix-X572":
diff --git a/library/core/src/test/assets/mp4/sample.mp4.0.dump b/library/core/src/test/assets/mp4/sample.mp4.0.dump
index efc804d48b1..b05d8250ab1 100644
--- a/library/core/src/test/assets/mp4/sample.mp4.0.dump
+++ b/library/core/src/test/assets/mp4/sample.mp4.0.dump
@@ -147,7 +147,7 @@ track 0:
data = length 530, hash C98BC6A8
sample 29:
time = 934266
- flags = 0
+ flags = 536870912
data = length 568, hash 4FE5C8EA
track 1:
format:
@@ -352,6 +352,6 @@ track 1:
data = length 229, hash FFF98DF0
sample 44:
time = 1065678
- flags = 1
+ flags = 536870913
data = length 6, hash 31B22286
tracksEnded = true
diff --git a/library/core/src/test/assets/mp4/sample.mp4.1.dump b/library/core/src/test/assets/mp4/sample.mp4.1.dump
index 10104b5e81c..84d86f8ccfc 100644
--- a/library/core/src/test/assets/mp4/sample.mp4.1.dump
+++ b/library/core/src/test/assets/mp4/sample.mp4.1.dump
@@ -147,7 +147,7 @@ track 0:
data = length 530, hash C98BC6A8
sample 29:
time = 934266
- flags = 0
+ flags = 536870912
data = length 568, hash 4FE5C8EA
track 1:
format:
@@ -304,6 +304,6 @@ track 1:
data = length 229, hash FFF98DF0
sample 32:
time = 1065678
- flags = 1
+ flags = 536870913
data = length 6, hash 31B22286
tracksEnded = true
diff --git a/library/core/src/test/assets/mp4/sample.mp4.2.dump b/library/core/src/test/assets/mp4/sample.mp4.2.dump
index 8af96be673e..9bbe8caa01d 100644
--- a/library/core/src/test/assets/mp4/sample.mp4.2.dump
+++ b/library/core/src/test/assets/mp4/sample.mp4.2.dump
@@ -147,7 +147,7 @@ track 0:
data = length 530, hash C98BC6A8
sample 29:
time = 934266
- flags = 0
+ flags = 536870912
data = length 568, hash 4FE5C8EA
track 1:
format:
@@ -244,6 +244,6 @@ track 1:
data = length 229, hash FFF98DF0
sample 17:
time = 1065678
- flags = 1
+ flags = 536870913
data = length 6, hash 31B22286
tracksEnded = true
diff --git a/library/core/src/test/assets/mp4/sample.mp4.3.dump b/library/core/src/test/assets/mp4/sample.mp4.3.dump
index f1259661edd..f210f277b3c 100644
--- a/library/core/src/test/assets/mp4/sample.mp4.3.dump
+++ b/library/core/src/test/assets/mp4/sample.mp4.3.dump
@@ -147,7 +147,7 @@ track 0:
data = length 530, hash C98BC6A8
sample 29:
time = 934266
- flags = 0
+ flags = 536870912
data = length 568, hash 4FE5C8EA
track 1:
format:
@@ -184,6 +184,6 @@ track 1:
data = length 229, hash FFF98DF0
sample 2:
time = 1065678
- flags = 1
+ flags = 536870913
data = length 6, hash 31B22286
tracksEnded = true
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java
new file mode 100644
index 00000000000..2f81836540c
--- /dev/null
+++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.exoplayer2.extractor.mp4;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Test for {@link MdtaMetadataEntry}. */
+@RunWith(RobolectricTestRunner.class)
+public final class MdtaMetadataEntryTest {
+
+ @Test
+ public void testParcelable() {
+ MdtaMetadataEntry mdtaMetadataEntryToParcel =
+ new MdtaMetadataEntry("test", new byte[] {1, 2}, 3, 4);
+
+ Parcel parcel = Parcel.obtain();
+ mdtaMetadataEntryToParcel.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ MdtaMetadataEntry mdtaMetadataEntryFromParcel =
+ MdtaMetadataEntry.CREATOR.createFromParcel(parcel);
+ assertThat(mdtaMetadataEntryFromParcel).isEqualTo(mdtaMetadataEntryToParcel);
+
+ parcel.recycle();
+ }
+}
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
index 5c9a933508e..ea5193eae16 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
@@ -46,6 +46,7 @@
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -452,13 +453,22 @@ private static int[][] getGroupedAdaptationSetIndices(List adapta
if (adaptationSetSwitchingProperty == null) {
groupedAdaptationSetIndices[groupCount++] = new int[] {i};
} else {
- String[] extraAdaptationSetIds = adaptationSetSwitchingProperty.value.split(",");
+ String[] extraAdaptationSetIds = Util.split(adaptationSetSwitchingProperty.value, ",");
int[] adaptationSetIndices = new int[1 + extraAdaptationSetIds.length];
adaptationSetIndices[0] = i;
+ int outputIndex = 1;
for (int j = 0; j < extraAdaptationSetIds.length; j++) {
- int extraIndex = idToIndexMap.get(Integer.parseInt(extraAdaptationSetIds[j]));
- adaptationSetUsedFlags[extraIndex] = true;
- adaptationSetIndices[1 + j] = extraIndex;
+ int extraIndex =
+ idToIndexMap.get(
+ Integer.parseInt(extraAdaptationSetIds[j]), /* valueIfKeyNotFound= */ -1);
+ if (extraIndex != -1) {
+ adaptationSetUsedFlags[extraIndex] = true;
+ adaptationSetIndices[outputIndex] = extraIndex;
+ outputIndex++;
+ }
+ }
+ if (outputIndex < adaptationSetIndices.length) {
+ adaptationSetIndices = Arrays.copyOf(adaptationSetIndices, outputIndex);
}
groupedAdaptationSetIndices[groupCount++] = adaptationSetIndices;
}
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
index c8de8f02b10..c65bfceb39a 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java
@@ -635,7 +635,8 @@ public void maybeThrowSourceInfoRefreshError() throws IOException {
}
@Override
- public MediaPeriod createPeriod(MediaPeriodId periodId, Allocator allocator) {
+ public MediaPeriod createPeriod(
+ MediaPeriodId periodId, Allocator allocator, long startPositionUs) {
int periodIndex = (Integer) periodId.periodUid - firstPeriodId;
EventDispatcher periodEventDispatcher =
createEventDispatcher(periodId, manifest.getPeriod(periodIndex).startMs);
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
index 5e20fb769ce..3e51009f20e 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
@@ -457,10 +457,10 @@ private long getSegmentNum(
}
private ArrayList getRepresentations() {
- List manifestAdapationSets = manifest.getPeriod(periodIndex).adaptationSets;
+ List manifestAdaptationSets = manifest.getPeriod(periodIndex).adaptationSets;
ArrayList representations = new ArrayList<>();
for (int adaptationSetIndex : adaptationSetIndices) {
- representations.addAll(manifestAdapationSets.get(adaptationSetIndex).representations);
+ representations.addAll(manifestAdaptationSets.get(adaptationSetIndex).representations);
}
return representations;
}
diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
index a9b0c579ac6..2afd0416312 100644
--- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
+++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
@@ -412,7 +412,7 @@ public void maybeThrowSourceInfoRefreshError() throws IOException {
}
@Override
- public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
+ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
EventDispatcher eventDispatcher = createEventDispatcher(id);
return new HlsMediaPeriod(
extractorFactory,
diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
index 65f47961878..242711431c3 100644
--- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
+++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
@@ -360,7 +360,7 @@ private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, Stri
/* initializationData= */ null,
selectionFlags,
language);
- if (uri == null) {
+ if (isMediaTagMuxed(variants, uri)) {
muxedAudioFormat = format;
} else {
audios.add(new HlsMasterPlaylist.HlsUrl(uri, format));
@@ -766,6 +766,20 @@ private static Pattern compileBooleanAttrPattern(String attribute) {
return Pattern.compile(attribute + "=(" + BOOLEAN_FALSE + "|" + BOOLEAN_TRUE + ")");
}
+ private static boolean isMediaTagMuxed(
+ List variants, String mediaTagUri) {
+ if (mediaTagUri == null) {
+ return true;
+ }
+ // The URI attribute is defined, but it may match the uri of a variant.
+ for (int i = 0; i < variants.size(); i++) {
+ if (mediaTagUri.equals(variants.get(i).url)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static class LineIterator {
private final BufferedReader reader;
diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
index d03049efb33..9701171ce9e 100644
--- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
+++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
@@ -134,6 +134,17 @@ public class HlsMasterPlaylistParserTest {
+ "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"{$codecs}\"\n"
+ "http://example.com/{$tricky}\n";
+ private static final String PLAYLIST_WITH_MULTIPLE_MUXED_MEDIA_TAGS =
+ "#EXTM3U\n"
+ + "#EXT-X-VERSION:3\n"
+ + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"a\",NAME=\"audio_0\",DEFAULT=YES,URI=\"0/0.m3u8\"\n"
+ + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"b\",NAME=\"audio_0\",DEFAULT=YES,URI=\"1/1.m3u8\"\n"
+ + "#EXT-X-STREAM-INF:BANDWIDTH=140800,CODECS=\"mp4a.40.2\",AUDIO=\"a\"\n"
+ + "0/0.m3u8\n"
+ + "\n"
+ + "#EXT-X-STREAM-INF:BANDWIDTH=281600,CODECS=\"mp4a.40.2\",AUDIO=\"b\"\n"
+ + "1/1.m3u8\n";
+
@Test
public void testParseMasterPlaylist() throws IOException {
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
@@ -271,6 +282,14 @@ public void testVariableSubstitution() throws IOException {
assertThat(variant.url).isEqualTo("http://example.com/This/{$nested}/reference/shouldnt/work");
}
+ @Test
+ public void testMultipleMuxedMediaTags() throws IOException {
+ HlsMasterPlaylist playlistWithMultipleMuxedMediaTags =
+ parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_MULTIPLE_MUXED_MEDIA_TAGS);
+ assertThat(playlistWithMultipleMuxedMediaTags.variants).hasSize(2);
+ assertThat(playlistWithMultipleMuxedMediaTags.audios).isEmpty();
+ }
+
private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString)
throws IOException {
Uri playlistUri = Uri.parse(uri);
diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java
index 9ac376efad4..7c76dba7491 100644
--- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java
+++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java
@@ -61,14 +61,13 @@ public SsChunkSource createChunkSource(
SsManifest manifest,
int elementIndex,
TrackSelection trackSelection,
- TrackEncryptionBox[] trackEncryptionBoxes,
@Nullable TransferListener transferListener) {
DataSource dataSource = dataSourceFactory.createDataSource();
if (transferListener != null) {
dataSource.addTransferListener(transferListener);
}
- return new DefaultSsChunkSource(manifestLoaderErrorThrower, manifest, elementIndex,
- trackSelection, dataSource, trackEncryptionBoxes);
+ return new DefaultSsChunkSource(
+ manifestLoaderErrorThrower, manifest, elementIndex, trackSelection, dataSource);
}
}
@@ -90,15 +89,13 @@ public SsChunkSource createChunkSource(
* @param streamElementIndex The index of the stream element in the manifest.
* @param trackSelection The track selection.
* @param dataSource A {@link DataSource} suitable for loading the media data.
- * @param trackEncryptionBoxes Track encryption boxes for the stream.
*/
public DefaultSsChunkSource(
LoaderErrorThrower manifestLoaderErrorThrower,
SsManifest manifest,
int streamElementIndex,
TrackSelection trackSelection,
- DataSource dataSource,
- TrackEncryptionBox[] trackEncryptionBoxes) {
+ DataSource dataSource) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest;
this.streamElementIndex = streamElementIndex;
@@ -110,6 +107,8 @@ public DefaultSsChunkSource(
for (int i = 0; i < extractorWrappers.length; i++) {
int manifestTrackIndex = trackSelection.getIndexInTrackGroup(i);
Format format = streamElement.formats[manifestTrackIndex];
+ TrackEncryptionBox[] trackEncryptionBoxes =
+ format.drmInitData != null ? manifest.protectionElement.trackEncryptionBoxes : null;
int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : 0;
Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale,
C.TIME_UNSET, manifest.durationUs, format, Track.TRANSFORMATION_NONE,
diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java
index f333a6f92c3..4940f1592fe 100644
--- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java
+++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java
@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.smoothstreaming;
import android.support.annotation.Nullable;
-import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer2.source.chunk.ChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.trackselection.TrackSelection;
@@ -38,7 +37,6 @@ interface Factory {
* @param manifest The initial manifest.
* @param streamElementIndex The index of the corresponding stream element in the manifest.
* @param trackSelection The track selection.
- * @param trackEncryptionBoxes Track encryption boxes for the stream.
* @param transferListener The transfer listener which should be informed of any data transfers.
* May be null if no listener is available.
* @return The created {@link SsChunkSource}.
@@ -48,7 +46,6 @@ SsChunkSource createChunkSource(
SsManifest manifest,
int streamElementIndex,
TrackSelection trackSelection,
- TrackEncryptionBox[] trackEncryptionBoxes,
@Nullable TransferListener transferListener);
}
diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
index 14b54bc471e..d3518c0a35e 100644
--- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
+++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
@@ -29,7 +29,6 @@
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
-import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
@@ -44,8 +43,6 @@
/* package */ final class SsMediaPeriod implements MediaPeriod,
SequenceableLoader.Callback> {
- private static final int INITIALIZATION_VECTOR_SIZE = 8;
-
private final SsChunkSource.Factory chunkSourceFactory;
private final @Nullable TransferListener transferListener;
private final LoaderErrorThrower manifestLoaderErrorThrower;
@@ -53,7 +50,6 @@
private final EventDispatcher eventDispatcher;
private final Allocator allocator;
private final TrackGroupArray trackGroups;
- private final TrackEncryptionBox[] trackEncryptionBoxes;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private @Nullable Callback callback;
@@ -71,6 +67,7 @@ public SsMediaPeriod(
EventDispatcher eventDispatcher,
LoaderErrorThrower manifestLoaderErrorThrower,
Allocator allocator) {
+ this.manifest = manifest;
this.chunkSourceFactory = chunkSourceFactory;
this.transferListener = transferListener;
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
@@ -78,18 +75,7 @@ public SsMediaPeriod(
this.eventDispatcher = eventDispatcher;
this.allocator = allocator;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
-
trackGroups = buildTrackGroups(manifest);
- ProtectionElement protectionElement = manifest.protectionElement;
- if (protectionElement != null) {
- byte[] keyId = getProtectionElementKeyId(protectionElement.data);
- // We assume pattern encryption does not apply.
- trackEncryptionBoxes = new TrackEncryptionBox[] {
- new TrackEncryptionBox(true, null, INITIALIZATION_VECTOR_SIZE, keyId, 0, 0, null)};
- } else {
- trackEncryptionBoxes = null;
- }
- this.manifest = manifest;
sampleStreams = newSampleStreamArray(0);
compositeSequenceableLoader =
compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams);
@@ -229,7 +215,6 @@ private ChunkSampleStream buildSampleStream(TrackSelection select
manifest,
streamElementIndex,
selection,
- trackEncryptionBoxes,
transferListener);
return new ChunkSampleStream<>(
manifest.streamElements[streamElementIndex].type,
@@ -277,5 +262,4 @@ private static void swap(byte[] data, int firstPosition, int secondPosition) {
data[firstPosition] = data[secondPosition];
data[secondPosition] = temp;
}
-
}
diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
index 103a52a55a0..d025f8fa3a8 100644
--- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
+++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java
@@ -533,7 +533,7 @@ public void maybeThrowSourceInfoRefreshError() throws IOException {
}
@Override
- public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
+ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
EventDispatcher eventDispatcher = createEventDispatcher(id);
SsMediaPeriod period =
new SsMediaPeriod(
diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java
index 2c508f0fde4..cfb772a86bb 100644
--- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java
+++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java
@@ -18,6 +18,7 @@
import android.net.Uri;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer2.offline.FilterableManifest;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.util.Assertions;
@@ -41,10 +42,12 @@ public static class ProtectionElement {
public final UUID uuid;
public final byte[] data;
+ public final TrackEncryptionBox[] trackEncryptionBoxes;
- public ProtectionElement(UUID uuid, byte[] data) {
+ public ProtectionElement(UUID uuid, byte[] data, TrackEncryptionBox[] trackEncryptionBoxes) {
this.uuid = uuid;
this.data = data;
+ this.trackEncryptionBoxes = trackEncryptionBoxes;
}
}
diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java
index 3d5ade403ab..4c1c6ee0cc9 100644
--- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java
+++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java
@@ -25,6 +25,7 @@
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
+import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
@@ -397,9 +398,10 @@ private static class ProtectionParser extends ElementParser {
public static final String TAG = "Protection";
public static final String TAG_PROTECTION_HEADER = "ProtectionHeader";
-
public static final String KEY_SYSTEM_ID = "SystemID";
+ private static final int INITIALIZATION_VECTOR_SIZE = 8;
+
private boolean inProtectionHeader;
private UUID uuid;
private byte[] initData;
@@ -439,7 +441,44 @@ public void parseEndTag(XmlPullParser parser) {
@Override
public Object build() {
- return new ProtectionElement(uuid, PsshAtomUtil.buildPsshAtom(uuid, initData));
+ return new ProtectionElement(
+ uuid, PsshAtomUtil.buildPsshAtom(uuid, initData), buildTrackEncryptionBoxes(initData));
+ }
+
+ private static TrackEncryptionBox[] buildTrackEncryptionBoxes(byte[] initData) {
+ return new TrackEncryptionBox[] {
+ new TrackEncryptionBox(
+ /* isEncrypted= */ true,
+ /* schemeType= */ null,
+ INITIALIZATION_VECTOR_SIZE,
+ getProtectionElementKeyId(initData),
+ /* defaultEncryptedBlocks= */ 0,
+ /* defaultClearBlocks= */ 0,
+ /* defaultInitializationVector= */ null)
+ };
+ }
+
+ private static byte[] getProtectionElementKeyId(byte[] initData) {
+ StringBuilder initDataStringBuilder = new StringBuilder();
+ for (int i = 0; i < initData.length; i += 2) {
+ initDataStringBuilder.append((char) initData[i]);
+ }
+ String initDataString = initDataStringBuilder.toString();
+ String keyIdString =
+ initDataString.substring(
+ initDataString.indexOf("") + 5, initDataString.indexOf(""));
+ byte[] keyId = Base64.decode(keyIdString, Base64.DEFAULT);
+ swap(keyId, 0, 3);
+ swap(keyId, 1, 2);
+ swap(keyId, 4, 5);
+ swap(keyId, 6, 7);
+ return keyId;
+ }
+
+ private static void swap(byte[] data, int firstPosition, int secondPosition) {
+ byte temp = data[firstPosition];
+ data[firstPosition] = data[secondPosition];
+ data[secondPosition] = temp;
}
private static String stripCurlyBraces(String uuidString) {
diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java
index 5125beff1c1..88830dde6af 100644
--- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java
+++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java
@@ -25,6 +25,7 @@
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
+import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions;
@@ -42,7 +43,7 @@ public final class SsDownloadHelper extends DownloadHelper {
private @MonotonicNonNull SsManifest manifest;
public SsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) {
- this.uri = uri;
+ this.uri = SsUtil.fixManifestUri(uri);;
this.manifestDataSourceFactory = manifestDataSourceFactory;
}
diff --git a/library/smoothstreaming/src/test/assets/sample_ismc_1 b/library/smoothstreaming/src/test/assets/sample_ismc_1
index 25a37d65b4f..1d279d0a67c 100644
--- a/library/smoothstreaming/src/test/assets/sample_ismc_1
+++ b/library/smoothstreaming/src/test/assets/sample_ismc_1
@@ -3,7 +3,7 @@
Duration="2300000000" TimeScale="10000000">
-
+ fgMAAAEAAQB0AzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQgBhAFUATQBPAEcAYwBzAGgAVQBDAEQAZAB3ADMANABZAGMAawBmAFoAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBnADcATgBhAFIARABJAEkATwA5ADAAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAcwA6AC8ALwBUAC0ATwBOAEwASQBOAEUALgBEAFUATQBNAFkALQBTAEUAUgBWAEUAUgAvAEEAcgB0AGUAbQBpAHMATABpAGMAZQBuAHMAZQBTAGUAcgB2AGUAcgAvAFAAbABhAHkAUgBlAGEAZAB5AE0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwAQwBJAEQAPgAxADcANQA4ADIANgA8AC8AQwBJAEQAPgA8AEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4ANwAuADEALgAxADUANgA1AC4ANAA8AC8ASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA8AC8AQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A
diff --git a/library/smoothstreaming/src/test/assets/sample_ismc_2 b/library/smoothstreaming/src/test/assets/sample_ismc_2
index 5875a181835..7f2a53036f0 100644
--- a/library/smoothstreaming/src/test/assets/sample_ismc_2
+++ b/library/smoothstreaming/src/test/assets/sample_ismc_2
@@ -3,7 +3,7 @@
Duration="2300000000" TimeScale="10000000">
-
+ fgMAAAEAAQB0AzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQgBhAFUATQBPAEcAYwBzAGgAVQBDAEQAZAB3ADMANABZAGMAawBmAFoAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBnADcATgBhAFIARABJAEkATwA5ADAAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAcwA6AC8ALwBUAC0ATwBOAEwASQBOAEUALgBEAFUATQBNAFkALQBTAEUAUgBWAEUAUgAvAEEAcgB0AGUAbQBpAHMATABpAGMAZQBuAHMAZQBTAGUAcgB2AGUAcgAvAFAAbABhAHkAUgBlAGEAZAB5AE0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwAQwBJAEQAPgAxADcANQA4ADIANgA8AC8AQwBJAEQAPgA8AEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4ANwAuADEALgAxADUANgA1AC4ANAA8AC8ASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA8AC8AQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A
diff --git a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java
index dc8d6754f59..b692d94c186 100644
--- a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java
+++ b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java
@@ -19,6 +19,7 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
@@ -36,7 +37,7 @@
public class SsManifestTest {
private static final ProtectionElement DUMMY_PROTECTION_ELEMENT =
- new ProtectionElement(C.WIDEVINE_UUID, new byte[] {0, 1, 2});
+ new ProtectionElement(C.WIDEVINE_UUID, new byte[0], new TrackEncryptionBox[0]);
@Test
public void testCopy() throws Exception {
diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java
index 8c7c507f924..da2081db31d 100644
--- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java
+++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java
@@ -137,23 +137,40 @@ protected String getPlayerStateString() {
/** Returns a string containing video debugging information. */
protected String getVideoString() {
Format format = player.getVideoFormat();
- if (format == null) {
+ DecoderCounters decoderCounters = player.getVideoDecoderCounters();
+ if (format == null || decoderCounters == null) {
return "";
}
- return "\n" + format.sampleMimeType + "(id:" + format.id + " r:" + format.width + "x"
- + format.height + getPixelAspectRatioString(format.pixelWidthHeightRatio)
- + getDecoderCountersBufferCountString(player.getVideoDecoderCounters()) + ")";
+ return "\n"
+ + format.sampleMimeType
+ + "(id:"
+ + format.id
+ + " r:"
+ + format.width
+ + "x"
+ + format.height
+ + getPixelAspectRatioString(format.pixelWidthHeightRatio)
+ + getDecoderCountersBufferCountString(decoderCounters)
+ + ")";
}
/** Returns a string containing audio debugging information. */
protected String getAudioString() {
Format format = player.getAudioFormat();
- if (format == null) {
+ DecoderCounters decoderCounters = player.getAudioDecoderCounters();
+ if (format == null || decoderCounters == null) {
return "";
}
- return "\n" + format.sampleMimeType + "(id:" + format.id + " hz:" + format.sampleRate + " ch:"
+ return "\n"
+ + format.sampleMimeType
+ + "(id:"
+ + format.id
+ + " hz:"
+ + format.sampleRate
+ + " ch:"
+ format.channelCount
- + getDecoderCountersBufferCountString(player.getAudioDecoderCounters()) + ")";
+ + getDecoderCountersBufferCountString(decoderCounters)
+ + ")";
}
private static String getDecoderCountersBufferCountString(DecoderCounters counters) {
diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
index 47025d9bba1..7cbe52d4046 100644
--- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
+++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java
@@ -125,6 +125,18 @@ public interface MediaDescriptionAdapter {
@Nullable
String getCurrentContentText(Player player);
+ /**
+ * Gets the content sub text for the current media item.
+ *
+ * See {@link NotificationCompat.Builder#setSubText(CharSequence)}.
+ *
+ * @param player The {@link Player} for which a notification is being built.
+ */
+ @Nullable
+ default String getCurrentSubText(Player player) {
+ return null;
+ }
+
/**
* Gets the large icon for the current media item.
*
@@ -832,6 +844,7 @@ protected Notification createNotification(Player player, @Nullable Bitmap largeI
// Set media specific notification properties from MediaDescriptionAdapter.
builder.setContentTitle(mediaDescriptionAdapter.getCurrentContentTitle(player));
builder.setContentText(mediaDescriptionAdapter.getCurrentContentText(player));
+ builder.setSubText(mediaDescriptionAdapter.getCurrentSubText(player));
if (largeIcon == null) {
largeIcon =
mediaDescriptionAdapter.getCurrentLargeIcon(
diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
index 88eabfed074..83f5b70cbb3 100644
--- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
+++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
@@ -679,8 +679,9 @@ public void setShutterBackgroundColor(int color) {
/**
* Sets whether the currently displayed video frame or media artwork is kept visible when the
* player is reset. A player reset is defined to mean the player being re-prepared with different
- * media, {@link Player#stop(boolean)} being called with {@code reset=true}, or the player being
- * replaced or cleared by calling {@link #setPlayer(Player)}.
+ * media, the player transitioning to unprepared media, {@link Player#stop(boolean)} being called
+ * with {@code reset=true}, or the player being replaced or cleared by calling {@link
+ * #setPlayer(Player)}.
*
*
If enabled, the currently displayed video frame or media artwork will be kept visible until
* the player set on the view has been successfully prepared with new media and loaded enough of
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java
index 1f0c0c1a406..999372b90ac 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java
@@ -116,7 +116,7 @@ public void maybeThrowSourceInfoRefreshError() throws IOException {
}
@Override
- public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
+ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
assertThat(preparedSource).isTrue();
assertThat(releasedSource).isFalse();
int periodIndex = timeline.getIndexOfPeriod(id.periodUid);
diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java
index 70e7669dfb2..e6fb5bc5f38 100644
--- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java
+++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java
@@ -142,15 +142,28 @@ public Timeline prepareSource() throws IOException {
}
/**
- * Calls {@link MediaSource#createPeriod(MediaSource.MediaPeriodId, Allocator)} on the playback
- * thread, asserting that a non-null {@link MediaPeriod} is returned.
+ * Calls {@link MediaSource#createPeriod(MediaSource.MediaPeriodId, Allocator, long)} with a zero
+ * start position on the playback thread, asserting that a non-null {@link MediaPeriod} is
+ * returned.
*
* @param periodId The id of the period to create.
* @return The created {@link MediaPeriod}.
*/
public MediaPeriod createPeriod(final MediaPeriodId periodId) {
+ return createPeriod(periodId, /* startPositionUs= */ 0);
+ }
+
+ /**
+ * Calls {@link MediaSource#createPeriod(MediaSource.MediaPeriodId, Allocator, long)} on the
+ * playback thread, asserting that a non-null {@link MediaPeriod} is returned.
+ *
+ * @param periodId The id of the period to create.
+ * @return The created {@link MediaPeriod}.
+ */
+ public MediaPeriod createPeriod(final MediaPeriodId periodId, long startPositionUs) {
final MediaPeriod[] holder = new MediaPeriod[1];
- runOnPlaybackThread(() -> holder[0] = mediaSource.createPeriod(periodId, allocator));
+ runOnPlaybackThread(
+ () -> holder[0] = mediaSource.createPeriod(periodId, allocator, startPositionUs));
assertThat(holder[0]).isNotNull();
return holder[0];
}