From b44bf32f8b21d133ad86d4d6c8ea254f2aac12e3 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 1 Feb 2018 09:32:21 -0800 Subject: [PATCH 01/53] Handle repeat mode/shuffle mode changes in MediaPeriodQueue This should be a no-op change. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184150266 --- .../exoplayer2/ExoPlayerImplInternal.java | 65 +++++------------- .../android/exoplayer2/MediaPeriodQueue.java | 67 ++++++++++++++----- 2 files changed, 67 insertions(+), 65 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 977620c5de..c6e0460d54 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -416,54 +416,29 @@ import java.util.Collections; private void setRepeatModeInternal(@Player.RepeatMode int repeatMode) throws ExoPlaybackException { this.repeatMode = repeatMode; - queue.setRepeatMode(repeatMode); - validateExistingPeriodHolders(); + if (!queue.updateRepeatMode(repeatMode)) { + seekToCurrentPosition(/* sendDiscontinuity= */ true); + } } private void setShuffleModeEnabledInternal(boolean shuffleModeEnabled) throws ExoPlaybackException { this.shuffleModeEnabled = shuffleModeEnabled; - queue.setShuffleModeEnabled(shuffleModeEnabled); - validateExistingPeriodHolders(); + if (!queue.updateShuffleModeEnabled(shuffleModeEnabled)) { + seekToCurrentPosition(/* sendDiscontinuity= */ true); + } } - private void validateExistingPeriodHolders() throws ExoPlaybackException { - // Find the last existing period holder that matches the new period order. - MediaPeriodHolder lastValidPeriodHolder = queue.getFrontPeriod(); - if (lastValidPeriodHolder == null) { - return; - } - while (true) { - int nextPeriodIndex = playbackInfo.timeline.getNextPeriodIndex( - lastValidPeriodHolder.info.id.periodIndex, period, window, repeatMode, - shuffleModeEnabled); - while (lastValidPeriodHolder.next != null - && !lastValidPeriodHolder.info.isLastInTimelinePeriod) { - lastValidPeriodHolder = lastValidPeriodHolder.next; - } - if (nextPeriodIndex == C.INDEX_UNSET || lastValidPeriodHolder.next == null - || lastValidPeriodHolder.next.info.id.periodIndex != nextPeriodIndex) { - break; - } - lastValidPeriodHolder = lastValidPeriodHolder.next; - } - - // Release any period holders that don't match the new period order. - boolean readingPeriodRemoved = queue.removeAfter(lastValidPeriodHolder); - - // Update the period info for the last holder, as it may now be the last period in the timeline. - lastValidPeriodHolder.info = queue.getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info); - - if (readingPeriodRemoved && queue.hasPlayingPeriod()) { - // Renderers may have read from a period that's been removed. Seek back to the current - // position of the playing period to make sure none of the removed period is played. - MediaPeriodId periodId = queue.getPlayingPeriod().info.id; - long newPositionUs = - seekToPeriodPosition( - periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true); - if (newPositionUs != playbackInfo.positionUs) { - playbackInfo = - playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs); + private void seekToCurrentPosition(boolean sendDiscontinuity) throws ExoPlaybackException { + // Renderers may have read from a period that's been removed. Seek back to the current + // position of the playing period to make sure none of the removed period is played. + MediaPeriodId periodId = queue.getPlayingPeriod().info.id; + long newPositionUs = + seekToPeriodPosition(periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true); + if (newPositionUs != playbackInfo.positionUs) { + playbackInfo = + playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs); + if (sendDiscontinuity) { playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); } } @@ -1271,13 +1246,7 @@ import java.util.Collections; // The holder is inconsistent with the new timeline. boolean readingPeriodRemoved = queue.removeAfter(previousPeriodHolder); if (readingPeriodRemoved) { - // Renderers may have read from a period that's been removed. Seek back to the current - // position of the playing period to make sure none of the removed period is played. - MediaPeriodId id = queue.getPlayingPeriod().info.id; - long newPositionUs = - seekToPeriodPosition(id, playbackInfo.positionUs, /* forceDisableRenderers= */ true); - playbackInfo = - playbackInfo.fromNewPosition(id, newPositionUs, playbackInfo.contentPositionUs); + seekToCurrentPosition(/* sendDiscontinuity= */ false); } break; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 8bc93ae243..65048795e6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.util.Assertions; * loading media period at the end of the queue, with methods for controlling loading and updating * the queue. Also has a reference to the media period currently being read. */ +@SuppressWarnings("UngroupedOverloads") /* package */ final class MediaPeriodQueue { /** @@ -66,19 +67,21 @@ import com.google.android.exoplayer2.util.Assertions; } /** - * Sets the {@link RepeatMode}. Call {@link #getUpdatedMediaPeriodInfo} to update period - * information taking into account the new repeat mode. + * Sets the {@link RepeatMode} and returns whether the repeat mode change has been fully handled. + * If not, it is necessary to seek to the current playback position. */ - public void setRepeatMode(@RepeatMode int repeatMode) { + public boolean updateRepeatMode(@RepeatMode int repeatMode) { this.repeatMode = repeatMode; + return updateForPlaybackModeChange(); } /** - * Sets whether shuffling is enabled. Call {@link #getUpdatedMediaPeriodInfo} to update period - * information taking into account the shuffle mode. + * Sets whether shuffling is enabled and returns whether the shuffle mode change has been fully + * handled. If not, it is necessary to seek to the current playback position. */ - public void setShuffleModeEnabled(boolean shuffleModeEnabled) { + public boolean updateShuffleModeEnabled(boolean shuffleModeEnabled) { this.shuffleModeEnabled = shuffleModeEnabled; + return updateForPlaybackModeChange(); } /** Returns whether {@code mediaPeriod} is the current loading media period. */ @@ -286,17 +289,6 @@ import com.google.android.exoplayer2.util.Assertions; length = 0; } - /** - * Returns new media period info based on specified {@code mediaPeriodInfo} but taking into - * account the current timeline. - * - * @param mediaPeriodInfo Media period info for a media period based on an old timeline. - * @return The updated media period info for the current timeline. - */ - public MediaPeriodInfo getUpdatedMediaPeriodInfo(MediaPeriodInfo mediaPeriodInfo) { - return getUpdatedMediaPeriodInfo(mediaPeriodInfo, mediaPeriodInfo.id); - } - /** * Returns new media period info based on specified {@code mediaPeriodInfo} but taking into * account the current timeline, and with the period index updated to {@code newPeriodIndex}. @@ -333,6 +325,47 @@ import com.google.android.exoplayer2.util.Assertions; // Internal methods. + /** + * Updates the queue for any playback mode change, and returns whether the change was fully + * handled. If not, it is necessary to seek to the current playback position. + */ + private boolean updateForPlaybackModeChange() { + // Find the last existing period holder that matches the new period order. + MediaPeriodHolder lastValidPeriodHolder = getFrontPeriod(); + if (lastValidPeriodHolder == null) { + return true; + } + while (true) { + int nextPeriodIndex = + timeline.getNextPeriodIndex( + lastValidPeriodHolder.info.id.periodIndex, + period, + window, + repeatMode, + shuffleModeEnabled); + while (lastValidPeriodHolder.next != null + && !lastValidPeriodHolder.info.isLastInTimelinePeriod) { + lastValidPeriodHolder = lastValidPeriodHolder.next; + } + if (nextPeriodIndex == C.INDEX_UNSET + || lastValidPeriodHolder.next == null + || lastValidPeriodHolder.next.info.id.periodIndex != nextPeriodIndex) { + break; + } + lastValidPeriodHolder = lastValidPeriodHolder.next; + } + + // Release any period holders that don't match the new period order. + boolean readingPeriodRemoved = removeAfter(lastValidPeriodHolder); + + // Update the period info for the last holder, as it may now be the last period in the timeline. + lastValidPeriodHolder.info = + getUpdatedMediaPeriodInfo(lastValidPeriodHolder.info, lastValidPeriodHolder.info.id); + + // If renderers may have read from a period that's been removed, it is necessary to restart. + return !readingPeriodRemoved || !hasPlayingPeriod(); + } + /** * Returns the first {@link MediaPeriodInfo} to play, based on the specified playback position. */ From c03f326274a9ce8051f9fd918d3d35b996d04e50 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 2 Feb 2018 05:52:30 -0800 Subject: [PATCH 02/53] Update internal opus build ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184274139 --- .../com/google/android/exoplayer2/ext/opus/OpusLibrary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java index 22985ea497..4cb3ce3190 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusLibrary.java @@ -27,7 +27,7 @@ public final class OpusLibrary { ExoPlayerLibraryInfo.registerModule("goog.exo.opus"); } - private static final LibraryLoader LOADER = new LibraryLoader("opus", "opusJNI"); + private static final LibraryLoader LOADER = new LibraryLoader("opusJNI"); private OpusLibrary() {} From 258122c89cca09467b14d264073dd315d9166aee Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 2 Feb 2018 09:41:31 -0800 Subject: [PATCH 03/53] Interrupt the test thread for uncaught errors This makes assertion errors in code running on the Looper less easy to miss. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184294290 --- .../com/google/android/exoplayer2/RobolectricUtil.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/RobolectricUtil.java b/library/core/src/test/java/com/google/android/exoplayer2/RobolectricUtil.java index af2a0ef8c6..0b5740b72c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/RobolectricUtil.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/RobolectricUtil.java @@ -117,7 +117,14 @@ public final class RobolectricUtil { } } if (!isRemoved) { - target.dispatchMessage(pendingMessage.message); + try { + target.dispatchMessage(pendingMessage.message); + } catch (Throwable t) { + // Interrupt the main thread to terminate the test. Robolectric's HandlerThread will + // print the rethrown error to standard output. + Looper.getMainLooper().getThread().interrupt(); + throw t; + } } } if (Util.SDK_INT >= 21) { From 8bc5febbb8e922defb8463b03c0f0572477afce0 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 2 Feb 2018 10:09:24 -0800 Subject: [PATCH 04/53] Only override gapless data if set in ClippingMediaPeriod This avoids reading a format that is not equal because of switching between NO_VALUE and 0. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184298076 --- .../android/exoplayer2/source/ClippingMediaPeriod.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java index 3f2c5ec894..f14c0faad4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java @@ -292,11 +292,13 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb } int result = childStream.readData(formatHolder, buffer, requireFormat); if (result == C.RESULT_FORMAT_READ) { - // Clear gapless playback metadata if the start/end points don't match the media. Format format = formatHolder.format; - int encoderDelay = startUs != 0 ? 0 : format.encoderDelay; - int encoderPadding = endUs != C.TIME_END_OF_SOURCE ? 0 : format.encoderPadding; - formatHolder.format = format.copyWithGaplessInfo(encoderDelay, encoderPadding); + if (format.encoderDelay != Format.NO_VALUE || format.encoderPadding != Format.NO_VALUE) { + // Clear gapless playback metadata if the start/end points don't match the media. + int encoderDelay = startUs != 0 ? 0 : format.encoderDelay; + int encoderPadding = endUs != C.TIME_END_OF_SOURCE ? 0 : format.encoderPadding; + formatHolder.format = format.copyWithGaplessInfo(encoderDelay, encoderPadding); + } return C.RESULT_FORMAT_READ; } if (endUs != C.TIME_END_OF_SOURCE From 1a6c8c69346e3328217f6c26d7164c16c20d4353 Mon Sep 17 00:00:00 2001 From: anjalibh Date: Fri, 2 Feb 2018 12:16:54 -0800 Subject: [PATCH 05/53] Reduce Libvpx output buffers to 8, to reduce the chances of out of memory errors. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184317120 --- .../google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index 101f05cd82..d93aa6d39e 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -98,7 +98,7 @@ public class LibvpxVideoRenderer extends BaseRenderer { * The number of output buffers. The renderer may limit the minimum possible value due to * requiring multiple output buffers to be dequeued at a time for it to make progress. */ - private static final int NUM_OUTPUT_BUFFERS = 16; + private static final int NUM_OUTPUT_BUFFERS = 8; /** * The initial input buffer size. Input buffers are reallocated dynamically if this value is * insufficient. From 2d76d63c3e9adde31c35100ea71196b061adafd1 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 5 Feb 2018 02:30:25 -0800 Subject: [PATCH 06/53] Refer to E-AC3 JOC rather than Atmos in MIME type/comments ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184501752 --- .../java/com/google/android/exoplayer2/audio/Ac3Util.java | 4 ++-- .../android/exoplayer2/mediacodec/MediaCodecUtil.java | 8 ++++---- .../com/google/android/exoplayer2/util/MimeTypes.java | 6 +++--- .../source/dash/manifest/DashManifestParser.java | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index 5797e73740..f45a6a11c6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -198,7 +198,7 @@ public final class Ac3Util { if (data.bytesLeft() > 0) { nextByte = data.readUnsignedByte(); if ((nextByte & 0x01) != 0) { // flag_ec3_extension_type_a - mimeType = MimeTypes.AUDIO_ATMOS; + mimeType = MimeTypes.AUDIO_E_AC3_JOC; } } return Format.createAudioSampleFormat(trackId, mimeType, null, Format.NO_VALUE, @@ -385,7 +385,7 @@ public final class Ac3Util { if (data.readBit()) { // addbsie int addbsil = data.readBits(6); if (addbsil == 1 && data.readBits(8) == 1) { // addbsi - mimeType = MimeTypes.AUDIO_ATMOS; + mimeType = MimeTypes.AUDIO_E_AC3_JOC; } } } else /* is AC-3 */ { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index 7ae8eb3cd4..b80780884c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -158,8 +158,8 @@ public final class MediaCodecUtil { + ". Assuming: " + decoderInfos.get(0).name); } } - if (MimeTypes.AUDIO_ATMOS.equals(mimeType)) { - // E-AC3 decoders can decode Atmos streams, but in 2-D rather than 3-D. + if (MimeTypes.AUDIO_E_AC3_JOC.equals(mimeType)) { + // E-AC3 decoders can decode JOC streams, but in 2-D rather than 3-D. CodecKey eac3Key = new CodecKey(MimeTypes.AUDIO_E_AC3, key.secure); ArrayList eac3DecoderInfos = getDecoderInfosInternal(eac3Key, mediaCodecList, mimeType); @@ -382,8 +382,8 @@ public final class MediaCodecUtil { return false; } - // MTK E-AC3 decoder doesn't support decoding Atmos streams in 2-D. See [Internal: b/69400041]. - if (MimeTypes.AUDIO_ATMOS.equals(requestedMimeType) + // MTK E-AC3 decoder doesn't support decoding JOC streams in 2-D. See [Internal: b/69400041]. + if (MimeTypes.AUDIO_E_AC3_JOC.equals(requestedMimeType) && "OMX.MTK.AUDIO.DECODER.DSPAC3".equals(name)) { return false; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index 3e65a754e2..f39f897567 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -52,7 +52,7 @@ public final class MimeTypes { public static final String AUDIO_MLAW = BASE_TYPE_AUDIO + "/g711-mlaw"; public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; public static final String AUDIO_E_AC3 = BASE_TYPE_AUDIO + "/eac3"; - public static final String AUDIO_ATMOS = BASE_TYPE_AUDIO + "/eac3-joc"; + public static final String AUDIO_E_AC3_JOC = BASE_TYPE_AUDIO + "/eac3-joc"; public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd"; public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/vnd.dts"; public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd"; @@ -200,7 +200,7 @@ public final class MimeTypes { } else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) { return MimeTypes.AUDIO_E_AC3; } else if (codec.startsWith("ec+3")) { - return MimeTypes.AUDIO_ATMOS; + return MimeTypes.AUDIO_E_AC3_JOC; } else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) { return MimeTypes.AUDIO_DTS; } else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) { @@ -258,7 +258,7 @@ public final class MimeTypes { case MimeTypes.AUDIO_AC3: return C.ENCODING_AC3; case MimeTypes.AUDIO_E_AC3: - case MimeTypes.AUDIO_ATMOS: + case MimeTypes.AUDIO_E_AC3_JOC: return C.ENCODING_E_AC3; case MimeTypes.AUDIO_DTS: return C.ENCODING_DTS; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 1b1e24005b..1416e9beeb 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -1072,7 +1072,7 @@ public class DashManifestParser extends DefaultHandler String schemeIdUri = descriptor.schemeIdUri; if ("tag:dolby.com,2014:dash:DolbyDigitalPlusExtensionType:2014".equals(schemeIdUri) && "ec+3".equals(descriptor.value)) { - return MimeTypes.AUDIO_ATMOS; + return MimeTypes.AUDIO_E_AC3_JOC; } } return MimeTypes.AUDIO_E_AC3; From b210c20e8405ff5d9ec95a1c74b0b777694b7f8e Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 5 Feb 2018 02:36:34 -0800 Subject: [PATCH 07/53] Fix not thread safe static buffer usage DefaultExtractorInput.SCRATCH_SPACE buffer is used to skip data by reading it into this buffer and discarding. Simultaneous use of skip methods corrupts this buffer. Normally the read data is discarded so it doesn't matter but the underlying DataSource may use the buffer too. If it's a CacheDataSource it uses this buffer to read data from upstream then write to cache. Issue: #3762 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184502170 --- .../exoplayer2/extractor/DefaultExtractorInput.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java index 87355a6c78..c3f6304091 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java @@ -30,8 +30,9 @@ public final class DefaultExtractorInput implements ExtractorInput { private static final int PEEK_MIN_FREE_SPACE_AFTER_RESIZE = 64 * 1024; private static final int PEEK_MAX_FREE_SPACE = 512 * 1024; - private static final byte[] SCRATCH_SPACE = new byte[4096]; + private static final int SCRATCH_SPACE_SIZE = 4096; + private final byte[] scratchSpace; private final DataSource dataSource; private final long streamLength; @@ -50,6 +51,7 @@ public final class DefaultExtractorInput implements ExtractorInput { this.position = position; this.streamLength = length; peekBuffer = new byte[PEEK_MIN_FREE_SPACE_AFTER_RESIZE]; + scratchSpace = new byte[SCRATCH_SPACE_SIZE]; } @Override @@ -84,7 +86,7 @@ public final class DefaultExtractorInput implements ExtractorInput { int bytesSkipped = skipFromPeekBuffer(length); if (bytesSkipped == 0) { bytesSkipped = - readFromDataSource(SCRATCH_SPACE, 0, Math.min(length, SCRATCH_SPACE.length), 0, true); + readFromDataSource(scratchSpace, 0, Math.min(length, scratchSpace.length), 0, true); } commitBytesRead(bytesSkipped); return bytesSkipped; @@ -95,8 +97,9 @@ public final class DefaultExtractorInput implements ExtractorInput { throws IOException, InterruptedException { int bytesSkipped = skipFromPeekBuffer(length); while (bytesSkipped < length && bytesSkipped != C.RESULT_END_OF_INPUT) { - bytesSkipped = readFromDataSource(SCRATCH_SPACE, -bytesSkipped, - Math.min(length, bytesSkipped + SCRATCH_SPACE.length), bytesSkipped, allowEndOfInput); + int minLength = Math.min(length, bytesSkipped + scratchSpace.length); + bytesSkipped = + readFromDataSource(scratchSpace, -bytesSkipped, minLength, bytesSkipped, allowEndOfInput); } commitBytesRead(bytesSkipped); return bytesSkipped != C.RESULT_END_OF_INPUT; From 4775b52741854110cebd00c98d0b07f03b846083 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 5 Feb 2018 05:15:40 -0800 Subject: [PATCH 08/53] Fix media period queue updating for ads Resolve the media period for ad playback when resolving a subsequent period and when receiving a timeline where the playing period in range (but wasn't before). Fix the seek position calculation when a current ad must be skipped and is followed by another ad. Check MediaPeriodInfos match when checking MediaPeriodHolders, to handle cases where a future ad should no longer be played. This may involve playing two content media periods consecutively. Issue: #3584 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184514558 --- .../source/ConcatenatingMediaSourceTest.java | 12 +- .../DynamicConcatenatingMediaSourceTest.java | 12 +- .../exoplayer2/ExoPlayerImplInternal.java | 86 ++++------- .../android/exoplayer2/MediaPeriodInfo.java | 5 +- .../android/exoplayer2/MediaPeriodQueue.java | 142 ++++++++++++------ .../android/exoplayer2/PlaybackInfo.java | 7 +- .../source/ads/AdPlaybackState.java | 4 +- .../android/exoplayer2/ExoPlayerTest.java | 52 +++++++ .../exoplayer2/testutil/FakeTimeline.java | 70 +++++---- 9 files changed, 249 insertions(+), 141 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index 14f6563cda..5b3b684ebb 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -212,8 +212,16 @@ public final class ConcatenatingMediaSourceTest extends TestCase { // Create media source with ad child source. Timeline timelineContentOnly = new FakeTimeline( new TimelineWindowDefinition(2, 111, true, false, 10 * C.MICROS_PER_SECOND)); - Timeline timelineWithAds = new FakeTimeline( - new TimelineWindowDefinition(2, 222, true, false, 10 * C.MICROS_PER_SECOND, 1, 1)); + Timeline timelineWithAds = + new FakeTimeline( + new TimelineWindowDefinition( + 2, + 222, + true, + false, + 10 * C.MICROS_PER_SECOND, + FakeTimeline.createAdPlaybackState( + /* adsPerAdGroup= */ 1, /* adGroupTimesUs= */ 0))); FakeMediaSource mediaSourceContentOnly = new FakeMediaSource(timelineContentOnly, null); FakeMediaSource mediaSourceWithAds = new FakeMediaSource(timelineWithAds, null); ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(mediaSourceContentOnly, diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java index af4b149c98..5198cde72e 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java @@ -597,8 +597,16 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { // Create dynamic media source with ad child source. Timeline timelineContentOnly = new FakeTimeline( new TimelineWindowDefinition(2, 111, true, false, 10 * C.MICROS_PER_SECOND)); - Timeline timelineWithAds = new FakeTimeline( - new TimelineWindowDefinition(2, 222, true, false, 10 * C.MICROS_PER_SECOND, 1, 1)); + Timeline timelineWithAds = + new FakeTimeline( + new TimelineWindowDefinition( + 2, + 222, + true, + false, + 10 * C.MICROS_PER_SECOND, + FakeTimeline.createAdPlaybackState( + /* adsPerAdGroup= */ 1, /* adGroupTimesUs= */ 0))); FakeMediaSource mediaSourceContentOnly = new FakeMediaSource(timelineContentOnly, null); FakeMediaSource mediaSourceWithAds = new FakeMediaSource(timelineWithAds, null); mediaSource.addMediaSource(mediaSourceContentOnly); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index c6e0460d54..e05068a7b3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -1145,8 +1145,9 @@ import java.util.Collections; int periodIndex = periodPosition.first; long positionUs = periodPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, positionUs); - playbackInfo = playbackInfo.fromNewPosition(periodId, periodId.isAd() ? 0 : positionUs, - positionUs); + playbackInfo = + playbackInfo.fromNewPosition( + periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs); } } else if (playbackInfo.startPositionUs == C.TIME_UNSET) { if (timeline.isEmpty()) { @@ -1157,18 +1158,30 @@ import java.util.Collections; int periodIndex = defaultPosition.first; long startPositionUs = defaultPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, startPositionUs); - playbackInfo = playbackInfo.fromNewPosition(periodId, - periodId.isAd() ? 0 : startPositionUs, startPositionUs); + playbackInfo = + playbackInfo.fromNewPosition( + periodId, + periodId.isAd() ? 0 : startPositionUs, + /* contentPositionUs= */ startPositionUs); } } return; } int playingPeriodIndex = playbackInfo.periodId.periodIndex; - MediaPeriodHolder periodHolder = queue.getFrontPeriod(); - if (periodHolder == null && playingPeriodIndex >= oldTimeline.getPeriodCount()) { + long contentPositionUs = playbackInfo.contentPositionUs; + if (oldTimeline.isEmpty()) { + // If the old timeline is empty, the period queue is also empty. + if (!timeline.isEmpty()) { + MediaPeriodId periodId = + queue.resolveMediaPeriodIdForAds(playingPeriodIndex, contentPositionUs); + playbackInfo = + playbackInfo.fromNewPosition( + periodId, periodId.isAd() ? 0 : contentPositionUs, contentPositionUs); + } return; } + MediaPeriodHolder periodHolder = queue.getFrontPeriod(); Object playingPeriodUid = periodHolder == null ? oldTimeline.getPeriod(playingPeriodIndex, period, true).uid : periodHolder.uid; int periodIndex = timeline.getIndexOfPeriod(playingPeriodUid); @@ -1185,7 +1198,8 @@ import java.util.Collections; Pair defaultPosition = getPeriodPosition(timeline, timeline.getPeriod(newPeriodIndex, period).windowIndex, C.TIME_UNSET); newPeriodIndex = defaultPosition.first; - long newPositionUs = defaultPosition.second; + contentPositionUs = defaultPosition.second; + MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(newPeriodIndex, contentPositionUs); timeline.getPeriod(newPeriodIndex, period, true); if (periodHolder != null) { // Clear the index of each holder that doesn't contain the default position. If a holder @@ -1202,9 +1216,8 @@ import java.util.Collections; } } // Actually do the seek. - MediaPeriodId periodId = new MediaPeriodId(newPeriodIndex); - newPositionUs = seekToPeriodPosition(periodId, newPositionUs); - playbackInfo = playbackInfo.fromNewPosition(periodId, newPositionUs, C.TIME_UNSET); + long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs); + playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs); return; } @@ -1213,53 +1226,20 @@ import java.util.Collections; playbackInfo = playbackInfo.copyWithPeriodIndex(periodIndex); } - if (playbackInfo.periodId.isAd()) { - // Check that the playing ad hasn't been marked as played. If it has, skip forward. - MediaPeriodId periodId = - queue.resolveMediaPeriodIdForAds(periodIndex, playbackInfo.contentPositionUs); - if (!periodId.isAd() || periodId.adIndexInAdGroup != playbackInfo.periodId.adIndexInAdGroup) { - long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.contentPositionUs); - long contentPositionUs = periodId.isAd() ? playbackInfo.contentPositionUs : C.TIME_UNSET; - playbackInfo = playbackInfo.fromNewPosition(periodId, newPositionUs, contentPositionUs); + MediaPeriodId playingPeriodId = playbackInfo.periodId; + if (playingPeriodId.isAd()) { + MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodIndex, contentPositionUs); + if (!periodId.equals(playingPeriodId)) { + // The previously playing ad should no longer be played, so skip it. + long seekPositionUs = + seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs); + playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs); return; } } - if (periodHolder == null) { - // We don't have any period holders, so we're done. - return; - } - - // Update the holder indices. If we find a subsequent holder that's inconsistent with the new - // timeline then take appropriate action. - periodHolder = updatePeriodInfo(periodHolder, periodIndex); - while (periodHolder.next != null) { - MediaPeriodHolder previousPeriodHolder = periodHolder; - periodHolder = periodHolder.next; - periodIndex = timeline.getNextPeriodIndex(periodIndex, period, window, repeatMode, - shuffleModeEnabled); - if (periodIndex != C.INDEX_UNSET - && periodHolder.uid.equals(timeline.getPeriod(periodIndex, period, true).uid)) { - // The holder is consistent with the new timeline. Update its index and continue. - periodHolder = updatePeriodInfo(periodHolder, periodIndex); - } else { - // The holder is inconsistent with the new timeline. - boolean readingPeriodRemoved = queue.removeAfter(previousPeriodHolder); - if (readingPeriodRemoved) { - seekToCurrentPosition(/* sendDiscontinuity= */ false); - } - break; - } - } - } - - private MediaPeriodHolder updatePeriodInfo(MediaPeriodHolder periodHolder, int periodIndex) { - while (true) { - periodHolder.info = queue.getUpdatedMediaPeriodInfo(periodHolder.info, periodIndex); - if (periodHolder.info.isLastInTimelinePeriod || periodHolder.next == null) { - return periodHolder; - } - periodHolder = periodHolder.next; + if (!queue.updateQueuedPeriods(playingPeriodId, rendererPositionUs)) { + seekToCurrentPosition(/* sendDiscontinuity= */ false); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java index a415f9f0a7..fce1780b71 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java @@ -36,8 +36,9 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; */ public final long contentPositionUs; /** - * The duration of the media to play within the media period, in microseconds, or {@link - * C#TIME_UNSET} if not known. + * The duration of the media period, like {@link #endPositionUs} but with {@link + * C#TIME_END_OF_SOURCE} resolved to the timeline period duration. May be {@link C#TIME_UNSET} if + * the end position is not known. */ public final long durationUs; /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 65048795e6..0c643ec120 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -59,8 +59,8 @@ import com.google.android.exoplayer2.util.Assertions; } /** - * Sets the {@link Timeline}. Call {@link #getUpdatedMediaPeriodInfo} to update period information - * taking into account the new timeline. + * Sets the {@link Timeline}. Call {@link #updateQueuedPeriods(MediaPeriodId, long)} to update the + * queued media periods to take into account the new timeline. */ public void setTimeline(Timeline timeline) { this.timeline = timeline; @@ -121,8 +121,7 @@ import com.google.android.exoplayer2.util.Assertions; long rendererPositionUs, PlaybackInfo playbackInfo) { return loading == null ? getFirstMediaPeriodInfo(playbackInfo) - : getFollowingMediaPeriodInfo( - loading.info, loading.getRendererOffset(), rendererPositionUs); + : getFollowingMediaPeriodInfo(loading, rendererPositionUs); } /** @@ -289,6 +288,61 @@ import com.google.android.exoplayer2.util.Assertions; length = 0; } + /** + * Updates media periods in the queue to take into account the latest timeline, and returns + * whether the timeline change has been fully handled. If not, it is necessary to seek to the + * current playback position. + * + * @param playingPeriodId The current playing media period identifier. + * @param rendererPositionUs The current renderer position in microseconds. + * @return Whether the timeline change has been handled completely. + */ + public boolean updateQueuedPeriods(MediaPeriodId playingPeriodId, long rendererPositionUs) { + // TODO: Merge this into setTimeline so that the queue gets updated as soon as the new timeline + // is set, once all cases handled by ExoPlayerImplInternal.handleSourceInfoRefreshed can be + // handled here. + int periodIndex = playingPeriodId.periodIndex; + // The front period is either playing now, or is being loaded and will become the playing + // period. + MediaPeriodHolder previousPeriodHolder = null; + MediaPeriodHolder periodHolder = getFrontPeriod(); + while (periodHolder != null) { + if (previousPeriodHolder == null) { + periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info, periodIndex); + } else { + // Check this period holder still follows the previous one, based on the new timeline. + MediaPeriodInfo periodInfo = + getFollowingMediaPeriodInfo(previousPeriodHolder, rendererPositionUs); + if (periodInfo == null) { + // We've loaded a next media period that is not in the new timeline. + return !removeAfter(previousPeriodHolder); + } + // Update the period index. + periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info, periodIndex); + // Check the media period information matches the new timeline. + if (!canKeepMediaPeriodHolder(periodHolder, periodInfo)) { + return !removeAfter(previousPeriodHolder); + } + } + + if (periodHolder.info.isLastInTimelinePeriod) { + // Move on to the next timeline period, if there is one. + periodIndex = + timeline.getNextPeriodIndex( + periodIndex, period, window, repeatMode, shuffleModeEnabled); + if (periodIndex == C.INDEX_UNSET + || !periodHolder.uid.equals(timeline.getPeriod(periodIndex, period, true).uid)) { + // The holder is inconsistent with the new timeline. + return previousPeriodHolder == null || !removeAfter(previousPeriodHolder); + } + } + + previousPeriodHolder = periodHolder; + periodHolder = periodHolder.next; + } + return true; + } + /** * Returns new media period info based on specified {@code mediaPeriodInfo} but taking into * account the current timeline, and with the period index updated to {@code newPeriodIndex}. @@ -325,6 +379,17 @@ import com.google.android.exoplayer2.util.Assertions; // Internal methods. + /** + * Returns whether {@code periodHolder} can be kept for playing the media period described by + * {@code info}. + */ + private boolean canKeepMediaPeriodHolder(MediaPeriodHolder periodHolder, MediaPeriodInfo info) { + MediaPeriodInfo periodHolderInfo = periodHolder.info; + return periodHolderInfo.startPositionUs == info.startPositionUs + && periodHolderInfo.endPositionUs == info.endPositionUs + && periodHolderInfo.id.equals(info.id); + } + /** * Updates the queue for any playback mode change, and returns whether the change was fully * handled. If not, it is necessary to seek to the current playback position. @@ -375,28 +440,25 @@ import com.google.android.exoplayer2.util.Assertions; } /** - * Returns the {@link MediaPeriodInfo} following {@code currentMediaPeriodInfo}. + * Returns the {@link MediaPeriodInfo} for the media period following {@code mediaPeriodHolder}'s + * media period. * - * @param currentMediaPeriodInfo The current media period info. - * @param rendererOffsetUs The current renderer offset in microseconds. + * @param mediaPeriodHolder The media period holder. * @param rendererPositionUs The current renderer position in microseconds. - * @return The following media period info, or {@code null} if it is not yet possible to get the + * @return The following media period's info, or {@code null} if it is not yet possible to get the * next media period info. */ - private MediaPeriodInfo getFollowingMediaPeriodInfo( - MediaPeriodInfo currentMediaPeriodInfo, long rendererOffsetUs, long rendererPositionUs) { + private @Nullable MediaPeriodInfo getFollowingMediaPeriodInfo( + MediaPeriodHolder mediaPeriodHolder, long rendererPositionUs) { // TODO: This method is called repeatedly from ExoPlayerImplInternal.maybeUpdateLoadingPeriod // but if the timeline is not ready to provide the next period it can't return a non-null value // until the timeline is updated. Store whether the next timeline period is ready when the // timeline is updated, to avoid repeatedly checking the same timeline. - if (currentMediaPeriodInfo.isLastInTimelinePeriod) { + MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info; + if (mediaPeriodInfo.isLastInTimelinePeriod) { int nextPeriodIndex = timeline.getNextPeriodIndex( - currentMediaPeriodInfo.id.periodIndex, - period, - window, - repeatMode, - shuffleModeEnabled); + mediaPeriodInfo.id.periodIndex, period, window, repeatMode, shuffleModeEnabled); if (nextPeriodIndex == C.INDEX_UNSET) { // We can't create a next period yet. return null; @@ -411,7 +473,7 @@ import com.google.android.exoplayer2.util.Assertions; // interruptions). Hence we project the default start position forward by the duration of // the buffer, and start buffering from this point. long defaultPositionProjectionUs = - rendererOffsetUs + currentMediaPeriodInfo.durationUs - rendererPositionUs; + mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs; Pair defaultPosition = timeline.getPeriodPosition( window, @@ -431,10 +493,10 @@ import com.google.android.exoplayer2.util.Assertions; return getMediaPeriodInfo(periodId, startPositionUs, startPositionUs); } - MediaPeriodId currentPeriodId = currentMediaPeriodInfo.id; + MediaPeriodId currentPeriodId = mediaPeriodInfo.id; + timeline.getPeriod(currentPeriodId.periodIndex, period); if (currentPeriodId.isAd()) { int currentAdGroupIndex = currentPeriodId.adGroupIndex; - timeline.getPeriod(currentPeriodId.periodIndex, period); int adCountInCurrentAdGroup = period.getAdCountInAdGroup(currentAdGroupIndex); if (adCountInCurrentAdGroup == C.LENGTH_UNSET) { return null; @@ -448,29 +510,24 @@ import com.google.android.exoplayer2.util.Assertions; currentPeriodId.periodIndex, currentAdGroupIndex, nextAdIndexInAdGroup, - currentMediaPeriodInfo.contentPositionUs); + mediaPeriodInfo.contentPositionUs); } else { // Play content from the ad group position. - int nextAdGroupIndex = - period.getAdGroupIndexAfterPositionUs(currentMediaPeriodInfo.contentPositionUs); - long endUs = - nextAdGroupIndex == C.INDEX_UNSET - ? C.TIME_END_OF_SOURCE - : period.getAdGroupTimeUs(nextAdGroupIndex); return getMediaPeriodInfoForContent( - currentPeriodId.periodIndex, currentMediaPeriodInfo.contentPositionUs, endUs); + currentPeriodId.periodIndex, mediaPeriodInfo.contentPositionUs); } - } else if (currentMediaPeriodInfo.endPositionUs != C.TIME_END_OF_SOURCE) { + } else if (mediaPeriodInfo.endPositionUs != C.TIME_END_OF_SOURCE) { // Play the next ad group if it's available. - int nextAdGroupIndex = - period.getAdGroupIndexForPositionUs(currentMediaPeriodInfo.endPositionUs); + int nextAdGroupIndex = period.getAdGroupIndexForPositionUs(mediaPeriodInfo.endPositionUs); + if (nextAdGroupIndex == C.INDEX_UNSET) { + // The next ad group can't be played. Play content from the ad group position instead. + return getMediaPeriodInfoForContent( + currentPeriodId.periodIndex, mediaPeriodInfo.endPositionUs); + } return !period.isAdAvailable(nextAdGroupIndex, 0) ? null : getMediaPeriodInfoForAd( - currentPeriodId.periodIndex, - nextAdGroupIndex, - 0, - currentMediaPeriodInfo.endPositionUs); + currentPeriodId.periodIndex, nextAdGroupIndex, 0, mediaPeriodInfo.endPositionUs); } else { // Check if the postroll ad should be played. int adGroupCount = period.getAdGroupCount(); @@ -516,12 +573,7 @@ import com.google.android.exoplayer2.util.Assertions; return getMediaPeriodInfoForAd( id.periodIndex, id.adGroupIndex, id.adIndexInAdGroup, contentPositionUs); } else { - int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); - long endUs = - nextAdGroupIndex == C.INDEX_UNSET - ? C.TIME_END_OF_SOURCE - : period.getAdGroupTimeUs(nextAdGroupIndex); - return getMediaPeriodInfoForContent(id.periodIndex, startPositionUs, endUs); + return getMediaPeriodInfoForContent(id.periodIndex, startPositionUs); } } @@ -548,12 +600,16 @@ import com.google.android.exoplayer2.util.Assertions; isLastInTimeline); } - private MediaPeriodInfo getMediaPeriodInfoForContent( - int periodIndex, long startPositionUs, long endUs) { + private MediaPeriodInfo getMediaPeriodInfoForContent(int periodIndex, long startPositionUs) { MediaPeriodId id = new MediaPeriodId(periodIndex); + timeline.getPeriod(id.periodIndex, period); + int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); + long endUs = + nextAdGroupIndex == C.INDEX_UNSET + ? C.TIME_END_OF_SOURCE + : period.getAdGroupTimeUs(nextAdGroupIndex); boolean isLastInPeriod = isLastInPeriod(id, endUs); boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); - timeline.getPeriod(id.periodIndex, period); long durationUs = endUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endUs; return new MediaPeriodInfo( id, startPositionUs, endUs, C.TIME_UNSET, durationUs, isLastInPeriod, isLastInTimeline); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 65392ba269..bb39bf3d0b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -70,11 +70,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; this.trackSelectorResult = trackSelectorResult; } - public PlaybackInfo fromNewPosition(int periodIndex, long startPositionUs, - long contentPositionUs) { - return fromNewPosition(new MediaPeriodId(periodIndex), startPositionUs, contentPositionUs); - } - public PlaybackInfo fromNewPosition(MediaPeriodId periodId, long startPositionUs, long contentPositionUs) { return new PlaybackInfo( @@ -82,7 +77,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; manifest, periodId, startPositionUs, - contentPositionUs, + periodId.isAd() ? contentPositionUs : C.TIME_UNSET, playbackState, isLoading, trackSelectorResult); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 0bd6c9f29f..7b06098d45 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -211,7 +211,7 @@ public final class AdPlaybackState { public static final int AD_STATE_ERROR = 4; /** Ad playback state with no ads. */ - public static final AdPlaybackState NONE = new AdPlaybackState(new long[0]); + public static final AdPlaybackState NONE = new AdPlaybackState(); /** The number of ad groups. */ public final int adGroupCount; @@ -233,7 +233,7 @@ public final class AdPlaybackState { * @param adGroupTimesUs The times of ad groups in microseconds. A final element with the value * {@link C#TIME_END_OF_SOURCE} indicates that there is a postroll ad. */ - public AdPlaybackState(long[] adGroupTimesUs) { + public AdPlaybackState(long... adGroupTimesUs) { int count = adGroupTimesUs.length; adGroupCount = count; this.adGroupTimesUs = Arrays.copyOf(adGroupTimesUs, count); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 3855d5ed2a..f10e889390 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget; @@ -384,6 +385,57 @@ public final class ExoPlayerTest { assertThat(renderer.isEnded).isTrue(); } + @Test + public void testAdGroupWithLoadErrorIsSkipped() throws Exception { + AdPlaybackState initialAdPlaybackState = + FakeTimeline.createAdPlaybackState( + /* adsPerAdGroup= */ 1, /* adGroupTimesUs= */ 5 * C.MICROS_PER_SECOND); + Timeline fakeTimeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ C.MICROS_PER_SECOND, + initialAdPlaybackState)); + AdPlaybackState errorAdPlaybackState = initialAdPlaybackState.withAdLoadError(0, 0); + final Timeline adErrorTimeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + /* id= */ 0, + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ C.MICROS_PER_SECOND, + errorAdPlaybackState)); + final FakeMediaSource fakeMediaSource = + new FakeMediaSource(fakeTimeline, /* manifest= */ null, Builder.VIDEO_FORMAT); + ActionSchedule actionSchedule = + new ActionSchedule.Builder("testAdGroupWithLoadErrorIsSkipped") + .pause() + .waitForPlaybackState(Player.STATE_READY) + .executeRunnable( + new Runnable() { + @Override + public void run() { + fakeMediaSource.setNewSourceInfo(adErrorTimeline, null); + } + }) + .waitForTimelineChanged(adErrorTimeline) + .play() + .build(); + ExoPlayerTestRunner testRunner = + new ExoPlayerTestRunner.Builder() + .setMediaSource(fakeMediaSource) + .setActionSchedule(actionSchedule) + .build() + .start() + .blockUntilEnded(TIMEOUT_MS); + // There is still one discontinuity from content to content for the failed ad insertion. + testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_AD_INSERTION); + } + @Test public void testPeriodHoldersReleasedAfterSeekWithRepeatModeAll() throws Exception { FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java index d0aa4761a4..7b27d3bd80 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java @@ -40,8 +40,7 @@ public final class FakeTimeline extends Timeline { public final boolean isSeekable; public final boolean isDynamic; public final long durationUs; - public final int adGroupsPerPeriodCount; - public final int adsPerAdGroupCount; + public final AdPlaybackState adPlaybackState; /** * Creates a seekable, non-dynamic window definition with one period with a duration of @@ -86,7 +85,7 @@ public final class FakeTimeline extends Timeline { */ public TimelineWindowDefinition(int periodCount, Object id, boolean isSeekable, boolean isDynamic, long durationUs) { - this(periodCount, id, isSeekable, isDynamic, durationUs, 0, 0); + this(periodCount, id, isSeekable, isDynamic, durationUs, AdPlaybackState.NONE); } /** @@ -98,19 +97,21 @@ public final class FakeTimeline extends Timeline { * @param isSeekable Whether the window is seekable. * @param isDynamic Whether the window is dynamic. * @param durationUs The duration of the window in microseconds. - * @param adGroupsCountPerPeriod The number of ad groups in each period. The position of the ad - * groups is equally distributed in each period starting. - * @param adsPerAdGroupCount The number of ads in each ad group. + * @param adPlaybackState The ad playback state. */ - public TimelineWindowDefinition(int periodCount, Object id, boolean isSeekable, - boolean isDynamic, long durationUs, int adGroupsCountPerPeriod, int adsPerAdGroupCount) { + public TimelineWindowDefinition( + int periodCount, + Object id, + boolean isSeekable, + boolean isDynamic, + long durationUs, + AdPlaybackState adPlaybackState) { this.periodCount = periodCount; this.id = id; this.isSeekable = isSeekable; this.isDynamic = isDynamic; this.durationUs = durationUs; - this.adGroupsPerPeriodCount = adGroupsCountPerPeriod; - this.adsPerAdGroupCount = adsPerAdGroupCount; + this.adPlaybackState = adPlaybackState; } } @@ -120,6 +121,27 @@ public final class FakeTimeline extends Timeline { private final TimelineWindowDefinition[] windowDefinitions; private final int[] periodOffsets; + /** + * Returns an ad playback state with the specified number of ads in each of the specified ad + * groups, each ten seconds long. + * + * @param adsPerAdGroup The number of ads per ad group. + * @param adGroupTimesUs The times of ad groups, in microseconds. + * @return The ad playback state. + */ + public static AdPlaybackState createAdPlaybackState(int adsPerAdGroup, long... adGroupTimesUs) { + int adGroupCount = adGroupTimesUs.length; + AdPlaybackState adPlaybackState = new AdPlaybackState(adGroupTimesUs); + long[][] adDurationsUs = new long[adGroupCount][]; + for (int i = 0; i < adGroupCount; i++) { + adPlaybackState = adPlaybackState.withAdCount(i, adsPerAdGroup); + adDurationsUs[i] = new long[adsPerAdGroup]; + Arrays.fill(adDurationsUs[i], AD_DURATION_US); + } + adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs); + return adPlaybackState; + } + /** * Creates a fake timeline with the given number of seekable, non-dynamic windows with one period * with a duration of {@link TimelineWindowDefinition#DEFAULT_WINDOW_DURATION_US} each. @@ -173,27 +195,13 @@ public final class FakeTimeline extends Timeline { Object uid = setIds ? Pair.create(windowDefinition.id, windowPeriodIndex) : null; long periodDurationUs = windowDefinition.durationUs / windowDefinition.periodCount; long positionInWindowUs = periodDurationUs * windowPeriodIndex; - if (windowDefinition.adGroupsPerPeriodCount == 0) { - return period.set(id, uid, windowIndex, periodDurationUs, positionInWindowUs); - } else { - int adGroups = windowDefinition.adGroupsPerPeriodCount; - long[] adGroupTimesUs = new long[adGroups]; - long adGroupOffset = adGroups > 1 ? periodDurationUs / (adGroups - 1) : 0; - for (int i = 0; i < adGroups; i++) { - adGroupTimesUs[i] = i * adGroupOffset; - } - AdPlaybackState adPlaybackState = new AdPlaybackState(adGroupTimesUs); - long[][] adDurationsUs = new long[adGroups][]; - for (int i = 0; i < adGroups; i++) { - int adCount = windowDefinition.adsPerAdGroupCount; - adPlaybackState = adPlaybackState.withAdCount(i, adCount); - adDurationsUs[i] = new long[adCount]; - Arrays.fill(adDurationsUs[i], AD_DURATION_US); - } - adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs); - return period.set( - id, uid, windowIndex, periodDurationUs, positionInWindowUs, adPlaybackState); - } + return period.set( + id, + uid, + windowIndex, + periodDurationUs, + positionInWindowUs, + windowDefinition.adPlaybackState); } @Override From 7b19de2e994bb292b9d491f18cb3bc43a46f20ee Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 5 Feb 2018 05:38:54 -0800 Subject: [PATCH 09/53] Handle LOG AdEvents for ad group load errors. If IMA loads an empty VAST document for an ad group it notifies via a LOG AdEvent. Handle the event by updating the AdPlaybackState accordingly. The error state will be handled in ExoPlayerImplInternal in a separate change. Issue: #3584 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184516585 --- RELEASENOTES.md | 2 + demos/main/src/main/assets/media.exolist.json | 10 ++++ .../exoplayer2/ext/ima/ImaAdsLoader.java | 49 +++++++++++++------ 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 01ddda93f8..1e5d47b044 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -81,6 +81,8 @@ * CacheDataSource: Check periodically if it's possible to read from/write to cache after deciding to bypass cache. * IMA extension: + * Fix the player getting stuck when an ad group fails to load + ([#3584](https://github.com/google/ExoPlayer/issues/3584)). * Work around loadAd not being called beore the LOADED AdEvent arrives ([#3552](https://github.com/google/ExoPlayer/issues/3552)). * Add support for playing non-Extractor content MediaSources in diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 15183a4a8b..7052e7c436 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -566,6 +566,16 @@ "name": "VMAP pre-roll single ad, mid-roll standard pods with 5 ads every 10 seconds for 1:40, post-roll single ad", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostlongpod&cmsid=496&vid=short_tencue&correlator=" + }, + { + "name": "VMAP empty midroll", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", + "ad_tag_uri": "http://vastsynthesizer.appspot.com/empty-midroll" + }, + { + "name": "VMAP full, empty, full midrolls", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", + "ad_tag_uri": "http://vastsynthesizer.appspot.com/empty-midroll-2" } ] } diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index b632e7ba84..493deed4ad 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -516,6 +516,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A case LOG: Map adData = adEvent.getAdData(); Log.i(TAG, "Log AdEvent: " + adData); + if ("adLoadError".equals(adData.get("type"))) { + handleAdGroupLoadError(); + } break; case ALL_ADS_COMPLETED: default: @@ -894,6 +897,23 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A } } + private void handleAdGroupLoadError() { + AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[expectedAdGroupIndex]; + // Ad group load error can be notified more than once, so check if it was already handled. + // TODO: Update the expected ad group index based on the position returned by + // getContentProgress so that it's possible to detect when more than one ad group fails to load + // consecutively. + if (adGroup.count == C.LENGTH_UNSET + || adGroup.states[0] == AdPlaybackState.AD_STATE_UNAVAILABLE) { + if (DEBUG) { + Log.d(TAG, "Removing ad group " + expectedAdGroupIndex + " as it failed to load"); + } + adPlaybackState = adPlaybackState.withAdCount(expectedAdGroupIndex, 1); + adPlaybackState = adPlaybackState.withAdLoadError(expectedAdGroupIndex, 0); + updateAdPlaybackState(); + } + } + private void checkForContentComplete() { if (contentDurationMs != C.TIME_UNSET && pendingContentPositionMs == C.TIME_UNSET && player.getContentPosition() + END_OF_CONTENT_POSITION_THRESHOLD_MS >= contentDurationMs @@ -939,6 +959,21 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A return adGroupTimesUs.length == 0 ? C.INDEX_UNSET : (adGroupTimesUs.length - 1); } + /** + * Returns the next ad index in the specified ad group to load, or {@link C#INDEX_UNSET} if all + * ads in the ad group have loaded. + */ + private int getAdIndexInAdGroupToLoad(int adGroupIndex) { + @AdState int[] states = adPlaybackState.adGroups[adGroupIndex].states; + int adIndexInAdGroup = 0; + // IMA loads ads in order. + while (adIndexInAdGroup < states.length + && states[adIndexInAdGroup] != AdPlaybackState.AD_STATE_UNAVAILABLE) { + adIndexInAdGroup++; + } + return adIndexInAdGroup == states.length ? C.INDEX_UNSET : adIndexInAdGroup; + } + private static long[] getAdGroupTimesUs(List cuePoints) { if (cuePoints.isEmpty()) { // If no cue points are specified, there is a preroll ad. @@ -955,18 +990,4 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A return adGroupTimesUs; } - /** - * Returns the next ad index in the specified ad group to load, or {@link C#INDEX_UNSET} if all - * ads in the ad group have loaded. - */ - private int getAdIndexInAdGroupToLoad(int adGroupIndex) { - @AdState int[] states = adPlaybackState.adGroups[adGroupIndex].states; - int adIndexInAdGroup = 0; - // IMA loads ads in order. - while (adIndexInAdGroup < states.length - && states[adIndexInAdGroup] != AdPlaybackState.AD_STATE_UNAVAILABLE) { - adIndexInAdGroup++; - } - return adIndexInAdGroup == states.length ? C.INDEX_UNSET : adIndexInAdGroup; - } } From fe98477045097a75efe1abf01ab4d5c51668870e Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 5 Feb 2018 06:48:48 -0800 Subject: [PATCH 10/53] Fix potential media source release before media period release. This could happen when a media source is removed from a DynamicConcatenatingMediaSource and one of its media periods is still active. This media period is only removed by the player after the player received a timeline update and thus we shouldn't release the removed child source as long as it has active media periods. Issue:#3796 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184522836 --- .../DynamicConcatenatingMediaSourceTest.java | 12 +++++++++++ .../DynamicConcatenatingMediaSource.java | 21 +++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java index 5198cde72e..1a1c07fd61 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java @@ -627,6 +627,18 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0)); } + public void testRemoveChildSourceWithActiveMediaPeriod() throws IOException { + FakeMediaSource childSource = createFakeMediaSource(); + mediaSource.addMediaSource(childSource); + testRunner.prepareSource(); + MediaPeriod mediaPeriod = testRunner.createPeriod(new MediaPeriodId(/* periodIndex= */ 0)); + mediaSource.removeMediaSource(/* index= */ 0); + testRunner.assertTimelineChangeBlocking(); + testRunner.releasePeriod(mediaPeriod); + childSource.assertReleased(); + testRunner.releaseSource(); + } + private static FakeMediaSource[] createMediaSources(int count) { FakeMediaSource[] sources = new FakeMediaSource[count]; for (int i = 0; i < count; i++) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java index a638992501..7ed8af5954 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java @@ -56,7 +56,7 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< // Accessed on the playback thread. private final List mediaSourceHolders; private final MediaSourceHolder query; - private final Map mediaSourceByMediaPeriod; + private final Map mediaSourceByMediaPeriod; private final List deferredMediaPeriods; private ExoPlayer player; @@ -355,19 +355,23 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< } else { mediaPeriod = holder.mediaSource.createPeriod(idInSource, allocator); } - mediaSourceByMediaPeriod.put(mediaPeriod, holder.mediaSource); + mediaSourceByMediaPeriod.put(mediaPeriod, holder); + holder.activeMediaPeriods++; return mediaPeriod; } @Override public void releasePeriod(MediaPeriod mediaPeriod) { - MediaSource mediaSource = mediaSourceByMediaPeriod.get(mediaPeriod); - mediaSourceByMediaPeriod.remove(mediaPeriod); + MediaSourceHolder holder = mediaSourceByMediaPeriod.remove(mediaPeriod); if (mediaPeriod instanceof DeferredMediaPeriod) { deferredMediaPeriods.remove(mediaPeriod); ((DeferredMediaPeriod) mediaPeriod).releasePeriod(); } else { - mediaSource.releasePeriod(mediaPeriod); + holder.mediaSource.releasePeriod(mediaPeriod); + } + holder.activeMediaPeriods--; + if (holder.activeMediaPeriods == 0 && holder.isRemoved) { + releaseChildSource(holder); } } @@ -520,7 +524,10 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< /* childIndexUpdate= */ -1, -oldTimeline.getWindowCount(), -oldTimeline.getPeriodCount()); - releaseChildSource(holder); + holder.isRemoved = true; + if (holder.activeMediaPeriods == 0) { + releaseChildSource(holder); + } } private void moveMediaSourceInternal(int currentIndex, int newIndex) { @@ -573,6 +580,8 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< public int firstWindowIndexInChild; public int firstPeriodIndexInChild; public boolean isPrepared; + public boolean isRemoved; + public int activeMediaPeriods; public MediaSourceHolder( MediaSource mediaSource, From 784e8a634477542dab41e6f90a05bf67965a4c7c Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 5 Feb 2018 07:34:31 -0800 Subject: [PATCH 11/53] Support isAtomic flag in DynamicConcatenatingMediaSource. This feature is supported in the ConcatenatingMediaSource and is easily copied to this media source. Also adding tests to check whether the atomic property works in normal concatenation and in also in nested use. Also fixes a bug where timeline methods of the DeferredTimeline were not correctly forwarded. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184526881 --- .../DynamicConcatenatingMediaSourceTest.java | 89 +++++++++++- .../source/AbstractConcatenatedTimeline.java | 22 ++- .../source/ConcatenatingMediaSource.java | 33 +---- .../DynamicConcatenatingMediaSource.java | 131 ++++++++++++------ .../exoplayer2/source/LoopingMediaSource.java | 2 +- 5 files changed, 197 insertions(+), 80 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java index 1a1c07fd61..38ac324e69 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java @@ -47,7 +47,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { @Override public void setUp() throws Exception { super.setUp(); - mediaSource = new DynamicConcatenatingMediaSource(new FakeShuffleOrder(0)); + mediaSource = + new DynamicConcatenatingMediaSource(/* isAtomic= */ false, new FakeShuffleOrder(0)); testRunner = new MediaSourceTestRunner(mediaSource, null); } @@ -627,6 +628,92 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0)); } + public void testAtomicTimelineWindowOrder() throws IOException { + // Release default test runner with non-atomic media source and replace with new test runner. + testRunner.release(); + DynamicConcatenatingMediaSource mediaSource = + new DynamicConcatenatingMediaSource(/* isAtomic= */ true, new FakeShuffleOrder(0)); + testRunner = new MediaSourceTestRunner(mediaSource, null); + mediaSource.addMediaSources(Arrays.asList(createMediaSources(3))); + Timeline timeline = testRunner.prepareSource(); + TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); + TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true, C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false, 2, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true, 2, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false, 2, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true, 2, 0, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true, 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false, 1, 2, 0); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true, 1, 2, 0); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false, 1, 2, 0); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true, 1, 2, 0); + assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ false)).isEqualTo(0); + assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(0); + assertThat(timeline.getLastWindowIndex(/* shuffleModeEnabled= */ false)).isEqualTo(2); + assertThat(timeline.getLastWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(2); + } + + public void testNestedTimeline() throws IOException { + DynamicConcatenatingMediaSource nestedSource1 = + new DynamicConcatenatingMediaSource(/* isAtomic= */ false, new FakeShuffleOrder(0)); + DynamicConcatenatingMediaSource nestedSource2 = + new DynamicConcatenatingMediaSource(/* isAtomic= */ true, new FakeShuffleOrder(0)); + mediaSource.addMediaSource(nestedSource1); + mediaSource.addMediaSource(nestedSource2); + testRunner.prepareSource(); + FakeMediaSource[] childSources = createMediaSources(4); + nestedSource1.addMediaSource(childSources[0]); + testRunner.assertTimelineChangeBlocking(); + nestedSource1.addMediaSource(childSources[1]); + testRunner.assertTimelineChangeBlocking(); + nestedSource2.addMediaSource(childSources[2]); + testRunner.assertTimelineChangeBlocking(); + nestedSource2.addMediaSource(childSources[3]); + Timeline timeline = testRunner.assertTimelineChangeBlocking(); + + TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 444); + TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3, 4); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, C.INDEX_UNSET, 0, 1, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false, 0, 1, 3, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false, 3, 0, 1, 2); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, 1, 2, 3, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false, 0, 1, 3, 2); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false, 1, 2, 3, 0); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true, 1, 3, C.INDEX_UNSET, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true, 0, 1, 3, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true, 1, 3, 0, 2); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true, C.INDEX_UNSET, 0, 3, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true, 0, 1, 3, 2); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true, 2, 0, 3, 1); + } + public void testRemoveChildSourceWithActiveMediaPeriod() throws IOException { FakeMediaSource childSource = createFakeMediaSource(); mediaSource.addMediaSource(childSource); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java index 35234753b0..696a6f6fad 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java @@ -27,14 +27,18 @@ import com.google.android.exoplayer2.Timeline; private final int childCount; private final ShuffleOrder shuffleOrder; + private final boolean isAtomic; /** * Sets up a concatenated timeline with a shuffle order of child timelines. * + * @param isAtomic Whether the child timelines shall be treated as atomic, i.e., treated as a + * single item for repeating and shuffling. * @param shuffleOrder A shuffle order of child timelines. The number of child timelines must * match the number of elements in the shuffle order. */ - public AbstractConcatenatedTimeline(ShuffleOrder shuffleOrder) { + public AbstractConcatenatedTimeline(boolean isAtomic, ShuffleOrder shuffleOrder) { + this.isAtomic = isAtomic; this.shuffleOrder = shuffleOrder; this.childCount = shuffleOrder.getLength(); } @@ -42,6 +46,11 @@ import com.google.android.exoplayer2.Timeline; @Override public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) { + if (isAtomic) { + // Adapt repeat and shuffle mode to atomic concatenation. + repeatMode = repeatMode == Player.REPEAT_MODE_ONE ? Player.REPEAT_MODE_ALL : repeatMode; + shuffleModeEnabled = false; + } // Find next window within current child. int childIndex = getChildIndexByWindowIndex(windowIndex); int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); @@ -71,6 +80,11 @@ import com.google.android.exoplayer2.Timeline; @Override public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) { + if (isAtomic) { + // Adapt repeat and shuffle mode to atomic concatenation. + repeatMode = repeatMode == Player.REPEAT_MODE_ONE ? Player.REPEAT_MODE_ALL : repeatMode; + shuffleModeEnabled = false; + } // Find previous window within current child. int childIndex = getChildIndexByWindowIndex(windowIndex); int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); @@ -103,6 +117,9 @@ import com.google.android.exoplayer2.Timeline; if (childCount == 0) { return C.INDEX_UNSET; } + if (isAtomic) { + shuffleModeEnabled = false; + } // Find last non-empty child. int lastChildIndex = shuffleModeEnabled ? shuffleOrder.getLastIndex() : childCount - 1; while (getTimelineByChildIndex(lastChildIndex).isEmpty()) { @@ -121,6 +138,9 @@ import com.google.android.exoplayer2.Timeline; if (childCount == 0) { return C.INDEX_UNSET; } + if (isAtomic) { + shuffleModeEnabled = false; + } // Find first non-empty child. int firstChildIndex = shuffleModeEnabled ? shuffleOrder.getFirstIndex() : 0; while (getTimelineByChildIndex(firstChildIndex).isEmpty()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 48de7de364..c29367e109 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder; import com.google.android.exoplayer2.upstream.Allocator; @@ -173,10 +172,9 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource mediaSourceByMediaPeriod; private final List deferredMediaPeriods; + private final boolean isAtomic; private ExoPlayer player; private Listener listener; @@ -70,22 +71,35 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< * Creates a new dynamic concatenating media source. */ public DynamicConcatenatingMediaSource() { - this(new DefaultShuffleOrder(0)); + this(/* isAtomic= */ false, new DefaultShuffleOrder(0)); + } + + /** + * Creates a new dynamic concatenating media source. + * + * @param isAtomic Whether the concatenating media source will be treated as atomic, i.e., treated + * as a single item for repeating and shuffling. + */ + public DynamicConcatenatingMediaSource(boolean isAtomic) { + this(isAtomic, new DefaultShuffleOrder(0)); } /** * Creates a new dynamic concatenating media source with a custom shuffle order. * + * @param isAtomic Whether the concatenating media source will be treated as atomic, i.e., treated + * as a single item for repeating and shuffling. * @param shuffleOrder The {@link ShuffleOrder} to use when shuffling the child media sources. * This shuffle order must be empty. */ - public DynamicConcatenatingMediaSource(ShuffleOrder shuffleOrder) { + public DynamicConcatenatingMediaSource(boolean isAtomic, ShuffleOrder shuffleOrder) { this.shuffleOrder = shuffleOrder; this.mediaSourceByMediaPeriod = new IdentityHashMap<>(); this.mediaSourcesPublic = new ArrayList<>(); this.mediaSourceHolders = new ArrayList<>(); this.deferredMediaPeriods = new ArrayList<>(1); this.query = new MediaSourceHolder(null, null, -1, -1, -1); + this.isAtomic = isAtomic; } /** @@ -446,8 +460,10 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< private void maybeNotifyListener(@Nullable EventDispatcher actionOnCompletion) { if (!preventListenerNotification) { - listener.onSourceInfoRefreshed(this, - new ConcatenatedTimeline(mediaSourceHolders, windowCount, periodCount, shuffleOrder), + listener.onSourceInfoRefreshed( + this, + new ConcatenatedTimeline( + mediaSourceHolders, windowCount, periodCount, shuffleOrder, isAtomic), null); if (actionOnCompletion != null) { player.createMessage(this).setType(MSG_ON_COMPLETION).setPayload(actionOnCompletion).send(); @@ -652,9 +668,13 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< private final int[] uids; private final SparseIntArray childIndexByUid; - public ConcatenatedTimeline(Collection mediaSourceHolders, int windowCount, - int periodCount, ShuffleOrder shuffleOrder) { - super(shuffleOrder); + public ConcatenatedTimeline( + Collection mediaSourceHolders, + int windowCount, + int periodCount, + ShuffleOrder shuffleOrder, + boolean isAtomic) { + super(isAtomic, shuffleOrder); this.windowCount = windowCount; this.periodCount = periodCount; int childCount = mediaSourceHolders.size(); @@ -728,61 +748,39 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< * Timeline used as placeholder for an unprepared media source. After preparation, a copy of the * DeferredTimeline is used to keep the originally assigned first period ID. */ - private static final class DeferredTimeline extends Timeline { + private static final class DeferredTimeline extends ForwardingTimeline { private static final Object DUMMY_ID = new Object(); private static final Period period = new Period(); + private static final DummyTimeline dummyTimeline = new DummyTimeline(); - private final Timeline timeline; - private final Object replacedID; + private final Object replacedId; public DeferredTimeline() { - timeline = null; - replacedID = null; + this(dummyTimeline, /* replacedId= */ null); } - private DeferredTimeline(Timeline timeline, Object replacedID) { - this.timeline = timeline; - this.replacedID = replacedID; + private DeferredTimeline(Timeline timeline, Object replacedId) { + super(timeline); + this.replacedId = replacedId; } public DeferredTimeline cloneWithNewTimeline(Timeline timeline) { - return new DeferredTimeline(timeline, replacedID == null && timeline.getPeriodCount() > 0 - ? timeline.getPeriod(0, period, true).uid : replacedID); + return new DeferredTimeline( + timeline, + replacedId == null && timeline.getPeriodCount() > 0 + ? timeline.getPeriod(0, period, true).uid + : replacedId); } public Timeline getTimeline() { return timeline; } - @Override - public int getWindowCount() { - return timeline == null ? 1 : timeline.getWindowCount(); - } - - @Override - public Window getWindow(int windowIndex, Window window, boolean setIds, - long defaultPositionProjectionUs) { - return timeline == null - // Dynamic window to indicate pending timeline updates. - ? window.set(setIds ? DUMMY_ID : null, C.TIME_UNSET, C.TIME_UNSET, false, true, 0, - C.TIME_UNSET, 0, 0, 0) - : timeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs); - } - - @Override - public int getPeriodCount() { - return timeline == null ? 1 : timeline.getPeriodCount(); - } - @Override public Period getPeriod(int periodIndex, Period period, boolean setIds) { - if (timeline == null) { - return period.set(setIds ? DUMMY_ID : null, setIds ? DUMMY_ID : null, 0, C.TIME_UNSET, - C.TIME_UNSET); - } timeline.getPeriod(periodIndex, period, setIds); - if (period.uid == replacedID) { + if (period.uid == replacedId) { period.uid = DUMMY_ID; } return period; @@ -790,11 +788,54 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< @Override public int getIndexOfPeriod(Object uid) { - return timeline == null ? (uid == DUMMY_ID ? 0 : C.INDEX_UNSET) - : timeline.getIndexOfPeriod(uid == DUMMY_ID ? replacedID : uid); + return timeline.getIndexOfPeriod(uid == DUMMY_ID ? replacedId : uid); } - } + /** Dummy placeholder timeline with one dynamic window with a period of indeterminate duration. */ + private static final class DummyTimeline extends Timeline { + + @Override + public int getWindowCount() { + return 1; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + // Dynamic window to indicate pending timeline updates. + return window.set( + /* id= */ null, + /* presentationStartTimeMs= */ C.TIME_UNSET, + /* windowStartTimeMs= */ C.TIME_UNSET, + /* isSeekable= */ false, + /* isDynamic= */ true, + /* defaultPositionUs= */ 0, + /* durationUs= */ C.TIME_UNSET, + /* firstPeriodIndex= */ 0, + /* lastPeriodIndex= */ 0, + /* positionInFirstPeriodUs= */ 0); + } + + @Override + public int getPeriodCount() { + return 1; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + return period.set( + /* id= */ null, + /* uid= */ null, + /* windowIndex= */ 0, + /* durationUs = */ C.TIME_UNSET, + /* positionInWindowUs= */ C.TIME_UNSET); + } + + @Override + public int getIndexOfPeriod(Object uid) { + return uid == null ? 0 : C.INDEX_UNSET; + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index 5eea1aa1cc..e2ef4eb5fa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -106,7 +106,7 @@ public final class LoopingMediaSource extends CompositeMediaSource { private final int loopCount; public LoopingTimeline(Timeline childTimeline, int loopCount) { - super(new UnshuffledShuffleOrder(loopCount)); + super(/* isAtomic= */ false, new UnshuffledShuffleOrder(loopCount)); this.childTimeline = childTimeline; childPeriodCount = childTimeline.getPeriodCount(); childWindowCount = childTimeline.getWindowCount(); From fea75f2e4287c2cc12fb0ae07551768c5581570c Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 6 Feb 2018 05:47:07 -0800 Subject: [PATCH 12/53] Prevent "unexpected read attempt" illegal state exception When using cronet data source, calling read after the end of input has been read will trigger this. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184667794 --- .../google/android/exoplayer2/ext/cronet/CronetDataSource.java | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 536155a70f..29bc874cd8 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -369,6 +369,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou throw new HttpDataSourceException(exception, currentDataSpec, HttpDataSourceException.TYPE_READ); } else if (finished) { + bytesRemaining = 0; return C.RESULT_END_OF_INPUT; } else { // The operation didn't time out, fail or finish, and therefore data must have been read. From 84a105b0314f30771d73ce566ed82a26ae2efa2f Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 7 Feb 2018 02:44:23 -0800 Subject: [PATCH 13/53] Get the next ad index to play in MediaPeriodQueue The ad index in the ad group may need to skip over ads that failed to load, so it can't just be incremented any more. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184812556 --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 2 +- .../android/exoplayer2/MediaPeriodQueue.java | 41 ++++++++++++------- .../google/android/exoplayer2/Timeline.java | 24 ++++++++--- .../source/ads/AdPlaybackState.java | 24 ++++++++--- .../source/ads/AdPlaybackStateTest.java | 22 +++++++--- 5 files changed, 81 insertions(+), 32 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 493deed4ad..987a58d3f7 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -887,7 +887,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A private void stopAdInternal() { Assertions.checkState(imaAdState != IMA_AD_STATE_NONE); imaAdState = IMA_AD_STATE_NONE; - int adIndexInAdGroup = adPlaybackState.adGroups[adGroupIndex].nextAdIndexToPlay; + int adIndexInAdGroup = adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay(); // TODO: Handle the skipped event so the ad can be marked as skipped rather than played. adPlaybackState = adPlaybackState.withPlayedAd(adGroupIndex, adIndexInAdGroup).withAdResumePositionUs(0); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 0c643ec120..4b6ef1807e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -372,7 +372,7 @@ import com.google.android.exoplayer2.util.Assertions; if (adGroupIndex == C.INDEX_UNSET) { return new MediaPeriodId(periodIndex); } else { - int adIndexInAdGroup = period.getNextAdIndexToPlay(adGroupIndex); + int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); return new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup); } } @@ -496,19 +496,20 @@ import com.google.android.exoplayer2.util.Assertions; MediaPeriodId currentPeriodId = mediaPeriodInfo.id; timeline.getPeriod(currentPeriodId.periodIndex, period); if (currentPeriodId.isAd()) { - int currentAdGroupIndex = currentPeriodId.adGroupIndex; - int adCountInCurrentAdGroup = period.getAdCountInAdGroup(currentAdGroupIndex); + int adGroupIndex = currentPeriodId.adGroupIndex; + int adCountInCurrentAdGroup = period.getAdCountInAdGroup(adGroupIndex); if (adCountInCurrentAdGroup == C.LENGTH_UNSET) { return null; } - int nextAdIndexInAdGroup = currentPeriodId.adIndexInAdGroup + 1; + int nextAdIndexInAdGroup = + period.getNextAdIndexToPlay(adGroupIndex, currentPeriodId.adIndexInAdGroup); if (nextAdIndexInAdGroup < adCountInCurrentAdGroup) { // Play the next ad in the ad group if it's available. - return !period.isAdAvailable(currentAdGroupIndex, nextAdIndexInAdGroup) + return !period.isAdAvailable(adGroupIndex, nextAdIndexInAdGroup) ? null : getMediaPeriodInfoForAd( currentPeriodId.periodIndex, - currentAdGroupIndex, + adGroupIndex, nextAdIndexInAdGroup, mediaPeriodInfo.contentPositionUs); } else { @@ -524,22 +525,32 @@ import com.google.android.exoplayer2.util.Assertions; return getMediaPeriodInfoForContent( currentPeriodId.periodIndex, mediaPeriodInfo.endPositionUs); } - return !period.isAdAvailable(nextAdGroupIndex, 0) + int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex); + return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup) ? null : getMediaPeriodInfoForAd( - currentPeriodId.periodIndex, nextAdGroupIndex, 0, mediaPeriodInfo.endPositionUs); + currentPeriodId.periodIndex, + nextAdGroupIndex, + adIndexInAdGroup, + mediaPeriodInfo.endPositionUs); } else { // Check if the postroll ad should be played. int adGroupCount = period.getAdGroupCount(); - if (adGroupCount == 0 - || period.getAdGroupTimeUs(adGroupCount - 1) != C.TIME_END_OF_SOURCE - || period.hasPlayedAdGroup(adGroupCount - 1) - || !period.isAdAvailable(adGroupCount - 1, 0)) { + if (adGroupCount == 0) { + return null; + } + int adGroupIndex = adGroupCount - 1; + if (period.getAdGroupTimeUs(adGroupIndex) != C.TIME_END_OF_SOURCE + || period.hasPlayedAdGroup(adGroupIndex)) { + return null; + } + int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); + if (!period.isAdAvailable(adGroupIndex, adIndexInAdGroup)) { return null; } long contentDurationUs = period.getDurationUs(); return getMediaPeriodInfoForAd( - currentPeriodId.periodIndex, adGroupCount - 1, 0, contentDurationUs); + currentPeriodId.periodIndex, adGroupIndex, adIndexInAdGroup, contentDurationUs); } } @@ -587,7 +598,7 @@ import com.google.android.exoplayer2.util.Assertions; .getPeriod(id.periodIndex, period) .getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup); long startPositionUs = - adIndexInAdGroup == period.getNextAdIndexToPlay(adGroupIndex) + adIndexInAdGroup == period.getFirstAdIndexToPlay(adGroupIndex) ? period.getAdResumePositionUs() : 0; return new MediaPeriodInfo( @@ -636,7 +647,7 @@ import com.google.android.exoplayer2.util.Assertions; boolean isLastAd = isAd && id.adGroupIndex == lastAdGroupIndex && id.adIndexInAdGroup == postrollAdCount - 1; - return isLastAd || (!isAd && period.getNextAdIndexToPlay(lastAdGroupIndex) == postrollAdCount); + return isLastAd || (!isAd && period.getFirstAdIndexToPlay(lastAdGroupIndex) == postrollAdCount); } private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index 26c2cc3e83..5906ffb8e7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -381,15 +381,29 @@ public abstract class Timeline { } /** - * Returns the index of the next ad to play in the specified ad group, or the number of ads in - * the ad group if the ad group does not have any ads remaining to play. + * Returns the index of the first ad in the specified ad group that should be played, or the + * number of ads in the ad group if no ads should be played. * * @param adGroupIndex The ad group index. + * @return The index of the first ad that should be played, or the number of ads in the ad group + * if no ads should be played. + */ + public int getFirstAdIndexToPlay(int adGroupIndex) { + return adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay(); + } + + /** + * Returns the index of the next ad in the specified ad group that should be played after + * playing {@code adIndexInAdGroup}, or the number of ads in the ad group if no later ads should + * be played. + * + * @param adGroupIndex The ad group index. + * @param lastPlayedAdIndex The last played ad index in the ad group. * @return The index of the next ad that should be played, or the number of ads in the ad group * if the ad group does not have any ads remaining to play. */ - public int getNextAdIndexToPlay(int adGroupIndex) { - return adPlaybackState.adGroups[adGroupIndex].nextAdIndexToPlay; + public int getNextAdIndexToPlay(int adGroupIndex, int lastPlayedAdIndex) { + return adPlaybackState.adGroups[adGroupIndex].getNextAdIndexToPlay(lastPlayedAdIndex); } /** @@ -400,7 +414,7 @@ public abstract class Timeline { */ public boolean hasPlayedAdGroup(int adGroupIndex) { AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; - return adGroup.nextAdIndexToPlay == adGroup.count; + return adGroup.getFirstAdIndexToPlay() == adGroup.count; } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 7b06098d45..02bd67dbd8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -49,8 +49,6 @@ public final class AdPlaybackState { public final @AdState int[] states; /** The durations of each ad in the ad group, in microseconds. */ public final long[] durationsUs; - /** The index of the next ad that should be played, or {@link #count} if all ads were played. */ - public final int nextAdIndexToPlay; /** Creates a new ad group with an unspecified number of ads. */ public AdGroup() { @@ -67,14 +65,30 @@ public final class AdPlaybackState { this.states = states; this.uris = uris; this.durationsUs = durationsUs; - int nextAdIndexToPlay; - for (nextAdIndexToPlay = 0; nextAdIndexToPlay < states.length; nextAdIndexToPlay++) { + } + + /** + * Returns the index of the first ad in the ad group that should be played, or {@link #count} if + * no ads should be played. + */ + public int getFirstAdIndexToPlay() { + return getNextAdIndexToPlay(-1); + } + + /** + * Returns the index of the next ad in the ad group that should be played after playing {@code + * lastPlayedAdIndex}, or {@link #count} if no later ads should be played. + */ + public int getNextAdIndexToPlay(int lastPlayedAdIndex) { + int nextAdIndexToPlay = lastPlayedAdIndex + 1; + while (nextAdIndexToPlay < states.length) { if (states[nextAdIndexToPlay] == AD_STATE_UNAVAILABLE || states[nextAdIndexToPlay] == AD_STATE_AVAILABLE) { break; } + nextAdIndexToPlay++; } - this.nextAdIndexToPlay = nextAdIndexToPlay; + return nextAdIndexToPlay; } /** diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java index 95f492f17f..ca8bf5d393 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java @@ -70,29 +70,29 @@ public final class AdPlaybackStateTest { } @Test - public void testInitialNextAdIndexToPlay() { + public void testGetFirstAdIndexToPlayIsZero() { state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI); - assertThat(state.adGroups[0].nextAdIndexToPlay).isEqualTo(0); + assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(0); } @Test - public void testNextAdIndexToPlayWithPlayedAd() { + public void testGetFirstAdIndexToPlaySkipsPlayedAd() { state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI); state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0); - assertThat(state.adGroups[0].nextAdIndexToPlay).isEqualTo(1); + assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(1); assertThat(state.adGroups[0].states[1]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE); assertThat(state.adGroups[0].states[2]).isEqualTo(AdPlaybackState.AD_STATE_AVAILABLE); } @Test - public void testNextAdIndexToPlaySkipsErrorAds() { + public void testGetFirstAdIndexToPlaySkipsErrorAds() { state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI); @@ -100,7 +100,17 @@ public final class AdPlaybackStateTest { state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0); state = state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1); - assertThat(state.adGroups[0].nextAdIndexToPlay).isEqualTo(2); + assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(2); + } + + @Test + public void testGetNextAdIndexToPlaySkipsErrorAds() { + state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3); + state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI); + + state = state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1); + + assertThat(state.adGroups[0].getNextAdIndexToPlay(0)).isEqualTo(2); } @Test From 5c49633c30ca1e26a8a760a28ecd1d230115fb6c Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 7 Feb 2018 06:45:52 -0800 Subject: [PATCH 14/53] Fix ad group skipping Allow skipping an ad group when requested by IMA, even if we aren't currently playing one, to handle cases where no ads in an ad group will load (so IMA requests resuming content but we never managed to start playing an ad). Use the known ad group index (rather than the expected one) when handling ad group load errors. This ensures we skip the right ad group if we notify IMA of playback errors for every ad in the ad group, then IMA notifies that the ad group is empty via a load error. Also make some other miscellaneous small fixes to ads code: - Avoid warning about unexpected ad group indices more than once. - Output a warning if the ad count in an ad group decreases. - Remove unnecessary assertion. - Fix getting the ad duration for ad indices that haven't loaded yet. - Allow setting an ad group state to its current value. - Fix javadoc for setting the ad resume position. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184831495 --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 28 +++++++++++++------ .../google/android/exoplayer2/Timeline.java | 3 +- .../source/ads/AdPlaybackState.java | 4 ++- .../exoplayer2/source/ads/AdsLoader.java | 20 ++++++------- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 987a58d3f7..4f20535ff1 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -477,8 +477,16 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A if (DEBUG) { Log.d(TAG, "Loaded ad " + adPosition + " of " + adCount + " in group " + adGroupIndex); } - adPlaybackState = adPlaybackState.withAdCount(adGroupIndex, adCount); - updateAdPlaybackState(); + int oldAdCount = adPlaybackState.adGroups[adGroupIndex].count; + if (adCount != oldAdCount) { + if (oldAdCount == C.LENGTH_UNSET) { + adPlaybackState = adPlaybackState.withAdCount(adGroupIndex, adCount); + updateAdPlaybackState(); + } else { + // IMA sometimes unexpectedly decreases the ad count in an ad group. + Log.w(TAG, "Unexpected ad count in LOADED, " + adCount + ", expected " + oldAdCount); + } + } if (adGroupIndex != expectedAdGroupIndex) { Log.w( TAG, @@ -486,6 +494,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A + expectedAdGroupIndex + ", actual ad group index " + adGroupIndex); + expectedAdGroupIndex = adGroupIndex; } break; case CONTENT_PAUSE_REQUESTED: @@ -536,7 +545,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A if (adsManager == null) { // No ads were loaded, so allow playback to start without any ads. pendingAdRequestContext = null; - adPlaybackState = new AdPlaybackState(new long[0]); + adPlaybackState = new AdPlaybackState(); updateAdPlaybackState(); } if (pendingAdErrorEvent == null) { @@ -866,7 +875,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A Log.d(TAG, "Unexpected CONTENT_RESUME_REQUESTED without stopAd"); } } - if (playingAd && adGroupIndex != C.INDEX_UNSET) { + if (adGroupIndex != C.INDEX_UNSET) { adPlaybackState = adPlaybackState.withSkippedAdGroup(adGroupIndex); adGroupIndex = C.INDEX_UNSET; updateAdPlaybackState(); @@ -885,7 +894,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A } private void stopAdInternal() { - Assertions.checkState(imaAdState != IMA_AD_STATE_NONE); imaAdState = IMA_AD_STATE_NONE; int adIndexInAdGroup = adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay(); // TODO: Handle the skipped event so the ad can be marked as skipped rather than played. @@ -898,7 +906,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A } private void handleAdGroupLoadError() { - AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[expectedAdGroupIndex]; + int adGroupIndex = + this.adGroupIndex == C.INDEX_UNSET ? expectedAdGroupIndex : this.adGroupIndex; + AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; // Ad group load error can be notified more than once, so check if it was already handled. // TODO: Update the expected ad group index based on the position returned by // getContentProgress so that it's possible to detect when more than one ad group fails to load @@ -906,10 +916,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A if (adGroup.count == C.LENGTH_UNSET || adGroup.states[0] == AdPlaybackState.AD_STATE_UNAVAILABLE) { if (DEBUG) { - Log.d(TAG, "Removing ad group " + expectedAdGroupIndex + " as it failed to load"); + Log.d(TAG, "Removing ad group " + adGroupIndex + " as it failed to load"); } - adPlaybackState = adPlaybackState.withAdCount(expectedAdGroupIndex, 1); - adPlaybackState = adPlaybackState.withAdLoadError(expectedAdGroupIndex, 0); + adPlaybackState = + adPlaybackState.withAdCount(adGroupIndex, 1).withAdLoadError(adGroupIndex, 0); updateAdPlaybackState(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index 5906ffb8e7..4b58a1075c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -495,7 +495,8 @@ public abstract class Timeline { * @return The duration of the ad, or {@link C#TIME_UNSET} if not yet known. */ public long getAdDurationUs(int adGroupIndex, int adIndexInAdGroup) { - return adPlaybackState.adGroups[adGroupIndex].durationsUs[adIndexInAdGroup]; + AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; + return adGroup.count != C.LENGTH_UNSET ? adGroup.durationsUs[adIndexInAdGroup] : C.TIME_UNSET; } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 02bd67dbd8..2f6f81d6b9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -140,7 +140,9 @@ public final class AdPlaybackState { Assertions.checkArgument(count == C.LENGTH_UNSET || index < count); @AdState int[] states = copyStatesWithSpaceForAdCount(this.states, index + 1); Assertions.checkArgument( - states[index] == AD_STATE_UNAVAILABLE || states[index] == AD_STATE_AVAILABLE); + states[index] == AD_STATE_UNAVAILABLE + || states[index] == AD_STATE_AVAILABLE + || states[index] == state); long[] durationsUs = this.durationsUs.length == states.length ? this.durationsUs diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java index 99feccd2f3..c2dfd91301 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java @@ -22,22 +22,22 @@ import java.io.IOException; /** * Interface for loaders of ads, which can be used with {@link AdsMediaSource}. - *

- * Ad loaders notify the {@link AdsMediaSource} about events via {@link EventListener}. In + * + *

Ad loaders notify the {@link AdsMediaSource} about events via {@link EventListener}. In * particular, implementations must call {@link EventListener#onAdPlaybackState(AdPlaybackState)} * with a new copy of the current {@link AdPlaybackState} whenever further information about ads * becomes known (for example, when an ad media URI is available, or an ad has played to the end). - *

- * {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} will be called when the ads media + * + *

{@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} will be called when the ads media * source first initializes, at which point the loader can request ads. If the player enters the * background, {@link #detachPlayer()} will be called. Loaders should maintain any ad playback state * in preparation for a later call to {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)}. If - * an ad is playing when the player is detached, store the current playback position via - * {@link AdPlaybackState#setAdResumePositionUs(long)}. - *

- * If {@link EventListener#onAdPlaybackState(AdPlaybackState)} has been called, the implementation - * of {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} should invoke the same listener to - * provide the existing playback state to the new player. + * an ad is playing when the player is detached, update the ad playback state with the current + * playback position using {@link AdPlaybackState#withAdResumePositionUs(long)}. + * + *

If {@link EventListener#onAdPlaybackState(AdPlaybackState)} has been called, the + * implementation of {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} should invoke the + * same listener to provide the existing playback state to the new player. */ public interface AdsLoader { From 36bf0b2658add4f7da0c423bac98e25f96f54caf Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 7 Feb 2018 08:59:32 -0800 Subject: [PATCH 15/53] Add issue link to release notes ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184844870 --- RELEASENOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1e5d47b044..7ddda64c83 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -91,6 +91,9 @@ * `EventLogger` moved from the demo app into the core library. * Fix ANR issue on Huawei P8 Lite ([#3724](https://github.com/google/ExoPlayer/issues/3724)). +* Fix potential NPE when removing media sources from a + DynamicConcatenatingMediaSource + ([#3796](https://github.com/google/ExoPlayer/issues/3796)). ### 2.6.1 ### From 542855b6eb59a13d290a0f70b38fcfc05944c161 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 8 Feb 2018 01:57:46 -0800 Subject: [PATCH 16/53] Link libopus statically with libopusJNI We now build one .so file for the opus extension in the internal build, so make the external build work the same way. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184962896 --- extensions/opus/src/main/jni/Android.mk | 4 ++-- extensions/opus/src/main/jni/libopus.mk | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/opus/src/main/jni/Android.mk b/extensions/opus/src/main/jni/Android.mk index 2ceb8fc4f7..9d1e4fe726 100644 --- a/extensions/opus/src/main/jni/Android.mk +++ b/extensions/opus/src/main/jni/Android.mk @@ -17,7 +17,7 @@ WORKING_DIR := $(call my-dir) include $(CLEAR_VARS) -# build libopus.so +# build libopus.a LOCAL_PATH := $(WORKING_DIR) include libopus.mk @@ -29,5 +29,5 @@ LOCAL_ARM_MODE := arm LOCAL_CPP_EXTENSION := .cc LOCAL_SRC_FILES := opus_jni.cc LOCAL_LDLIBS := -llog -lz -lm -LOCAL_SHARED_LIBRARIES := libopus +LOCAL_STATIC_LIBRARIES := libopus include $(BUILD_SHARED_LIBRARY) diff --git a/extensions/opus/src/main/jni/libopus.mk b/extensions/opus/src/main/jni/libopus.mk index 0a5dd15b5a..672df600c0 100644 --- a/extensions/opus/src/main/jni/libopus.mk +++ b/extensions/opus/src/main/jni/libopus.mk @@ -47,4 +47,4 @@ endif LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include -include $(BUILD_SHARED_LIBRARY) +include $(BUILD_STATIC_LIBRARY) From 6aad066d51a20e2cb1f1e6ac70e785f2fc36bc45 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 9 Feb 2018 03:06:43 -0800 Subject: [PATCH 17/53] Check sys.display-size on Philips ATVs Device models are from https://support.google.com/googleplay/answer/1727131?hl=en. It looks like among these devices Build.MANUFACTURER can be set to either "PHILIPS" or "Philips", based on looking at internal bug reports. Issue: #3807 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185121182 --- RELEASENOTES.md | 2 ++ .../main/java/com/google/android/exoplayer2/util/Util.java | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7ddda64c83..4c0354fad7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -94,6 +94,8 @@ * Fix potential NPE when removing media sources from a DynamicConcatenatingMediaSource ([#3796](https://github.com/google/ExoPlayer/issues/3796)). +* Check `sys.display-size` on Philips ATVs + ([#3807](https://github.com/google/ExoPlayer/issues/3807)). ### 2.6.1 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index f4637ebde6..cd643f2df4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -1334,7 +1334,11 @@ public final class Util { if ("Sony".equals(Util.MANUFACTURER) && Util.MODEL.startsWith("BRAVIA") && context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) { return new Point(3840, 2160); - } else if ("NVIDIA".equals(Util.MANUFACTURER) && Util.MODEL.contains("SHIELD")) { + } else if (("NVIDIA".equals(Util.MANUFACTURER) && Util.MODEL.contains("SHIELD")) + || ("philips".equals(Util.toLowerInvariant(Util.MANUFACTURER)) + && (Util.MODEL.startsWith("QM1") + || Util.MODEL.equals("QV151E") + || Util.MODEL.equals("TPM171E")))) { // Attempt to read sys.display-size. String sysDisplaySize = null; try { From d274ca6750c44e9b705fced8440d69fca1aec85a Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 9 Feb 2018 03:57:13 -0800 Subject: [PATCH 18/53] Extend support of 608 captions in SEI messages Issue:#3816 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185124378 --- .../android/exoplayer2/text/cea/CeaUtil.java | 92 +++++++++---------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java index ddb3804c3e..0022d37d6c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java @@ -19,18 +19,19 @@ import android.util.Log; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.Util; -/** - * Utility methods for handling CEA-608/708 messages. - */ +/** Utility methods for handling CEA-608/708 messages. Defined in A/53 Part 4:2009. */ public final class CeaUtil { private static final String TAG = "CeaUtil"; private static final int PAYLOAD_TYPE_CC = 4; private static final int COUNTRY_CODE = 0xB5; - private static final int PROVIDER_CODE = 0x31; - private static final int USER_ID = 0x47413934; // "GA94" + private static final int PROVIDER_CODE_ATSC = 0x31; + private static final int PROVIDER_CODE_DIRECTV = 0x2F; + private static final int USER_ID_GA94 = Util.getIntegerCodeForString("GA94"); + private static final int USER_ID_DTG1 = Util.getIntegerCodeForString("DTG1"); private static final int USER_DATA_TYPE_CODE = 0x3; /** @@ -46,33 +47,49 @@ public final class CeaUtil { while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) { int payloadType = readNon255TerminatedValue(seiBuffer); int payloadSize = readNon255TerminatedValue(seiBuffer); + int nextPayloadPosition = seiBuffer.getPosition() + payloadSize; // Process the payload. if (payloadSize == -1 || payloadSize > seiBuffer.bytesLeft()) { // This might occur if we're trying to read an encrypted SEI NAL unit. Log.w(TAG, "Skipping remainder of malformed SEI NAL unit."); seiBuffer.setPosition(seiBuffer.limit()); - } else if (isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) { - // Ignore country_code (1) + provider_code (2) + user_identifier (4) - // + user_data_type_code (1). - seiBuffer.skipBytes(8); - // Ignore first three bits: reserved (1) + process_cc_data_flag (1) + zero_bit (1). - int ccCount = seiBuffer.readUnsignedByte() & 0x1F; - // Ignore em_data (1) - seiBuffer.skipBytes(1); - // Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2) - // + cc_data_1 (8) + cc_data_2 (8). - int sampleLength = ccCount * 3; - int sampleStartPosition = seiBuffer.getPosition(); - for (TrackOutput output : outputs) { - seiBuffer.setPosition(sampleStartPosition); - output.sampleData(seiBuffer, sampleLength); - output.sampleMetadata(presentationTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null); + } else if (payloadType == PAYLOAD_TYPE_CC && payloadSize >= 8) { + int countryCode = seiBuffer.readUnsignedByte(); + int providerCode = seiBuffer.readUnsignedShort(); + int userIdentifier = 0; + if (providerCode == PROVIDER_CODE_ATSC) { + userIdentifier = seiBuffer.readInt(); + } + int userDataTypeCode = seiBuffer.readUnsignedByte(); + if (providerCode == PROVIDER_CODE_DIRECTV) { + seiBuffer.skipBytes(1); // user_data_length. + } + boolean messageIsSupportedCeaCaption = + countryCode == COUNTRY_CODE + && (providerCode == PROVIDER_CODE_ATSC || providerCode == PROVIDER_CODE_DIRECTV) + && userDataTypeCode == USER_DATA_TYPE_CODE; + if (providerCode == PROVIDER_CODE_ATSC) { + messageIsSupportedCeaCaption &= + userIdentifier == USER_ID_GA94 || userIdentifier == USER_ID_DTG1; + } + if (messageIsSupportedCeaCaption) { + // Ignore first three bits: reserved (1) + process_cc_data_flag (1) + zero_bit (1). + int ccCount = seiBuffer.readUnsignedByte() & 0x1F; + // Ignore em_data (1) + seiBuffer.skipBytes(1); + // Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2) + // + cc_data_1 (8) + cc_data_2 (8). + int sampleLength = ccCount * 3; + int sampleStartPosition = seiBuffer.getPosition(); + for (TrackOutput output : outputs) { + seiBuffer.setPosition(sampleStartPosition); + output.sampleData(seiBuffer, sampleLength); + output.sampleMetadata( + presentationTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null); + } } - // Ignore trailing information in SEI, if any. - seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3)); - } else { - seiBuffer.skipBytes(payloadSize); } + seiBuffer.setPosition(nextPayloadPosition); } } @@ -97,31 +114,6 @@ public final class CeaUtil { return value; } - /** - * Inspects an sei message to determine whether it contains CEA-608. - *

- * The position of {@code payload} is left unchanged. - * - * @param payloadType The payload type of the message. - * @param payloadLength The length of the payload. - * @param payload A {@link ParsableByteArray} containing the payload. - * @return Whether the sei message contains CEA-608. - */ - private static boolean isSeiMessageCea608(int payloadType, int payloadLength, - ParsableByteArray payload) { - if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) { - return false; - } - int startPosition = payload.getPosition(); - int countryCode = payload.readUnsignedByte(); - int providerCode = payload.readUnsignedShort(); - int userIdentifier = payload.readInt(); - int userDataTypeCode = payload.readUnsignedByte(); - payload.setPosition(startPosition); - return countryCode == COUNTRY_CODE && providerCode == PROVIDER_CODE - && userIdentifier == USER_ID && userDataTypeCode == USER_DATA_TYPE_CODE; - } - private CeaUtil() {} } From 97999ef3b16e4432863e1ec2cd2c93e1a9f5c2eb Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 9 Feb 2018 05:21:52 -0800 Subject: [PATCH 19/53] Fix bug in media period queue update at dynamic timeline changes. If the period uid doesn't match, the update procedure currently doesn't remove the correct periods. This may cause the player to get stuck or to play the wrong periods. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185129503 --- .../android/exoplayer2/MediaPeriodQueue.java | 15 ++++--- .../DynamicConcatenatingMediaSource.java | 4 +- .../android/exoplayer2/ExoPlayerTest.java | 45 +++++++++++++++++++ .../exoplayer2/testutil/FakeMediaSource.java | 6 +++ 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 4b6ef1807e..208a235777 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -291,7 +291,8 @@ import com.google.android.exoplayer2.util.Assertions; /** * Updates media periods in the queue to take into account the latest timeline, and returns * whether the timeline change has been fully handled. If not, it is necessary to seek to the - * current playback position. + * current playback position. The method assumes that the first media period in the queue is still + * consistent with the new timeline. * * @param playingPeriodId The current playing media period identifier. * @param rendererPositionUs The current renderer position in microseconds. @@ -311,6 +312,11 @@ import com.google.android.exoplayer2.util.Assertions; periodHolder.info = getUpdatedMediaPeriodInfo(periodHolder.info, periodIndex); } else { // Check this period holder still follows the previous one, based on the new timeline. + if (periodIndex == C.INDEX_UNSET + || !periodHolder.uid.equals(timeline.getPeriod(periodIndex, period, true).uid)) { + // The holder uid is inconsistent with the new timeline. + return !removeAfter(previousPeriodHolder); + } MediaPeriodInfo periodInfo = getFollowingMediaPeriodInfo(previousPeriodHolder, rendererPositionUs); if (periodInfo == null) { @@ -326,15 +332,10 @@ import com.google.android.exoplayer2.util.Assertions; } if (periodHolder.info.isLastInTimelinePeriod) { - // Move on to the next timeline period, if there is one. + // Move on to the next timeline period index, if there is one. periodIndex = timeline.getNextPeriodIndex( periodIndex, period, window, repeatMode, shuffleModeEnabled); - if (periodIndex == C.INDEX_UNSET - || !periodHolder.uid.equals(timeline.getPeriod(periodIndex, period, true).uid)) { - // The holder is inconsistent with the new timeline. - return previousPeriodHolder == null || !removeAfter(previousPeriodHolder); - } } previousPeriodHolder = periodHolder; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java index 4deb97879c..f52c1bfd0f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java @@ -780,7 +780,7 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< @Override public Period getPeriod(int periodIndex, Period period, boolean setIds) { timeline.getPeriod(periodIndex, period, setIds); - if (period.uid == replacedId) { + if (Util.areEqual(period.uid, replacedId)) { period.uid = DUMMY_ID; } return period; @@ -788,7 +788,7 @@ public final class DynamicConcatenatingMediaSource extends CompositeMediaSource< @Override public int getIndexOfPeriod(Object uid) { - return timeline.getIndexOfPeriod(uid == DUMMY_ID ? replacedId : uid); + return timeline.getIndexOfPeriod(DUMMY_ID.equals(uid) ? replacedId : uid); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index f10e889390..ec5a8ccfce 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.Player.EventListener; import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.ads.AdPlaybackState; @@ -1765,6 +1766,50 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); } + @Test + public void testTimelineUpdateDropsPrebufferedPeriods() throws Exception { + Timeline timeline1 = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 2)); + final Timeline timeline2 = + new FakeTimeline( + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1), + new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 3)); + final FakeMediaSource mediaSource = + new FakeMediaSource(timeline1, /* manifest= */ null, Builder.VIDEO_FORMAT); + ActionSchedule actionSchedule = + new ActionSchedule.Builder("testTimelineUpdateDropsPeriods") + .pause() + .waitForPlaybackState(Player.STATE_READY) + // Ensure next period is pre-buffered by playing until end of first period. + .playUntilPosition( + /* windowIndex= */ 0, + /* positionMs= */ C.usToMs(TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US)) + .executeRunnable( + new Runnable() { + @Override + public void run() { + mediaSource.setNewSourceInfo(timeline2, /* newManifest= */ null); + } + }) + .waitForTimelineChanged(timeline2) + .play() + .build(); + ExoPlayerTestRunner testRunner = + new ExoPlayerTestRunner.Builder() + .setMediaSource(mediaSource) + .setActionSchedule(actionSchedule) + .build() + .start() + .blockUntilEnded(TIMEOUT_MS); + testRunner.assertPlayedPeriodIndices(0, 1); + // Assert that the second period was re-created from the new timeline. + assertThat(mediaSource.getCreatedMediaPeriods()) + .containsExactly(new MediaPeriodId(0), new MediaPeriodId(1), new MediaPeriodId(1)) + .inOrder(); + } + // Internal methods. private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { 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 17613ce519..da81bbb62c 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 @@ -30,6 +30,7 @@ import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; import java.util.ArrayList; +import java.util.List; /** * Fake {@link MediaSource} that provides a given timeline. Creating the period will return a @@ -160,6 +161,11 @@ public class FakeMediaSource implements MediaSource { assertThat(createdMediaPeriods).contains(mediaPeriodId); } + /** Returns a list of {@link MediaPeriodId}s, with one element for each created media period. */ + public List getCreatedMediaPeriods() { + return createdMediaPeriods; + } + protected FakeMediaPeriod createFakeMediaPeriod(MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator) { return new FakeMediaPeriod(trackGroupArray); From 5fe235b7c143e6f314f0204036664b677f63ee2b Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 9 Feb 2018 07:12:51 -0800 Subject: [PATCH 20/53] Move two further methods from Timeline to AdPlaybackState ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185138158 --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 19 +------- .../google/android/exoplayer2/Timeline.java | 37 +++------------- .../source/ads/AdPlaybackState.java | 43 +++++++++++++++++++ 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 4f20535ff1..a8573f31e3 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -797,7 +797,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints()); adPlaybackState = new AdPlaybackState(adGroupTimesUs); int adGroupIndexForPosition = - getAdGroupIndexForPosition(adGroupTimesUs, C.msToUs(pendingContentPositionMs)); + adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(pendingContentPositionMs)); if (adGroupIndexForPosition == 0) { podIndexOffset = 0; } else if (adGroupIndexForPosition == C.INDEX_UNSET) { @@ -952,23 +952,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A } } - /** - * Returns the index of the ad group that should be played before playing the content at {@code - * playbackPositionUs} when starting playback for the first time. This is the latest ad group at - * or before the specified playback position. If the first ad is after the playback position, - * returns {@link C#INDEX_UNSET}. - */ - private int getAdGroupIndexForPosition(long[] adGroupTimesUs, long playbackPositionUs) { - for (int i = 0; i < adGroupTimesUs.length; i++) { - long adGroupTimeUs = adGroupTimesUs[i]; - // A postroll ad is after any position in the content. - if (adGroupTimeUs == C.TIME_END_OF_SOURCE || playbackPositionUs < adGroupTimeUs) { - return i == 0 ? C.INDEX_UNSET : (i - 1); - } - } - return adGroupTimesUs.length == 0 ? C.INDEX_UNSET : (adGroupTimesUs.length - 1); - } - /** * Returns the next ad index in the specified ad group to load, or {@link C#INDEX_UNSET} if all * ads in the ad group have loaded. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index 4b58a1075c..50a3e66880 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -413,53 +413,30 @@ public abstract class Timeline { * @return Whether the ad group at index {@code adGroupIndex} has been played. */ public boolean hasPlayedAdGroup(int adGroupIndex) { - AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; - return adGroup.getFirstAdIndexToPlay() == adGroup.count; + return !adPlaybackState.adGroups[adGroupIndex].hasUnplayedAds(); } /** * Returns the index of the ad group at or before {@code positionUs}, if that ad group is - * unplayed. Returns {@link C#INDEX_UNSET} if the ad group before {@code positionUs} has been - * played, or if there is no such ad group. + * unplayed. Returns {@link C#INDEX_UNSET} if the ad group at or before {@code positionUs} has + * no ads remaining to be played, or if there is no such ad group. * * @param positionUs The position at or before which to find an ad group, in microseconds. * @return The index of the ad group, or {@link C#INDEX_UNSET}. */ public int getAdGroupIndexForPositionUs(long positionUs) { - long[] adGroupTimesUs = adPlaybackState.adGroupTimesUs; - if (adGroupTimesUs == null) { - return C.INDEX_UNSET; - } - // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. - // In practice we expect there to be few ad groups so the search shouldn't be expensive. - int index = adGroupTimesUs.length - 1; - while (index >= 0 && (adGroupTimesUs[index] == C.TIME_END_OF_SOURCE - || adGroupTimesUs[index] > positionUs)) { - index--; - } - return index >= 0 && !hasPlayedAdGroup(index) ? index : C.INDEX_UNSET; + return adPlaybackState.getAdGroupIndexForPositionUs(positionUs); } /** - * Returns the index of the next unplayed ad group after {@code positionUs}. Returns - * {@link C#INDEX_UNSET} if there is no such ad group. + * Returns the index of the next ad group after {@code positionUs} that has ads remaining to be + * played. Returns {@link C#INDEX_UNSET} if there is no such ad group. * * @param positionUs The position after which to find an ad group, in microseconds. * @return The index of the ad group, or {@link C#INDEX_UNSET}. */ public int getAdGroupIndexAfterPositionUs(long positionUs) { - long[] adGroupTimesUs = adPlaybackState.adGroupTimesUs; - if (adGroupTimesUs == null) { - return C.INDEX_UNSET; - } - // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. - // In practice we expect there to be few ad groups so the search shouldn't be expensive. - int index = 0; - while (index < adGroupTimesUs.length && adGroupTimesUs[index] != C.TIME_END_OF_SOURCE - && (positionUs >= adGroupTimesUs[index] || hasPlayedAdGroup(index))) { - index++; - } - return index < adGroupTimesUs.length ? index : C.INDEX_UNSET; + return adPlaybackState.getAdGroupIndexAfterPositionUs(positionUs); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 2f6f81d6b9..8654e94bdb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -91,6 +91,11 @@ public final class AdPlaybackState { return nextAdIndexToPlay; } + /** Returns whether the ad group has at least one ad that still needs to be played. */ + public boolean hasUnplayedAds() { + return count == C.LENGTH_UNSET || getFirstAdIndexToPlay() < count; + } + /** * Returns a new instance with the ad count set to {@code count}. This method may only be called * if this instance's ad count has not yet been specified. @@ -270,6 +275,44 @@ public final class AdPlaybackState { this.contentDurationUs = contentDurationUs; } + /** + * Returns the index of the ad group at or before {@code positionUs}, if that ad group is + * unplayed. Returns {@link C#INDEX_UNSET} if the ad group at or before {@code positionUs} has no + * ads remaining to be played, or if there is no such ad group. + * + * @param positionUs The position at or before which to find an ad group, in microseconds. + * @return The index of the ad group, or {@link C#INDEX_UNSET}. + */ + public int getAdGroupIndexForPositionUs(long positionUs) { + // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. + // In practice we expect there to be few ad groups so the search shouldn't be expensive. + int index = adGroupTimesUs.length - 1; + while (index >= 0 + && (adGroupTimesUs[index] == C.TIME_END_OF_SOURCE || adGroupTimesUs[index] > positionUs)) { + index--; + } + return index >= 0 && adGroups[index].hasUnplayedAds() ? index : C.INDEX_UNSET; + } + + /** + * Returns the index of the next ad group after {@code positionUs} that has ads remaining to be + * played. Returns {@link C#INDEX_UNSET} if there is no such ad group. + * + * @param positionUs The position after which to find an ad group, in microseconds. + * @return The index of the ad group, or {@link C#INDEX_UNSET}. + */ + public int getAdGroupIndexAfterPositionUs(long positionUs) { + // Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE. + // In practice we expect there to be few ad groups so the search shouldn't be expensive. + int index = 0; + while (index < adGroupTimesUs.length + && adGroupTimesUs[index] != C.TIME_END_OF_SOURCE + && (positionUs >= adGroupTimesUs[index] || !adGroups[index].hasUnplayedAds())) { + index++; + } + return index < adGroupTimesUs.length ? index : C.INDEX_UNSET; + } + /** * Returns an instance with the number of ads in {@code adGroupIndex} resolved to {@code adCount}. * The ad count must be greater than zero. From fbc6ed2e8100cef3b77bba6f3191695b59558517 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 9 Feb 2018 07:24:45 -0800 Subject: [PATCH 21/53] Set the expected ad group based on the content position ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185139106 --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index a8573f31e3..d5e120afe7 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -558,22 +558,27 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A @Override public VideoProgressUpdate getContentProgress() { - boolean hasContentDuration = contentDurationMs != C.TIME_UNSET; - long contentDurationMs = hasContentDuration ? this.contentDurationMs : IMA_DURATION_UNSET; if (player == null) { return lastContentProgress; - } else if (pendingContentPositionMs != C.TIME_UNSET) { + } + boolean hasContentDuration = contentDurationMs != C.TIME_UNSET; + long contentPositionMs; + if (pendingContentPositionMs != C.TIME_UNSET) { sentPendingContentPositionMs = true; - return new VideoProgressUpdate(pendingContentPositionMs, contentDurationMs); + contentPositionMs = pendingContentPositionMs; } else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) { long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs; - long fakePositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs; - return new VideoProgressUpdate(fakePositionMs, contentDurationMs); - } else if (imaAdState != IMA_AD_STATE_NONE || !hasContentDuration) { - return VideoProgressUpdate.VIDEO_TIME_NOT_READY; + contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs; + } else if (imaAdState == IMA_AD_STATE_NONE && hasContentDuration) { + contentPositionMs = player.getCurrentPosition(); } else { - return new VideoProgressUpdate(player.getCurrentPosition(), contentDurationMs); + return VideoProgressUpdate.VIDEO_TIME_NOT_READY; } + // Keep track of the ad group index that IMA will load for the current content position. + expectedAdGroupIndex = + adPlaybackState.getAdGroupIndexAfterPositionUs(C.msToUs(contentPositionMs)); + long contentDurationMs = hasContentDuration ? this.contentDurationMs : IMA_DURATION_UNSET; + return new VideoProgressUpdate(contentPositionMs, contentDurationMs); } // VideoAdPlayer implementation. @@ -607,11 +612,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A int adIndexInAdGroup = getAdIndexInAdGroupToLoad(adGroupIndex); adPlaybackState = adPlaybackState.withAdUri(adGroupIndex, adIndexInAdGroup, Uri.parse(adUriString)); - if (getAdIndexInAdGroupToLoad(adGroupIndex) == C.INDEX_UNSET) { - // Keep track of the expected ad group index to use as a fallback if the LOADED event is - // unexpectedly not triggered. - expectedAdGroupIndex++; - } updateAdPlaybackState(); } @@ -774,7 +774,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A timeline.getPeriod(0, period); int newAdGroupIndex = period.getAdGroupIndexForPositionUs(C.msToUs(positionMs)); if (newAdGroupIndex != C.INDEX_UNSET) { - expectedAdGroupIndex = newAdGroupIndex; sentPendingContentPositionMs = false; pendingContentPositionMs = positionMs; } @@ -819,7 +818,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A // We're removing one or more ads, which means that the earliest ad (if any) will be a // midroll/postroll. Midroll pod indices start at 1. podIndexOffset = adGroupIndexForPosition - 1; - expectedAdGroupIndex = adGroupIndexForPosition; } // Start ad playback. @@ -910,9 +908,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A this.adGroupIndex == C.INDEX_UNSET ? expectedAdGroupIndex : this.adGroupIndex; AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; // Ad group load error can be notified more than once, so check if it was already handled. - // TODO: Update the expected ad group index based on the position returned by - // getContentProgress so that it's possible to detect when more than one ad group fails to load - // consecutively. if (adGroup.count == C.LENGTH_UNSET || adGroup.states[0] == AdPlaybackState.AD_STATE_UNAVAILABLE) { if (DEBUG) { From 6c82431e7555a5a64f571c4f82cdbeb0c9cf07bd Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 12 Feb 2018 05:41:57 -0800 Subject: [PATCH 22/53] Move (almost all) remaining core library instrumentation tests to Robolectric. There are 4 tests which can't currently be moved: - DownloadManagerTest explicitly uses the main looper which isn't easily supported because the test runs on this thread. - ContentDataSourceTest uses an AssetFileDescriptor which wraps a ParcelFileDescriptor. It seems Robolectric doesn't correctly forward the inner wrapped file descriptor leading to NPE. - CacheContentIndexTest and SimpleCacheSpanTest both work fine with Gradle but fail with seemingly valid test failures on Blaze. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185366678 --- .../ext/flac/FlacExtractorTest.java | 15 +- .../src/androidTest/assets/ssa/typical_format | 1 - library/core/src/test/AndroidManifest.xml | 23 ++ .../assets/binary/1024_incrementing_bytes.mp3 | Bin 0 -> 1024 bytes .../assets/flv/sample.flv | Bin .../assets/flv/sample.flv.0.dump | 0 .../assets/mkv/sample.mkv | Bin .../assets/mkv/sample.mkv.0.dump | 0 .../assets/mkv/sample.mkv.1.dump | 0 .../assets/mkv/sample.mkv.2.dump | 0 .../assets/mkv/sample.mkv.3.dump | 0 .../mkv/subsample_encrypted_altref.webm | Bin .../subsample_encrypted_altref.webm.0.dump | 0 .../mkv/subsample_encrypted_noaltref.webm | Bin .../subsample_encrypted_noaltref.webm.0.dump | 0 .../{androidTest => test}/assets/mp3/bear.mp3 | Bin .../assets/mp3/bear.mp3.0.dump | 0 .../assets/mp3/bear.mp3.1.dump | 0 .../assets/mp3/bear.mp3.2.dump | 0 .../assets/mp3/bear.mp3.3.dump | 0 .../assets/mp3/play-trimmed.mp3 | Bin .../assets/mp3/play-trimmed.mp3.0.dump | 0 .../assets/mp3/play-trimmed.mp3.1.dump | 0 .../assets/mp3/play-trimmed.mp3.2.dump | 0 .../assets/mp3/play-trimmed.mp3.3.dump | 0 .../assets/mp3/play-trimmed.mp3.unklen.dump | 0 .../assets/mp4/sample.mp4 | Bin .../assets/mp4/sample.mp4.0.dump | 0 .../assets/mp4/sample.mp4.1.dump | 0 .../assets/mp4/sample.mp4.2.dump | 0 .../assets/mp4/sample.mp4.3.dump | 0 .../assets/mp4/sample_fragmented.mp4 | Bin .../assets/mp4/sample_fragmented.mp4.0.dump | 0 .../assets/mp4/sample_fragmented_sei.mp4 | Bin .../mp4/sample_fragmented_sei.mp4.0.dump | 0 .../assets/ogg/bear.opus | Bin .../assets/ogg/bear.opus.0.dump | 0 .../assets/ogg/bear.opus.1.dump | 0 .../assets/ogg/bear.opus.2.dump | 0 .../assets/ogg/bear.opus.3.dump | 0 .../assets/ogg/bear.opus.unklen.dump | 0 .../assets/ogg/bear_flac.ogg | Bin .../assets/ogg/bear_flac.ogg.0.dump | 0 .../assets/ogg/bear_flac.ogg.1.dump | 0 .../assets/ogg/bear_flac.ogg.2.dump | 0 .../assets/ogg/bear_flac.ogg.3.dump | 0 .../assets/ogg/bear_flac.ogg.unklen.dump | 0 .../assets/ogg/bear_flac_noseektable.ogg | Bin .../ogg/bear_flac_noseektable.ogg.0.dump | 0 .../ogg/bear_flac_noseektable.ogg.1.dump | 0 .../ogg/bear_flac_noseektable.ogg.2.dump | 0 .../ogg/bear_flac_noseektable.ogg.3.dump | 0 .../ogg/bear_flac_noseektable.ogg.unklen.dump | 0 .../assets/ogg/bear_vorbis.ogg | Bin .../assets/ogg/bear_vorbis.ogg.0.dump | 0 .../assets/ogg/bear_vorbis.ogg.1.dump | 0 .../assets/ogg/bear_vorbis.ogg.2.dump | 0 .../assets/ogg/bear_vorbis.ogg.3.dump | 0 .../assets/ogg/bear_vorbis.ogg.unklen.dump | 0 .../assets/rawcc/sample.rawcc | Bin .../assets/rawcc/sample.rawcc.0.dump | 0 .../{androidTest => test}/assets/ssa/empty | 0 .../assets/ssa/invalid_timecodes | 2 +- .../assets/ssa/no_end_timecodes | 2 +- .../{androidTest => test}/assets/ssa/typical | 2 +- .../assets/ssa/typical_dialogue | 2 +- .../core/src/test/assets/ssa/typical_format | 1 + .../assets/ssa/typical_header | 2 +- .../{androidTest => test}/assets/subrip/empty | 0 .../assets/subrip/no_end_timecodes | 2 +- .../assets/subrip/typical | 2 +- .../assets/subrip/typical_extra_blank_line | 2 +- .../assets/subrip/typical_missing_sequence | 2 +- .../assets/subrip/typical_missing_timecode | 2 +- .../assets/subrip/typical_negative_timestamps | 0 .../assets/subrip/typical_unexpected_end | 2 +- .../subrip/typical_with_byte_order_mark | 2 +- .../assets/ts/sample.ac3 | Bin .../assets/ts/sample.ac3.0.dump | 0 .../assets/ts/sample.adts | Bin .../assets/ts/sample.adts.0.dump | 0 .../{androidTest => test}/assets/ts/sample.ps | Bin .../assets/ts/sample.ps.0.dump | 0 .../{androidTest => test}/assets/ts/sample.ts | Bin .../assets/ts/sample.ts.0.dump | 0 .../assets/ts/sample_with_sdt.ts | Bin .../assets/ttml/chain_multiple_styles.xml | 0 .../assets/ttml/font_size.xml | 0 .../assets/ttml/font_size_empty.xml | 0 .../assets/ttml/font_size_invalid.xml | 0 .../assets/ttml/font_size_no_unit.xml | 0 .../assets/ttml/frame_rate.xml | 0 .../ttml/inherit_and_override_style.xml | 0 .../assets/ttml/inherit_global_and_parent.xml | 0 .../assets/ttml/inherit_multiple_styles.xml | 0 .../assets/ttml/inherit_style.xml | 0 .../assets/ttml/inline_style_attributes.xml | 0 .../assets/ttml/multiple_regions.xml | 0 .../assets/ttml/no_underline_linethrough.xml | 0 .../assets/tx3g/initialization | Bin .../assets/tx3g/initialization_all_defaults | Bin .../assets/tx3g/no_subtitle | Bin .../assets/tx3g/sample_just_text | Bin .../assets/tx3g/sample_utf16_be_no_styl | Bin .../assets/tx3g/sample_utf16_le_no_styl | Bin .../assets/tx3g/sample_with_multiple_styl | Bin .../assets/tx3g/sample_with_other_extension | Bin .../assets/tx3g/sample_with_styl | Bin .../assets/tx3g/sample_with_styl_all_defaults | Bin .../assets/tx3g/sample_with_tbox | Bin .../assets/wav/sample.wav | Bin .../assets/wav/sample.wav.0.dump | 0 .../assets/wav/sample.wav.1.dump | 0 .../assets/wav/sample.wav.2.dump | 0 .../assets/wav/sample.wav.3.dump | 0 .../assets/webm/vorbis_codec_private | Bin .../{androidTest => test}/assets/webvtt/empty | 0 .../assets/webvtt/typical | 0 .../assets/webvtt/typical_with_bad_timestamps | 0 .../assets/webvtt/typical_with_comments | 0 .../assets/webvtt/typical_with_identifiers | 0 .../assets/webvtt/with_bad_cue_header | 0 .../assets/webvtt/with_css_complex_selectors | 0 .../assets/webvtt/with_css_styles | 0 .../assets/webvtt/with_positioning | 0 .../assets/webvtt/with_tags | 0 .../com/google/android/exoplayer2/CTest.java | 2 - .../exoplayer2/DefaultMediaClockTest.java | 3 +- .../android/exoplayer2/ExoPlayerTest.java | 6 +- .../google/android/exoplayer2/FormatTest.java | 2 - .../android/exoplayer2/TimelineTest.java | 22 +- .../audio/SimpleDecoderAudioRendererTest.java | 1 - .../audio/SonicAudioProcessorTest.java | 2 - .../exoplayer2/drm/ClearKeyUtilTest.java | 9 +- .../exoplayer2/drm/DrmInitDataTest.java | 2 - .../drm/OfflineLicenseHelperTest.java | 66 +++-- .../extractor/DefaultExtractorInputTest.java | 2 - .../exoplayer2/extractor/ExtractorTest.java | 2 - .../extractor/flv/FlvExtractorTest.java | 27 +- .../extractor/mkv/DefaultEbmlReaderTest.java | 2 - .../extractor/mkv/MatroskaExtractorTest.java | 57 +++-- .../extractor/mkv/VarintReaderTest.java | 2 - .../extractor/mp3/Mp3ExtractorTest.java | 42 +-- .../extractor/mp3/XingSeekerTest.java | 2 - .../extractor/mp4/AtomParsersTest.java | 2 - .../mp4/FragmentedMp4ExtractorTest.java | 27 +- .../extractor/mp4/Mp4ExtractorTest.java | 27 +- .../extractor/mp4/PsshAtomUtilTest.java | 2 - .../extractor/ogg/DefaultOggSeekerTest.java | 40 ++- .../ogg/DefaultOggSeekerUtilMethodsTest.java | 2 - .../extractor/ogg/OggExtractorTest.java | 56 ++-- .../extractor/ogg/OggPacketTest.java | 142 ++++++----- .../extractor/ogg/OggPageHeaderTest.java | 2 - .../exoplayer2/extractor/ogg/OggTestFile.java | 21 +- .../extractor/ogg/VorbisBitArrayTest.java | 2 - .../extractor/ogg/VorbisReaderTest.java | 2 - .../extractor/ogg/VorbisUtilTest.java | 2 - .../extractor/rawcc/RawCcExtractorTest.java | 27 +- .../extractor/ts/Ac3ExtractorTest.java | 27 +- .../extractor/ts/AdtsExtractorTest.java | 27 +- .../extractor/ts/AdtsReaderTest.java | 88 ++++--- .../extractor/ts/PsExtractorTest.java | 27 +- .../extractor/ts/SectionReaderTest.java | 2 - .../extractor/ts/TsExtractorTest.java | 102 ++++---- .../extractor/wav/WavExtractorTest.java | 27 +- .../emsg/EventMessageDecoderTest.java | 2 - .../emsg/EventMessageEncoderTest.java | 2 - .../metadata/emsg/EventMessageTest.java | 2 - .../metadata/id3/ChapterFrameTest.java | 2 - .../metadata/id3/ChapterTocFrameTest.java | 2 - .../metadata/id3/Id3DecoderTest.java | 2 - .../scte35/SpliceInfoDecoderTest.java | 2 - .../source/ClippingMediaSourceTest.java | 69 +++-- .../CompositeSequenceableLoaderTest.java | 2 - .../source/ConcatenatingMediaSourceTest.java | 161 ++++++------ .../DynamicConcatenatingMediaSourceTest.java | 241 ++++++++++-------- .../source/LoopingMediaSourceTest.java | 85 +++--- .../source/MergingMediaSourceTest.java | 33 ++- .../exoplayer2/source/SampleQueueTest.java | 2 - .../exoplayer2/source/ShuffleOrderTest.java | 2 - .../source/SinglePeriodTimelineTest.java | 2 - .../source/ads/AdPlaybackStateTest.java | 2 - .../exoplayer2/text/ssa/SsaDecoderTest.java | 32 ++- .../text/subrip/SubripDecoderTest.java | 42 +-- .../exoplayer2/text/ttml/TtmlDecoderTest.java | 224 ++++++++++++---- .../text/ttml/TtmlRenderUtilTest.java | 2 - .../exoplayer2/text/ttml/TtmlStyleTest.java | 2 - .../exoplayer2/text/tx3g/Tx3gDecoderTest.java | 64 +++-- .../exoplayer2/text/webvtt/CssParserTest.java | 2 - .../text/webvtt/Mp4WebvttDecoderTest.java | 2 - .../text/webvtt/WebvttCueParserTest.java | 2 - .../text/webvtt/WebvttDecoderTest.java | 162 +++++++++--- .../text/webvtt/WebvttSubtitleTest.java | 2 - .../AdaptiveTrackSelectionTest.java | 2 - .../DefaultTrackSelectorTest.java | 2 - .../MappingTrackSelectorTest.java | 2 - .../upstream/AssetDataSourceTest.java | 33 ++- .../upstream/ByteArrayDataSourceTest.java | 2 - .../upstream/DataSchemeDataSourceTest.java | 2 - .../upstream/DataSourceInputStreamTest.java | 2 - .../upstream/cache/CacheDataSourceTest.java | 2 - .../upstream/cache/CacheDataSourceTest2.java | 2 - .../upstream/cache/CacheUtilTest.java | 2 - .../cache/CachedRegionTrackerTest.java | 96 ++++--- .../LeastRecentlyUsedCacheEvictorTest.java | 2 - .../upstream/cache/SimpleCacheTest.java | 2 - .../crypto/AesFlushingCipherTest.java | 2 - .../exoplayer2/util/AtomicFileTest.java | 2 - .../exoplayer2/util/ColorParserTest.java | 2 - .../exoplayer2/util/NalUnitUtilTest.java | 2 - .../exoplayer2/util/ParsableBitArrayTest.java | 2 - .../util/ParsableByteArrayTest.java | 2 - .../util/ParsableNalUnitBitArrayTest.java | 2 - .../ReusableBufferedOutputStreamTest.java | 2 - .../android/exoplayer2/util/UriUtilTest.java | 2 - .../android/exoplayer2/util/UtilTest.java | 2 - .../exoplayer2/testutil/ExtractorAsserts.java | 158 ++++++++---- .../testutil/FakeExtractorOutput.java | 14 +- .../testutil/MediaSourceTestRunner.java | 62 +++-- .../android/exoplayer2/testutil/TestUtil.java | 5 +- 220 files changed, 1488 insertions(+), 1040 deletions(-) delete mode 100644 library/core/src/androidTest/assets/ssa/typical_format create mode 100644 library/core/src/test/AndroidManifest.xml create mode 100644 library/core/src/test/assets/binary/1024_incrementing_bytes.mp3 rename library/core/src/{androidTest => test}/assets/flv/sample.flv (100%) rename library/core/src/{androidTest => test}/assets/flv/sample.flv.0.dump (100%) rename library/core/src/{androidTest => test}/assets/mkv/sample.mkv (100%) rename library/core/src/{androidTest => test}/assets/mkv/sample.mkv.0.dump (100%) rename library/core/src/{androidTest => test}/assets/mkv/sample.mkv.1.dump (100%) rename library/core/src/{androidTest => test}/assets/mkv/sample.mkv.2.dump (100%) rename library/core/src/{androidTest => test}/assets/mkv/sample.mkv.3.dump (100%) rename library/core/src/{androidTest => test}/assets/mkv/subsample_encrypted_altref.webm (100%) rename library/core/src/{androidTest => test}/assets/mkv/subsample_encrypted_altref.webm.0.dump (100%) rename library/core/src/{androidTest => test}/assets/mkv/subsample_encrypted_noaltref.webm (100%) rename library/core/src/{androidTest => test}/assets/mkv/subsample_encrypted_noaltref.webm.0.dump (100%) rename library/core/src/{androidTest => test}/assets/mp3/bear.mp3 (100%) rename library/core/src/{androidTest => test}/assets/mp3/bear.mp3.0.dump (100%) rename library/core/src/{androidTest => test}/assets/mp3/bear.mp3.1.dump (100%) rename library/core/src/{androidTest => test}/assets/mp3/bear.mp3.2.dump (100%) rename library/core/src/{androidTest => test}/assets/mp3/bear.mp3.3.dump (100%) rename library/core/src/{androidTest => test}/assets/mp3/play-trimmed.mp3 (100%) rename library/core/src/{androidTest => test}/assets/mp3/play-trimmed.mp3.0.dump (100%) rename library/core/src/{androidTest => test}/assets/mp3/play-trimmed.mp3.1.dump (100%) rename library/core/src/{androidTest => test}/assets/mp3/play-trimmed.mp3.2.dump (100%) rename library/core/src/{androidTest => test}/assets/mp3/play-trimmed.mp3.3.dump (100%) rename library/core/src/{androidTest => test}/assets/mp3/play-trimmed.mp3.unklen.dump (100%) rename library/core/src/{androidTest => test}/assets/mp4/sample.mp4 (100%) rename library/core/src/{androidTest => test}/assets/mp4/sample.mp4.0.dump (100%) rename library/core/src/{androidTest => test}/assets/mp4/sample.mp4.1.dump (100%) rename library/core/src/{androidTest => test}/assets/mp4/sample.mp4.2.dump (100%) rename library/core/src/{androidTest => test}/assets/mp4/sample.mp4.3.dump (100%) rename library/core/src/{androidTest => test}/assets/mp4/sample_fragmented.mp4 (100%) rename library/core/src/{androidTest => test}/assets/mp4/sample_fragmented.mp4.0.dump (100%) rename library/core/src/{androidTest => test}/assets/mp4/sample_fragmented_sei.mp4 (100%) rename library/core/src/{androidTest => test}/assets/mp4/sample_fragmented_sei.mp4.0.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear.opus (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear.opus.0.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear.opus.1.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear.opus.2.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear.opus.3.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear.opus.unklen.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac.ogg (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac.ogg.0.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac.ogg.1.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac.ogg.2.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac.ogg.3.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac.ogg.unklen.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac_noseektable.ogg (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac_noseektable.ogg.0.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac_noseektable.ogg.1.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac_noseektable.ogg.2.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac_noseektable.ogg.3.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_flac_noseektable.ogg.unklen.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_vorbis.ogg (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_vorbis.ogg.0.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_vorbis.ogg.1.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_vorbis.ogg.2.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_vorbis.ogg.3.dump (100%) rename library/core/src/{androidTest => test}/assets/ogg/bear_vorbis.ogg.unklen.dump (100%) rename library/core/src/{androidTest => test}/assets/rawcc/sample.rawcc (100%) rename library/core/src/{androidTest => test}/assets/rawcc/sample.rawcc.0.dump (100%) rename library/core/src/{androidTest => test}/assets/ssa/empty (100%) rename library/core/src/{androidTest => test}/assets/ssa/invalid_timecodes (96%) rename library/core/src/{androidTest => test}/assets/ssa/no_end_timecodes (96%) rename library/core/src/{androidTest => test}/assets/ssa/typical (96%) rename library/core/src/{androidTest => test}/assets/ssa/typical_dialogue (91%) create mode 100644 library/core/src/test/assets/ssa/typical_format rename library/core/src/{androidTest => test}/assets/ssa/typical_header (85%) rename library/core/src/{androidTest => test}/assets/subrip/empty (100%) rename library/core/src/{androidTest => test}/assets/subrip/no_end_timecodes (87%) rename library/core/src/{androidTest => test}/assets/subrip/typical (87%) rename library/core/src/{androidTest => test}/assets/subrip/typical_extra_blank_line (87%) rename library/core/src/{androidTest => test}/assets/subrip/typical_missing_sequence (86%) rename library/core/src/{androidTest => test}/assets/subrip/typical_missing_timecode (85%) rename library/core/src/{androidTest => test}/assets/subrip/typical_negative_timestamps (100%) rename library/core/src/{androidTest => test}/assets/subrip/typical_unexpected_end (98%) rename library/core/src/{androidTest => test}/assets/subrip/typical_with_byte_order_mark (87%) rename library/core/src/{androidTest => test}/assets/ts/sample.ac3 (100%) rename library/core/src/{androidTest => test}/assets/ts/sample.ac3.0.dump (100%) rename library/core/src/{androidTest => test}/assets/ts/sample.adts (100%) rename library/core/src/{androidTest => test}/assets/ts/sample.adts.0.dump (100%) rename library/core/src/{androidTest => test}/assets/ts/sample.ps (100%) rename library/core/src/{androidTest => test}/assets/ts/sample.ps.0.dump (100%) rename library/core/src/{androidTest => test}/assets/ts/sample.ts (100%) rename library/core/src/{androidTest => test}/assets/ts/sample.ts.0.dump (100%) rename library/core/src/{androidTest => test}/assets/ts/sample_with_sdt.ts (100%) rename library/core/src/{androidTest => test}/assets/ttml/chain_multiple_styles.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/font_size.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/font_size_empty.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/font_size_invalid.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/font_size_no_unit.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/frame_rate.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/inherit_and_override_style.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/inherit_global_and_parent.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/inherit_multiple_styles.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/inherit_style.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/inline_style_attributes.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/multiple_regions.xml (100%) rename library/core/src/{androidTest => test}/assets/ttml/no_underline_linethrough.xml (100%) rename library/core/src/{androidTest => test}/assets/tx3g/initialization (100%) rename library/core/src/{androidTest => test}/assets/tx3g/initialization_all_defaults (100%) rename library/core/src/{androidTest => test}/assets/tx3g/no_subtitle (100%) rename library/core/src/{androidTest => test}/assets/tx3g/sample_just_text (100%) rename library/core/src/{androidTest => test}/assets/tx3g/sample_utf16_be_no_styl (100%) rename library/core/src/{androidTest => test}/assets/tx3g/sample_utf16_le_no_styl (100%) rename library/core/src/{androidTest => test}/assets/tx3g/sample_with_multiple_styl (100%) rename library/core/src/{androidTest => test}/assets/tx3g/sample_with_other_extension (100%) rename library/core/src/{androidTest => test}/assets/tx3g/sample_with_styl (100%) rename library/core/src/{androidTest => test}/assets/tx3g/sample_with_styl_all_defaults (100%) rename library/core/src/{androidTest => test}/assets/tx3g/sample_with_tbox (100%) rename library/core/src/{androidTest => test}/assets/wav/sample.wav (100%) rename library/core/src/{androidTest => test}/assets/wav/sample.wav.0.dump (100%) rename library/core/src/{androidTest => test}/assets/wav/sample.wav.1.dump (100%) rename library/core/src/{androidTest => test}/assets/wav/sample.wav.2.dump (100%) rename library/core/src/{androidTest => test}/assets/wav/sample.wav.3.dump (100%) rename library/core/src/{androidTest => test}/assets/webm/vorbis_codec_private (100%) rename library/core/src/{androidTest => test}/assets/webvtt/empty (100%) rename library/core/src/{androidTest => test}/assets/webvtt/typical (100%) rename library/core/src/{androidTest => test}/assets/webvtt/typical_with_bad_timestamps (100%) rename library/core/src/{androidTest => test}/assets/webvtt/typical_with_comments (100%) rename library/core/src/{androidTest => test}/assets/webvtt/typical_with_identifiers (100%) rename library/core/src/{androidTest => test}/assets/webvtt/with_bad_cue_header (100%) rename library/core/src/{androidTest => test}/assets/webvtt/with_css_complex_selectors (100%) rename library/core/src/{androidTest => test}/assets/webvtt/with_css_styles (100%) rename library/core/src/{androidTest => test}/assets/webvtt/with_positioning (100%) rename library/core/src/{androidTest => test}/assets/webvtt/with_tags (100%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/TimelineTest.java (83%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java (74%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java (67%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java (53%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java (59%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java (71%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java (69%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java (84%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java (79%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java (68%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java (91%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java (69%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java (67%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java (67%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java (69%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java (67%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java (79%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java (67%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java (77%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java (70%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java (86%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java (61%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java (76%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java (83%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java (84%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java (83%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java (84%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java (75%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java (58%) rename library/core/src/{androidTest => test}/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java (59%) diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java index 57ce487ac7..c5f1f5c146 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacExtractorTest.java @@ -34,11 +34,14 @@ public class FlacExtractorTest extends InstrumentationTestCase { } public void testSample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new FlacExtractor(); - } - }, "bear.flac", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new FlacExtractor(); + } + }, + "bear.flac", + getInstrumentation().getContext()); } } diff --git a/library/core/src/androidTest/assets/ssa/typical_format b/library/core/src/androidTest/assets/ssa/typical_format deleted file mode 100644 index 0cc5f1690f..0000000000 --- a/library/core/src/androidTest/assets/ssa/typical_format +++ /dev/null @@ -1 +0,0 @@ -Format: Layer, Start, End, Style, Name, Text \ No newline at end of file diff --git a/library/core/src/test/AndroidManifest.xml b/library/core/src/test/AndroidManifest.xml new file mode 100644 index 0000000000..660c33c636 --- /dev/null +++ b/library/core/src/test/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/library/core/src/test/assets/binary/1024_incrementing_bytes.mp3 b/library/core/src/test/assets/binary/1024_incrementing_bytes.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..c8b49c8cd518e58491924bfc364ff26e01a85009 GIT binary patch literal 1024 zcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{)Y8_`)zddH zG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUz zF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v` -Or to the end of the media. \ No newline at end of file +Or to the end of the media. diff --git a/library/core/src/androidTest/assets/subrip/typical b/library/core/src/test/assets/subrip/typical similarity index 87% rename from library/core/src/androidTest/assets/subrip/typical rename to library/core/src/test/assets/subrip/typical index 1c8ce4dd43..1331f75651 100644 --- a/library/core/src/androidTest/assets/subrip/typical +++ b/library/core/src/test/assets/subrip/typical @@ -9,4 +9,4 @@ Second subtitle with second line. 3 00:00:04,567 --> 00:00:08,901 -This is the third subtitle. \ No newline at end of file +This is the third subtitle. diff --git a/library/core/src/androidTest/assets/subrip/typical_extra_blank_line b/library/core/src/test/assets/subrip/typical_extra_blank_line similarity index 87% rename from library/core/src/androidTest/assets/subrip/typical_extra_blank_line rename to library/core/src/test/assets/subrip/typical_extra_blank_line index 83508dd733..f5882a1d68 100644 --- a/library/core/src/androidTest/assets/subrip/typical_extra_blank_line +++ b/library/core/src/test/assets/subrip/typical_extra_blank_line @@ -10,4 +10,4 @@ Second subtitle with second line. 3 00:00:04,567 --> 00:00:08,901 -This is the third subtitle. \ No newline at end of file +This is the third subtitle. diff --git a/library/core/src/androidTest/assets/subrip/typical_missing_sequence b/library/core/src/test/assets/subrip/typical_missing_sequence similarity index 86% rename from library/core/src/androidTest/assets/subrip/typical_missing_sequence rename to library/core/src/test/assets/subrip/typical_missing_sequence index 9318ba3239..56d49ac63c 100644 --- a/library/core/src/androidTest/assets/subrip/typical_missing_sequence +++ b/library/core/src/test/assets/subrip/typical_missing_sequence @@ -8,4 +8,4 @@ Second subtitle with second line. 3 00:00:04,567 --> 00:00:08,901 -This is the third subtitle. \ No newline at end of file +This is the third subtitle. diff --git a/library/core/src/androidTest/assets/subrip/typical_missing_timecode b/library/core/src/test/assets/subrip/typical_missing_timecode similarity index 85% rename from library/core/src/androidTest/assets/subrip/typical_missing_timecode rename to library/core/src/test/assets/subrip/typical_missing_timecode index b9c999ada9..2c6fe69b6f 100644 --- a/library/core/src/androidTest/assets/subrip/typical_missing_timecode +++ b/library/core/src/test/assets/subrip/typical_missing_timecode @@ -8,4 +8,4 @@ Second subtitle with second line. 3 00:00:04,567 --> 00:00:08,901 -This is the third subtitle. \ No newline at end of file +This is the third subtitle. diff --git a/library/core/src/androidTest/assets/subrip/typical_negative_timestamps b/library/core/src/test/assets/subrip/typical_negative_timestamps similarity index 100% rename from library/core/src/androidTest/assets/subrip/typical_negative_timestamps rename to library/core/src/test/assets/subrip/typical_negative_timestamps diff --git a/library/core/src/androidTest/assets/subrip/typical_unexpected_end b/library/core/src/test/assets/subrip/typical_unexpected_end similarity index 98% rename from library/core/src/androidTest/assets/subrip/typical_unexpected_end rename to library/core/src/test/assets/subrip/typical_unexpected_end index 8e2949b8db..91e82b1174 100644 --- a/library/core/src/androidTest/assets/subrip/typical_unexpected_end +++ b/library/core/src/test/assets/subrip/typical_unexpected_end @@ -7,4 +7,4 @@ This is the first subtitle. This is the second subtitle. Second subtitle with second line. -3 \ No newline at end of file +3 diff --git a/library/core/src/androidTest/assets/subrip/typical_with_byte_order_mark b/library/core/src/test/assets/subrip/typical_with_byte_order_mark similarity index 87% rename from library/core/src/androidTest/assets/subrip/typical_with_byte_order_mark rename to library/core/src/test/assets/subrip/typical_with_byte_order_mark index 9601c99bfb..4f5b32f4d7 100644 --- a/library/core/src/androidTest/assets/subrip/typical_with_byte_order_mark +++ b/library/core/src/test/assets/subrip/typical_with_byte_order_mark @@ -9,4 +9,4 @@ Second subtitle with second line. 3 00:00:04,567 --> 00:00:08,901 -This is the third subtitle. \ No newline at end of file +This is the third subtitle. diff --git a/library/core/src/androidTest/assets/ts/sample.ac3 b/library/core/src/test/assets/ts/sample.ac3 similarity index 100% rename from library/core/src/androidTest/assets/ts/sample.ac3 rename to library/core/src/test/assets/ts/sample.ac3 diff --git a/library/core/src/androidTest/assets/ts/sample.ac3.0.dump b/library/core/src/test/assets/ts/sample.ac3.0.dump similarity index 100% rename from library/core/src/androidTest/assets/ts/sample.ac3.0.dump rename to library/core/src/test/assets/ts/sample.ac3.0.dump diff --git a/library/core/src/androidTest/assets/ts/sample.adts b/library/core/src/test/assets/ts/sample.adts similarity index 100% rename from library/core/src/androidTest/assets/ts/sample.adts rename to library/core/src/test/assets/ts/sample.adts diff --git a/library/core/src/androidTest/assets/ts/sample.adts.0.dump b/library/core/src/test/assets/ts/sample.adts.0.dump similarity index 100% rename from library/core/src/androidTest/assets/ts/sample.adts.0.dump rename to library/core/src/test/assets/ts/sample.adts.0.dump diff --git a/library/core/src/androidTest/assets/ts/sample.ps b/library/core/src/test/assets/ts/sample.ps similarity index 100% rename from library/core/src/androidTest/assets/ts/sample.ps rename to library/core/src/test/assets/ts/sample.ps diff --git a/library/core/src/androidTest/assets/ts/sample.ps.0.dump b/library/core/src/test/assets/ts/sample.ps.0.dump similarity index 100% rename from library/core/src/androidTest/assets/ts/sample.ps.0.dump rename to library/core/src/test/assets/ts/sample.ps.0.dump diff --git a/library/core/src/androidTest/assets/ts/sample.ts b/library/core/src/test/assets/ts/sample.ts similarity index 100% rename from library/core/src/androidTest/assets/ts/sample.ts rename to library/core/src/test/assets/ts/sample.ts diff --git a/library/core/src/androidTest/assets/ts/sample.ts.0.dump b/library/core/src/test/assets/ts/sample.ts.0.dump similarity index 100% rename from library/core/src/androidTest/assets/ts/sample.ts.0.dump rename to library/core/src/test/assets/ts/sample.ts.0.dump diff --git a/library/core/src/androidTest/assets/ts/sample_with_sdt.ts b/library/core/src/test/assets/ts/sample_with_sdt.ts similarity index 100% rename from library/core/src/androidTest/assets/ts/sample_with_sdt.ts rename to library/core/src/test/assets/ts/sample_with_sdt.ts diff --git a/library/core/src/androidTest/assets/ttml/chain_multiple_styles.xml b/library/core/src/test/assets/ttml/chain_multiple_styles.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/chain_multiple_styles.xml rename to library/core/src/test/assets/ttml/chain_multiple_styles.xml diff --git a/library/core/src/androidTest/assets/ttml/font_size.xml b/library/core/src/test/assets/ttml/font_size.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/font_size.xml rename to library/core/src/test/assets/ttml/font_size.xml diff --git a/library/core/src/androidTest/assets/ttml/font_size_empty.xml b/library/core/src/test/assets/ttml/font_size_empty.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/font_size_empty.xml rename to library/core/src/test/assets/ttml/font_size_empty.xml diff --git a/library/core/src/androidTest/assets/ttml/font_size_invalid.xml b/library/core/src/test/assets/ttml/font_size_invalid.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/font_size_invalid.xml rename to library/core/src/test/assets/ttml/font_size_invalid.xml diff --git a/library/core/src/androidTest/assets/ttml/font_size_no_unit.xml b/library/core/src/test/assets/ttml/font_size_no_unit.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/font_size_no_unit.xml rename to library/core/src/test/assets/ttml/font_size_no_unit.xml diff --git a/library/core/src/androidTest/assets/ttml/frame_rate.xml b/library/core/src/test/assets/ttml/frame_rate.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/frame_rate.xml rename to library/core/src/test/assets/ttml/frame_rate.xml diff --git a/library/core/src/androidTest/assets/ttml/inherit_and_override_style.xml b/library/core/src/test/assets/ttml/inherit_and_override_style.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/inherit_and_override_style.xml rename to library/core/src/test/assets/ttml/inherit_and_override_style.xml diff --git a/library/core/src/androidTest/assets/ttml/inherit_global_and_parent.xml b/library/core/src/test/assets/ttml/inherit_global_and_parent.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/inherit_global_and_parent.xml rename to library/core/src/test/assets/ttml/inherit_global_and_parent.xml diff --git a/library/core/src/androidTest/assets/ttml/inherit_multiple_styles.xml b/library/core/src/test/assets/ttml/inherit_multiple_styles.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/inherit_multiple_styles.xml rename to library/core/src/test/assets/ttml/inherit_multiple_styles.xml diff --git a/library/core/src/androidTest/assets/ttml/inherit_style.xml b/library/core/src/test/assets/ttml/inherit_style.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/inherit_style.xml rename to library/core/src/test/assets/ttml/inherit_style.xml diff --git a/library/core/src/androidTest/assets/ttml/inline_style_attributes.xml b/library/core/src/test/assets/ttml/inline_style_attributes.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/inline_style_attributes.xml rename to library/core/src/test/assets/ttml/inline_style_attributes.xml diff --git a/library/core/src/androidTest/assets/ttml/multiple_regions.xml b/library/core/src/test/assets/ttml/multiple_regions.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/multiple_regions.xml rename to library/core/src/test/assets/ttml/multiple_regions.xml diff --git a/library/core/src/androidTest/assets/ttml/no_underline_linethrough.xml b/library/core/src/test/assets/ttml/no_underline_linethrough.xml similarity index 100% rename from library/core/src/androidTest/assets/ttml/no_underline_linethrough.xml rename to library/core/src/test/assets/ttml/no_underline_linethrough.xml diff --git a/library/core/src/androidTest/assets/tx3g/initialization b/library/core/src/test/assets/tx3g/initialization similarity index 100% rename from library/core/src/androidTest/assets/tx3g/initialization rename to library/core/src/test/assets/tx3g/initialization diff --git a/library/core/src/androidTest/assets/tx3g/initialization_all_defaults b/library/core/src/test/assets/tx3g/initialization_all_defaults similarity index 100% rename from library/core/src/androidTest/assets/tx3g/initialization_all_defaults rename to library/core/src/test/assets/tx3g/initialization_all_defaults diff --git a/library/core/src/androidTest/assets/tx3g/no_subtitle b/library/core/src/test/assets/tx3g/no_subtitle similarity index 100% rename from library/core/src/androidTest/assets/tx3g/no_subtitle rename to library/core/src/test/assets/tx3g/no_subtitle diff --git a/library/core/src/androidTest/assets/tx3g/sample_just_text b/library/core/src/test/assets/tx3g/sample_just_text similarity index 100% rename from library/core/src/androidTest/assets/tx3g/sample_just_text rename to library/core/src/test/assets/tx3g/sample_just_text diff --git a/library/core/src/androidTest/assets/tx3g/sample_utf16_be_no_styl b/library/core/src/test/assets/tx3g/sample_utf16_be_no_styl similarity index 100% rename from library/core/src/androidTest/assets/tx3g/sample_utf16_be_no_styl rename to library/core/src/test/assets/tx3g/sample_utf16_be_no_styl diff --git a/library/core/src/androidTest/assets/tx3g/sample_utf16_le_no_styl b/library/core/src/test/assets/tx3g/sample_utf16_le_no_styl similarity index 100% rename from library/core/src/androidTest/assets/tx3g/sample_utf16_le_no_styl rename to library/core/src/test/assets/tx3g/sample_utf16_le_no_styl diff --git a/library/core/src/androidTest/assets/tx3g/sample_with_multiple_styl b/library/core/src/test/assets/tx3g/sample_with_multiple_styl similarity index 100% rename from library/core/src/androidTest/assets/tx3g/sample_with_multiple_styl rename to library/core/src/test/assets/tx3g/sample_with_multiple_styl diff --git a/library/core/src/androidTest/assets/tx3g/sample_with_other_extension b/library/core/src/test/assets/tx3g/sample_with_other_extension similarity index 100% rename from library/core/src/androidTest/assets/tx3g/sample_with_other_extension rename to library/core/src/test/assets/tx3g/sample_with_other_extension diff --git a/library/core/src/androidTest/assets/tx3g/sample_with_styl b/library/core/src/test/assets/tx3g/sample_with_styl similarity index 100% rename from library/core/src/androidTest/assets/tx3g/sample_with_styl rename to library/core/src/test/assets/tx3g/sample_with_styl diff --git a/library/core/src/androidTest/assets/tx3g/sample_with_styl_all_defaults b/library/core/src/test/assets/tx3g/sample_with_styl_all_defaults similarity index 100% rename from library/core/src/androidTest/assets/tx3g/sample_with_styl_all_defaults rename to library/core/src/test/assets/tx3g/sample_with_styl_all_defaults diff --git a/library/core/src/androidTest/assets/tx3g/sample_with_tbox b/library/core/src/test/assets/tx3g/sample_with_tbox similarity index 100% rename from library/core/src/androidTest/assets/tx3g/sample_with_tbox rename to library/core/src/test/assets/tx3g/sample_with_tbox diff --git a/library/core/src/androidTest/assets/wav/sample.wav b/library/core/src/test/assets/wav/sample.wav similarity index 100% rename from library/core/src/androidTest/assets/wav/sample.wav rename to library/core/src/test/assets/wav/sample.wav diff --git a/library/core/src/androidTest/assets/wav/sample.wav.0.dump b/library/core/src/test/assets/wav/sample.wav.0.dump similarity index 100% rename from library/core/src/androidTest/assets/wav/sample.wav.0.dump rename to library/core/src/test/assets/wav/sample.wav.0.dump diff --git a/library/core/src/androidTest/assets/wav/sample.wav.1.dump b/library/core/src/test/assets/wav/sample.wav.1.dump similarity index 100% rename from library/core/src/androidTest/assets/wav/sample.wav.1.dump rename to library/core/src/test/assets/wav/sample.wav.1.dump diff --git a/library/core/src/androidTest/assets/wav/sample.wav.2.dump b/library/core/src/test/assets/wav/sample.wav.2.dump similarity index 100% rename from library/core/src/androidTest/assets/wav/sample.wav.2.dump rename to library/core/src/test/assets/wav/sample.wav.2.dump diff --git a/library/core/src/androidTest/assets/wav/sample.wav.3.dump b/library/core/src/test/assets/wav/sample.wav.3.dump similarity index 100% rename from library/core/src/androidTest/assets/wav/sample.wav.3.dump rename to library/core/src/test/assets/wav/sample.wav.3.dump diff --git a/library/core/src/androidTest/assets/webm/vorbis_codec_private b/library/core/src/test/assets/webm/vorbis_codec_private similarity index 100% rename from library/core/src/androidTest/assets/webm/vorbis_codec_private rename to library/core/src/test/assets/webm/vorbis_codec_private diff --git a/library/core/src/androidTest/assets/webvtt/empty b/library/core/src/test/assets/webvtt/empty similarity index 100% rename from library/core/src/androidTest/assets/webvtt/empty rename to library/core/src/test/assets/webvtt/empty diff --git a/library/core/src/androidTest/assets/webvtt/typical b/library/core/src/test/assets/webvtt/typical similarity index 100% rename from library/core/src/androidTest/assets/webvtt/typical rename to library/core/src/test/assets/webvtt/typical diff --git a/library/core/src/androidTest/assets/webvtt/typical_with_bad_timestamps b/library/core/src/test/assets/webvtt/typical_with_bad_timestamps similarity index 100% rename from library/core/src/androidTest/assets/webvtt/typical_with_bad_timestamps rename to library/core/src/test/assets/webvtt/typical_with_bad_timestamps diff --git a/library/core/src/androidTest/assets/webvtt/typical_with_comments b/library/core/src/test/assets/webvtt/typical_with_comments similarity index 100% rename from library/core/src/androidTest/assets/webvtt/typical_with_comments rename to library/core/src/test/assets/webvtt/typical_with_comments diff --git a/library/core/src/androidTest/assets/webvtt/typical_with_identifiers b/library/core/src/test/assets/webvtt/typical_with_identifiers similarity index 100% rename from library/core/src/androidTest/assets/webvtt/typical_with_identifiers rename to library/core/src/test/assets/webvtt/typical_with_identifiers diff --git a/library/core/src/androidTest/assets/webvtt/with_bad_cue_header b/library/core/src/test/assets/webvtt/with_bad_cue_header similarity index 100% rename from library/core/src/androidTest/assets/webvtt/with_bad_cue_header rename to library/core/src/test/assets/webvtt/with_bad_cue_header diff --git a/library/core/src/androidTest/assets/webvtt/with_css_complex_selectors b/library/core/src/test/assets/webvtt/with_css_complex_selectors similarity index 100% rename from library/core/src/androidTest/assets/webvtt/with_css_complex_selectors rename to library/core/src/test/assets/webvtt/with_css_complex_selectors diff --git a/library/core/src/androidTest/assets/webvtt/with_css_styles b/library/core/src/test/assets/webvtt/with_css_styles similarity index 100% rename from library/core/src/androidTest/assets/webvtt/with_css_styles rename to library/core/src/test/assets/webvtt/with_css_styles diff --git a/library/core/src/androidTest/assets/webvtt/with_positioning b/library/core/src/test/assets/webvtt/with_positioning similarity index 100% rename from library/core/src/androidTest/assets/webvtt/with_positioning rename to library/core/src/test/assets/webvtt/with_positioning diff --git a/library/core/src/androidTest/assets/webvtt/with_tags b/library/core/src/test/assets/webvtt/with_tags similarity index 100% rename from library/core/src/androidTest/assets/webvtt/with_tags rename to library/core/src/test/assets/webvtt/with_tags diff --git a/library/core/src/test/java/com/google/android/exoplayer2/CTest.java b/library/core/src/test/java/com/google/android/exoplayer2/CTest.java index ff4756f5ed..f56510f654 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/CTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/CTest.java @@ -22,13 +22,11 @@ import android.media.MediaCodec; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link C}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class CTest { @SuppressLint("InlinedApi") diff --git a/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java b/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java index 53872c59f0..ece22dc02a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/DefaultMediaClockTest.java @@ -30,13 +30,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link DefaultMediaClock}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class DefaultMediaClockTest { private static final long TEST_POSITION_US = 123456789012345678L; @@ -377,6 +375,7 @@ public class DefaultMediaClockTest { assertThat(mediaClock.syncAndGetPositionUs()).isEqualTo(positionAtStartUs); } + @SuppressWarnings("HidingField") private static class MediaClockRenderer extends FakeMediaClockRenderer { private final boolean playbackParametersAreMutable; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index ec5a8ccfce..7faa349705 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -55,11 +55,7 @@ import org.robolectric.annotation.Config; /** Unit test for {@link ExoPlayer}. */ @RunWith(RobolectricTestRunner.class) -@Config( - sdk = Config.TARGET_SDK, - manifest = Config.NONE, - shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class} -) +@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class}) public final class ExoPlayerTest { /** diff --git a/library/core/src/test/java/com/google/android/exoplayer2/FormatTest.java b/library/core/src/test/java/com/google/android/exoplayer2/FormatTest.java index 33e1a673bd..eb51485a36 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/FormatTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/FormatTest.java @@ -39,13 +39,11 @@ import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link Format}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class FormatTest { private static final List INIT_DATA; diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/TimelineTest.java b/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java similarity index 83% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/TimelineTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java index f5c33843a1..b5457555ab 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/TimelineTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/TimelineTest.java @@ -18,23 +18,26 @@ package com.google.android.exoplayer2; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.TimelineAsserts; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link Timeline}. - */ -public class TimelineTest extends TestCase { +/** Unit test for {@link Timeline}. */ +@RunWith(RobolectricTestRunner.class) +public class TimelineTest { + @Test public void testEmptyTimeline() { TimelineAsserts.assertEmpty(Timeline.EMPTY); } + @Test public void testSinglePeriodTimeline() { Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(1, 111)); TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertPeriodCounts(timeline, 1); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET); @@ -42,12 +45,13 @@ public class TimelineTest extends TestCase { TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0); } + @Test public void testMultiPeriodTimeline() { Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(5, 111)); TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertPeriodCounts(timeline, 5); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java index 2c10490e1f..9d5533e8ab 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java @@ -46,7 +46,6 @@ import org.robolectric.annotation.Config; * Unit test for {@link SimpleDecoderAudioRenderer}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class SimpleDecoderAudioRendererTest { private static final Format FORMAT = Format.createSampleFormat(null, MimeTypes.AUDIO_RAW, 0); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java index a4f02f8257..d060ba3f16 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java @@ -23,13 +23,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link SonicAudioProcessor}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class SonicAudioProcessorTest { private SonicAudioProcessor sonicAudioProcessor; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/ClearKeyUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/ClearKeyUtilTest.java index 01ab9ea9aa..c84ca6182c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/ClearKeyUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/ClearKeyUtilTest.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.C; import java.nio.charset.Charset; -import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -32,7 +31,7 @@ import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) public final class ClearKeyUtilTest { - @Config(sdk = 26, manifest = Config.NONE) + @Config(sdk = 26) @Test public void testAdjustResponseDataV26() { byte[] data = ("{\"keys\":[{" @@ -47,10 +46,10 @@ public final class ClearKeyUtilTest { + "\"kid\":\"ab\\/cde+f\"}]," + "\"type\":\"abc_def-" + "\"}").getBytes(Charset.forName(C.UTF8_NAME)); - assertThat(Arrays.equals(expected, ClearKeyUtil.adjustResponseData(data))).isTrue(); + assertThat(ClearKeyUtil.adjustResponseData(data)).isEqualTo(expected); } - @Config(sdk = 26, manifest = Config.NONE) + @Config(sdk = 26) @Test public void testAdjustRequestDataV26() { byte[] data = "{\"kids\":[\"abc+def/\",\"ab+cde/f\"],\"type\":\"abc+def/\"}" @@ -58,7 +57,7 @@ public final class ClearKeyUtilTest { // We expect "+" and "/" to be replaced with "-" and "_" respectively, for "kids". byte[] expected = "{\"kids\":[\"abc-def_\",\"ab-cde_f\"],\"type\":\"abc+def/\"}" .getBytes(Charset.forName(C.UTF8_NAME)); - assertThat(Arrays.equals(expected, ClearKeyUtil.adjustRequestData(data))).isTrue(); + assertThat(ClearKeyUtil.adjustRequestData(data)).isEqualTo(expected); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java index 9fc6e801d3..2b3bdd6a2f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java @@ -30,13 +30,11 @@ import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link DrmInitData}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class DrmInitDataTest { private static final SchemeData DATA_1 = new SchemeData(WIDEVINE_UUID, VIDEO_MP4, diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java similarity index 74% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java index 8dde1ed828..f67301f017 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java @@ -16,42 +16,48 @@ package com.google.android.exoplayer2.drm; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Mockito.when; -import android.test.InstrumentationTestCase; import android.util.Pair; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; -import com.google.android.exoplayer2.testutil.MockitoUtil; import java.util.HashMap; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; -/** - * Tests {@link OfflineLicenseHelper}. - */ -public class OfflineLicenseHelperTest extends InstrumentationTestCase { +/** Tests {@link OfflineLicenseHelper}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class}) +public class OfflineLicenseHelperTest { private OfflineLicenseHelper offlineLicenseHelper; @Mock private MediaDrmCallback mediaDrmCallback; @Mock private ExoMediaDrm mediaDrm; - @Override - protected void setUp() throws Exception { - super.setUp(); - MockitoUtil.setUpMockito(this); + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); when(mediaDrm.openSession()).thenReturn(new byte[] {1, 2, 3}); - offlineLicenseHelper = new OfflineLicenseHelper<>(C.WIDEVINE_UUID, mediaDrm, mediaDrmCallback, - null); + offlineLicenseHelper = + new OfflineLicenseHelper<>(C.WIDEVINE_UUID, mediaDrm, mediaDrmCallback, null); } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { offlineLicenseHelper.release(); offlineLicenseHelper = null; - super.tearDown(); } + @Test public void testDownloadRenewReleaseKey() throws Exception { setStubLicenseAndPlaybackDurationValues(1000, 200); @@ -72,6 +78,7 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { offlineLicenseHelper.releaseLicense(offlineLicenseKeySetId2); } + @Test public void testDownloadLicenseFailsIfNullInitData() throws Exception { try { offlineLicenseHelper.downloadLicense(null); @@ -81,6 +88,7 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { } } + @Test public void testDownloadLicenseFailsIfNoKeySetIdIsReturned() throws Exception { setStubLicenseAndPlaybackDurationValues(1000, 200); @@ -89,6 +97,7 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { assertThat(offlineLicenseKeySetId).isNull(); } + @Test public void testDownloadLicenseDoesNotFailIfDurationNotAvailable() throws Exception { setDefaultStubKeySetId(); @@ -97,6 +106,7 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { assertThat(offlineLicenseKeySetId).isNotNull(); } + @Test public void testGetLicenseDurationRemainingSec() throws Exception { long licenseDuration = 1000; int playbackDuration = 200; @@ -105,13 +115,14 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); - Pair licenseDurationRemainingSec = offlineLicenseHelper - .getLicenseDurationRemainingSec(offlineLicenseKeySetId); + Pair licenseDurationRemainingSec = + offlineLicenseHelper.getLicenseDurationRemainingSec(offlineLicenseKeySetId); assertThat(licenseDurationRemainingSec.first).isEqualTo(licenseDuration); assertThat(licenseDurationRemainingSec.second).isEqualTo(playbackDuration); } + @Test public void testGetLicenseDurationRemainingSecExpiredLicense() throws Exception { long licenseDuration = 0; int playbackDuration = 0; @@ -120,8 +131,8 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); - Pair licenseDurationRemainingSec = offlineLicenseHelper - .getLicenseDurationRemainingSec(offlineLicenseKeySetId); + Pair licenseDurationRemainingSec = + offlineLicenseHelper.getLicenseDurationRemainingSec(offlineLicenseKeySetId); assertThat(licenseDurationRemainingSec.first).isEqualTo(licenseDuration); assertThat(licenseDurationRemainingSec.second).isEqualTo(playbackDuration); @@ -143,19 +154,18 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { assertThat(actualKeySetId).isEqualTo(expectedKeySetId); } - private void setStubLicenseAndPlaybackDurationValues(long licenseDuration, - long playbackDuration) { + private void setStubLicenseAndPlaybackDurationValues( + long licenseDuration, long playbackDuration) { HashMap keyStatus = new HashMap<>(); - keyStatus.put(WidevineUtil.PROPERTY_LICENSE_DURATION_REMAINING, - String.valueOf(licenseDuration)); - keyStatus.put(WidevineUtil.PROPERTY_PLAYBACK_DURATION_REMAINING, - String.valueOf(playbackDuration)); + keyStatus.put( + WidevineUtil.PROPERTY_LICENSE_DURATION_REMAINING, String.valueOf(licenseDuration)); + keyStatus.put( + WidevineUtil.PROPERTY_PLAYBACK_DURATION_REMAINING, String.valueOf(playbackDuration)); when(mediaDrm.queryKeyStatus(any(byte[].class))).thenReturn(keyStatus); } private static DrmInitData newDrmInitData() { - return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType", - new byte[] {1, 4, 7, 0, 3, 6})); + return new DrmInitData( + new SchemeData(C.WIDEVINE_UUID, "mimeType", new byte[] {1, 4, 7, 0, 3, 6})); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java index 8e27c4f7ca..a96dfaf2f8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java @@ -31,13 +31,11 @@ import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Test for {@link DefaultExtractorInput}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class DefaultExtractorInputTest { private static final String TEST_URI = "http://www.google.com"; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ExtractorTest.java index fc31a7be73..3271e1ddf6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ExtractorTest.java @@ -21,13 +21,11 @@ import com.google.android.exoplayer2.C; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link Extractor}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class ExtractorTest { @Test diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java similarity index 67% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java index fc8d181eac..5a093988dd 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java @@ -15,23 +15,26 @@ */ package com.google.android.exoplayer2.extractor.flv; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link FlvExtractor}. - */ -public final class FlvExtractorTest extends InstrumentationTestCase { +/** Unit test for {@link FlvExtractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class FlvExtractorTest { + @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new FlvExtractor(); - } - }, "flv/sample.flv", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new FlvExtractor(); + } + }, + "flv/sample.flv"); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java index ee359ffa82..2fec5c7cab 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java @@ -27,13 +27,11 @@ import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Tests {@link DefaultEbmlReader}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class DefaultEbmlReaderTest { @Test diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java similarity index 53% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java index 624a5ccb7e..4a0f87a80a 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java @@ -15,41 +15,50 @@ */ package com.google.android.exoplayer2.extractor.mkv; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Tests for {@link MatroskaExtractor}. - */ -public final class MatroskaExtractorTest extends InstrumentationTestCase { +/** Tests for {@link MatroskaExtractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class MatroskaExtractorTest { + @Test public void testMkvSample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new MatroskaExtractor(); - } - }, "mkv/sample.mkv", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new MatroskaExtractor(); + } + }, + "mkv/sample.mkv"); } + @Test public void testWebmSubsampleEncryption() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new MatroskaExtractor(); - } - }, "mkv/subsample_encrypted_noaltref.webm", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new MatroskaExtractor(); + } + }, + "mkv/subsample_encrypted_noaltref.webm"); } + @Test public void testWebmSubsampleEncryptionWithAltrefFrames() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new MatroskaExtractor(); - } - }, "mkv/subsample_encrypted_altref.webm", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new MatroskaExtractor(); + } + }, + "mkv/subsample_encrypted_altref.webm"); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java index bda93db812..a13a185b3e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java @@ -29,13 +29,11 @@ import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Tests for {@link VarintReader}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class VarintReaderTest { private static final byte MAX_BYTE = (byte) 0xFF; diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java similarity index 59% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java index 0f98624d69..b977766a1c 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java @@ -15,32 +15,38 @@ */ package com.google.android.exoplayer2.extractor.mp3; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link Mp3Extractor}. - */ -public final class Mp3ExtractorTest extends InstrumentationTestCase { +/** Unit test for {@link Mp3Extractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class Mp3ExtractorTest { + @Test public void testMp3Sample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new Mp3Extractor(); - } - }, "mp3/bear.mp3", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new Mp3Extractor(); + } + }, + "mp3/bear.mp3"); } + @Test public void testTrimmedMp3Sample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new Mp3Extractor(); - } - }, "mp3/play-trimmed.mp3", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new Mp3Extractor(); + } + }, + "mp3/play-trimmed.mp3"); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java index 46cd7a2451..6df40b5dcc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java @@ -27,13 +27,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Tests for {@link XingSeeker}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class XingSeekerTest { // Xing header/payload from http://storage.googleapis.com/exoplayer-test-media-0/play.mp3. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java index b0c37ee452..9c7f0e8acc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java @@ -22,13 +22,11 @@ import com.google.android.exoplayer2.util.Util; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Tests for {@link AtomParsers}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class AtomParsersTest { private static final String ATOM_HEADER = "000000000000000000000000"; diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java similarity index 71% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java index d24788f74a..f5b0f48592 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.extractor.mp4; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; @@ -23,23 +22,28 @@ import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import com.google.android.exoplayer2.util.MimeTypes; import java.util.Collections; import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link FragmentedMp4Extractor}. - */ -public final class FragmentedMp4ExtractorTest extends InstrumentationTestCase { +/** Unit test for {@link FragmentedMp4Extractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class FragmentedMp4ExtractorTest { + @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior(getExtractorFactory(Collections.emptyList()), - "mp4/sample_fragmented.mp4", getInstrumentation()); + ExtractorAsserts.assertBehavior( + getExtractorFactory(Collections.emptyList()), "mp4/sample_fragmented.mp4"); } + @Test public void testSampleWithSeiPayloadParsing() throws Exception { // Enabling the CEA-608 track enables SEI payload parsing. - ExtractorFactory extractorFactory = getExtractorFactory(Collections.singletonList( - Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, 0, null))); - ExtractorAsserts.assertBehavior(extractorFactory, "mp4/sample_fragmented_sei.mp4", - getInstrumentation()); + ExtractorFactory extractorFactory = + getExtractorFactory( + Collections.singletonList( + Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, 0, null))); + ExtractorAsserts.assertBehavior(extractorFactory, "mp4/sample_fragmented_sei.mp4"); } private static ExtractorFactory getExtractorFactory(final List closedCaptionFormats) { @@ -50,5 +54,4 @@ public final class FragmentedMp4ExtractorTest extends InstrumentationTestCase { } }; } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java similarity index 69% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java index 5e327e5502..f1812a69c4 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java @@ -16,24 +16,27 @@ package com.google.android.exoplayer2.extractor.mp4; import android.annotation.TargetApi; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Tests for {@link Mp4Extractor}. - */ +/** Tests for {@link Mp4Extractor}. */ @TargetApi(16) -public final class Mp4ExtractorTest extends InstrumentationTestCase { +@RunWith(RobolectricTestRunner.class) +public final class Mp4ExtractorTest { + @Test public void testMp4Sample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new Mp4Extractor(); - } - }, "mp4/sample.mp4", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new Mp4Extractor(); + } + }, + "mp4/sample.mp4"); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtilTest.java index 4d7931cc02..d7a13ab061 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtilTest.java @@ -27,13 +27,11 @@ import java.util.UUID; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Tests for {@link PsshAtomUtil}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class PsshAtomUtilTest { @Test diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java similarity index 84% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java index d0f666d72a..993bb86b48 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java @@ -17,19 +17,22 @@ package com.google.android.exoplayer2.extractor.ogg; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; import java.util.Random; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link DefaultOggSeeker}. - */ -public final class DefaultOggSeekerTest extends TestCase { +/** Unit test for {@link DefaultOggSeeker}. */ +@RunWith(RobolectricTestRunner.class) +public final class DefaultOggSeekerTest { + @Test public void testSetupWithUnsetEndPositionFails() { try { new DefaultOggSeeker(0, C.LENGTH_UNSET, new TestStreamReader(), 1, 1); @@ -39,6 +42,7 @@ public final class DefaultOggSeekerTest extends TestCase { } } + @Test public void testSeeking() throws IOException, InterruptedException { Random random = new Random(0); for (int i = 0; i < 100; i++) { @@ -50,8 +54,13 @@ public final class DefaultOggSeekerTest extends TestCase { OggTestFile testFile = OggTestFile.generate(random, 1000); FakeExtractorInput input = new FakeExtractorInput.Builder().setData(testFile.data).build(); TestStreamReader streamReader = new TestStreamReader(); - DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, testFile.data.length, streamReader, - testFile.firstPayloadPageSize, testFile.firstPayloadPageGranulePosition); + DefaultOggSeeker oggSeeker = + new DefaultOggSeeker( + 0, + testFile.data.length, + streamReader, + testFile.firstPayloadPageSize, + testFile.firstPayloadPageGranulePosition); OggPageHeader pageHeader = new OggPageHeader(); while (true) { @@ -119,14 +128,19 @@ public final class DefaultOggSeekerTest extends TestCase { long granuleDiff = currentGranule - targetGranule; if ((granuleDiff > DefaultOggSeeker.MATCH_RANGE || granuleDiff < 0) && positionDiff > DefaultOggSeeker.MATCH_BYTE_RANGE) { - fail("granuleDiff (" + granuleDiff + ") or positionDiff (" + positionDiff - + ") is more than allowed."); + fail( + "granuleDiff (" + + granuleDiff + + ") or positionDiff (" + + positionDiff + + ") is more than allowed."); } } } - private long seekTo(FakeExtractorInput input, DefaultOggSeeker oggSeeker, long targetGranule, - int initialPosition) throws IOException, InterruptedException { + private long seekTo( + FakeExtractorInput input, DefaultOggSeeker oggSeeker, long targetGranule, int initialPosition) + throws IOException, InterruptedException { long nextSeekPosition = initialPosition; int count = 0; oggSeeker.resetSeeking(); @@ -150,8 +164,8 @@ public final class DefaultOggSeekerTest extends TestCase { } @Override - protected boolean readHeaders(ParsableByteArray packet, long position, - SetupData setupData) throws IOException, InterruptedException { + protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) + throws IOException, InterruptedException { return false; } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java index a3f7e9a548..be771ac3b9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java @@ -28,13 +28,11 @@ import java.util.Random; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link DefaultOggSeeker} utility methods. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class DefaultOggSeekerUtilMethodsTest { private final Random random = new Random(0); diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java similarity index 79% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java index fdab480167..20808f73f2 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.extractor.ogg; import static com.google.common.truth.Truth.assertThat; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; @@ -25,38 +24,43 @@ import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.OggTestData; import com.google.android.exoplayer2.testutil.TestUtil; import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link OggExtractor}. - */ -public final class OggExtractorTest extends InstrumentationTestCase { +/** Unit test for {@link OggExtractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class OggExtractorTest { - private static final ExtractorFactory OGG_EXTRACTOR_FACTORY = new ExtractorFactory() { - @Override - public Extractor create() { - return new OggExtractor(); - } - }; + private static final ExtractorFactory OGG_EXTRACTOR_FACTORY = + new ExtractorFactory() { + @Override + public Extractor create() { + return new OggExtractor(); + } + }; + @Test public void testOpus() throws Exception { - ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear.opus", getInstrumentation()); + ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear.opus"); } + @Test public void testFlac() throws Exception { - ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac.ogg", - getInstrumentation()); + ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac.ogg"); } + @Test public void testFlacNoSeektable() throws Exception { - ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac_noseektable.ogg", - getInstrumentation()); + ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac_noseektable.ogg"); } + @Test public void testVorbis() throws Exception { - ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear_vorbis.ogg", - getInstrumentation()); + ExtractorAsserts.assertBehavior(OGG_EXTRACTOR_FACTORY, "ogg/bear_vorbis.ogg"); } + @Test public void testSniffVorbis() throws Exception { byte[] data = TestUtil.joinByteArrays( @@ -66,6 +70,7 @@ public final class OggExtractorTest extends InstrumentationTestCase { assertThat(sniff(data)).isTrue(); } + @Test public void testSniffFlac() throws Exception { byte[] data = TestUtil.joinByteArrays( @@ -75,6 +80,7 @@ public final class OggExtractorTest extends InstrumentationTestCase { assertThat(sniff(data)).isTrue(); } + @Test public void testSniffFailsOpusFile() throws Exception { byte[] data = TestUtil.joinByteArrays( @@ -82,11 +88,13 @@ public final class OggExtractorTest extends InstrumentationTestCase { assertThat(sniff(data)).isFalse(); } + @Test public void testSniffFailsInvalidOggHeader() throws Exception { byte[] data = OggTestData.buildOggHeader(0x00, 0, 1000, 0x00); assertThat(sniff(data)).isFalse(); } + @Test public void testSniffInvalidHeader() throws Exception { byte[] data = TestUtil.joinByteArrays( @@ -96,16 +104,20 @@ public final class OggExtractorTest extends InstrumentationTestCase { assertThat(sniff(data)).isFalse(); } + @Test public void testSniffFailsEOF() throws Exception { byte[] data = OggTestData.buildOggHeader(0x02, 0, 1000, 0x00); assertThat(sniff(data)).isFalse(); } private boolean sniff(byte[] data) throws InterruptedException, IOException { - FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data) - .setSimulateIOErrors(true).setSimulateUnknownLength(true).setSimulatePartialReads(true) - .build(); + FakeExtractorInput input = + new FakeExtractorInput.Builder() + .setData(data) + .setSimulateIOErrors(true) + .setSimulateUnknownLength(true) + .setSimulatePartialReads(true) + .build(); return TestUtil.sniffTestData(OGG_EXTRACTOR_FACTORY.create(), input); } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java similarity index 68% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java index ca511adb7e..e9af630f83 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.extractor.ogg; import static com.google.common.truth.Truth.assertThat; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.OggTestData; import com.google.android.exoplayer2.testutil.TestUtil; @@ -25,24 +24,28 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; import java.util.Arrays; import java.util.Random; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit test for {@link OggPacket}. - */ -public final class OggPacketTest extends InstrumentationTestCase { +/** Unit test for {@link OggPacket}. */ +@RunWith(RobolectricTestRunner.class) +public final class OggPacketTest { private static final String TEST_FILE = "ogg/bear.opus"; private Random random; private OggPacket oggPacket; - @Override + @Before public void setUp() throws Exception { - super.setUp(); random = new Random(0); oggPacket = new OggPacket(); } + @Test public void testReadPacketsWithEmptyPage() throws Exception { byte[] firstPacket = TestUtil.buildTestData(8, random); byte[] secondPacket = TestUtil.buildTestData(272, random); @@ -107,35 +110,41 @@ public final class OggPacketTest extends InstrumentationTestCase { assertReadEof(input); } + @Test public void testReadPacketWithZeroSizeTerminator() throws Exception { byte[] firstPacket = TestUtil.buildTestData(255, random); byte[] secondPacket = TestUtil.buildTestData(8, random); - FakeExtractorInput input = OggTestData.createInput( - TestUtil.joinByteArrays( - OggTestData.buildOggHeader(0x06, 0, 1000, 0x04), - TestUtil.createByteArray(0xFF, 0x00, 0x00, 0x08), // Laces. - firstPacket, - secondPacket), true); + FakeExtractorInput input = + OggTestData.createInput( + TestUtil.joinByteArrays( + OggTestData.buildOggHeader(0x06, 0, 1000, 0x04), + TestUtil.createByteArray(0xFF, 0x00, 0x00, 0x08), // Laces. + firstPacket, + secondPacket), + true); assertReadPacket(input, firstPacket); assertReadPacket(input, secondPacket); assertReadEof(input); } + @Test public void testReadContinuedPacketOverTwoPages() throws Exception { byte[] firstPacket = TestUtil.buildTestData(518); - FakeExtractorInput input = OggTestData.createInput( - TestUtil.joinByteArrays( - // First page. - OggTestData.buildOggHeader(0x02, 0, 1000, 0x02), - TestUtil.createByteArray(0xFF, 0xFF), // Laces. - Arrays.copyOf(firstPacket, 510), - // Second page (continued packet). - OggTestData.buildOggHeader(0x05, 10, 1001, 0x01), - TestUtil.createByteArray(0x08), // Laces. - Arrays.copyOfRange(firstPacket, 510, 510 + 8)), true); + FakeExtractorInput input = + OggTestData.createInput( + TestUtil.joinByteArrays( + // First page. + OggTestData.buildOggHeader(0x02, 0, 1000, 0x02), + TestUtil.createByteArray(0xFF, 0xFF), // Laces. + Arrays.copyOf(firstPacket, 510), + // Second page (continued packet). + OggTestData.buildOggHeader(0x05, 10, 1001, 0x01), + TestUtil.createByteArray(0x08), // Laces. + Arrays.copyOfRange(firstPacket, 510, 510 + 8)), + true); assertReadPacket(input, firstPacket); assertThat((oggPacket.getPageHeader().type & 0x04) == 0x04).isTrue(); @@ -145,27 +154,30 @@ public final class OggPacketTest extends InstrumentationTestCase { assertReadEof(input); } + @Test public void testReadContinuedPacketOverFourPages() throws Exception { byte[] firstPacket = TestUtil.buildTestData(1028); - FakeExtractorInput input = OggTestData.createInput( - TestUtil.joinByteArrays( - // First page. - OggTestData.buildOggHeader(0x02, 0, 1000, 0x02), - TestUtil.createByteArray(0xFF, 0xFF), // Laces. - Arrays.copyOf(firstPacket, 510), - // Second page (continued packet). - OggTestData.buildOggHeader(0x01, 10, 1001, 0x01), - TestUtil.createByteArray(0xFF), // Laces. - Arrays.copyOfRange(firstPacket, 510, 510 + 255), - // Third page (continued packet). - OggTestData.buildOggHeader(0x01, 10, 1002, 0x01), - TestUtil.createByteArray(0xFF), // Laces. - Arrays.copyOfRange(firstPacket, 510 + 255, 510 + 255 + 255), - // Fourth page (continued packet). - OggTestData.buildOggHeader(0x05, 10, 1003, 0x01), - TestUtil.createByteArray(0x08), // Laces. - Arrays.copyOfRange(firstPacket, 510 + 255 + 255, 510 + 255 + 255 + 8)), true); + FakeExtractorInput input = + OggTestData.createInput( + TestUtil.joinByteArrays( + // First page. + OggTestData.buildOggHeader(0x02, 0, 1000, 0x02), + TestUtil.createByteArray(0xFF, 0xFF), // Laces. + Arrays.copyOf(firstPacket, 510), + // Second page (continued packet). + OggTestData.buildOggHeader(0x01, 10, 1001, 0x01), + TestUtil.createByteArray(0xFF), // Laces. + Arrays.copyOfRange(firstPacket, 510, 510 + 255), + // Third page (continued packet). + OggTestData.buildOggHeader(0x01, 10, 1002, 0x01), + TestUtil.createByteArray(0xFF), // Laces. + Arrays.copyOfRange(firstPacket, 510 + 255, 510 + 255 + 255), + // Fourth page (continued packet). + OggTestData.buildOggHeader(0x05, 10, 1003, 0x01), + TestUtil.createByteArray(0x08), // Laces. + Arrays.copyOfRange(firstPacket, 510 + 255 + 255, 510 + 255 + 255 + 8)), + true); assertReadPacket(input, firstPacket); assertThat((oggPacket.getPageHeader().type & 0x04) == 0x04).isTrue(); @@ -175,37 +187,43 @@ public final class OggPacketTest extends InstrumentationTestCase { assertReadEof(input); } + @Test public void testReadDiscardContinuedPacketAtStart() throws Exception { byte[] pageBody = TestUtil.buildTestData(256 + 8); - FakeExtractorInput input = OggTestData.createInput( - TestUtil.joinByteArrays( - // Page with a continued packet at start. - OggTestData.buildOggHeader(0x01, 10, 1001, 0x03), - TestUtil.createByteArray(255, 1, 8), // Laces. - pageBody), true); + FakeExtractorInput input = + OggTestData.createInput( + TestUtil.joinByteArrays( + // Page with a continued packet at start. + OggTestData.buildOggHeader(0x01, 10, 1001, 0x03), + TestUtil.createByteArray(255, 1, 8), // Laces. + pageBody), + true); // Expect the first partial packet to be discarded. assertReadPacket(input, Arrays.copyOfRange(pageBody, 256, 256 + 8)); assertReadEof(input); } + @Test public void testReadZeroSizedPacketsAtEndOfStream() throws Exception { byte[] firstPacket = TestUtil.buildTestData(8, random); byte[] secondPacket = TestUtil.buildTestData(8, random); byte[] thirdPacket = TestUtil.buildTestData(8, random); - FakeExtractorInput input = OggTestData.createInput( - TestUtil.joinByteArrays( - OggTestData.buildOggHeader(0x02, 0, 1000, 0x01), - TestUtil.createByteArray(0x08), // Laces. - firstPacket, - OggTestData.buildOggHeader(0x04, 0, 1001, 0x03), - TestUtil.createByteArray(0x08, 0x00, 0x00), // Laces. - secondPacket, - OggTestData.buildOggHeader(0x04, 0, 1002, 0x03), - TestUtil.createByteArray(0x08, 0x00, 0x00), // Laces. - thirdPacket), true); + FakeExtractorInput input = + OggTestData.createInput( + TestUtil.joinByteArrays( + OggTestData.buildOggHeader(0x02, 0, 1000, 0x01), + TestUtil.createByteArray(0x08), // Laces. + firstPacket, + OggTestData.buildOggHeader(0x04, 0, 1001, 0x03), + TestUtil.createByteArray(0x08, 0x00, 0x00), // Laces. + secondPacket, + OggTestData.buildOggHeader(0x04, 0, 1002, 0x03), + TestUtil.createByteArray(0x08, 0x00, 0x00), // Laces. + thirdPacket), + true); assertReadPacket(input, firstPacket); assertReadPacket(input, secondPacket); @@ -213,9 +231,9 @@ public final class OggPacketTest extends InstrumentationTestCase { assertReadEof(input); } - + @Test public void testParseRealFile() throws IOException, InterruptedException { - byte[] data = TestUtil.getByteArray(getInstrumentation(), TEST_FILE); + byte[] data = TestUtil.getByteArray(RuntimeEnvironment.application, TEST_FILE); FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build(); int packetCounter = 0; while (readPacket(input)) { @@ -236,8 +254,7 @@ public final class OggPacketTest extends InstrumentationTestCase { assertThat(readPacket(extractorInput)).isFalse(); } - private boolean readPacket(FakeExtractorInput input) - throws InterruptedException, IOException { + private boolean readPacket(FakeExtractorInput input) throws InterruptedException, IOException { while (true) { try { return oggPacket.populate(input); @@ -246,5 +263,4 @@ public final class OggPacketTest extends InstrumentationTestCase { } } } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java index c8bcffde3c..930d067d2b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java @@ -25,13 +25,11 @@ import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link OggPageHeader}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class OggPageHeaderTest { @Test diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java similarity index 91% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java index 4803c28a54..e5512dda36 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java @@ -22,9 +22,7 @@ import com.google.android.exoplayer2.testutil.TestUtil; import java.util.ArrayList; import java.util.Random; -/** - * Generates test data. - */ +/** Generates test data. */ /* package */ final class OggTestFile { private static final int MAX_PACKET_LENGTH = 2048; @@ -38,8 +36,13 @@ import java.util.Random; public final int firstPayloadPageSize; public final long firstPayloadPageGranulePosition; - private OggTestFile(byte[] data, long lastGranule, int packetCount, int pageCount, - int firstPayloadPageSize, long firstPayloadPageGranulePosition) { + private OggTestFile( + byte[] data, + long lastGranule, + int packetCount, + int pageCount, + int firstPayloadPageSize, + long firstPayloadPageGranulePosition) { this.data = data; this.lastGranule = lastGranule; this.packetCount = packetCount; @@ -110,7 +113,12 @@ import java.util.Random; System.arraycopy(data, 0, file, position, data.length); position += data.length; } - return new OggTestFile(file, granule, packetCount, pageCount, firstPayloadPageSize, + return new OggTestFile( + file, + granule, + packetCount, + pageCount, + firstPayloadPageSize, firstPayloadPageGranulePosition); } @@ -123,5 +131,4 @@ import java.util.Random; fail(); return -1; } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java index 08b9b12a18..eca94f076e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java @@ -21,13 +21,11 @@ import com.google.android.exoplayer2.testutil.TestUtil; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link VorbisBitArray}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class VorbisBitArrayTest { @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java index 20a76e83e0..f0361c5395 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java @@ -29,13 +29,11 @@ import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link VorbisReader}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class VorbisReaderTest { @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtilTest.java index bdc573f218..5b395771fc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtilTest.java @@ -26,13 +26,11 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link VorbisUtil}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class VorbisUtilTest { @Test diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java similarity index 69% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java index 18050f48a3..9632577e82 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java @@ -16,29 +16,38 @@ package com.google.android.exoplayer2.extractor.rawcc; import android.annotation.TargetApi; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; import com.google.android.exoplayer2.util.MimeTypes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Tests for {@link RawCcExtractor}. - */ +/** Tests for {@link RawCcExtractor}. */ @TargetApi(16) -public final class RawCcExtractorTest extends InstrumentationTestCase { +@RunWith(RobolectricTestRunner.class) +public final class RawCcExtractorTest { + @Test public void testRawCcSample() throws Exception { ExtractorAsserts.assertBehavior( new ExtractorFactory() { @Override public Extractor create() { return new RawCcExtractor( - Format.createTextContainerFormat(null, null, MimeTypes.APPLICATION_CEA608, - "cea608", Format.NO_VALUE, 0, null, 1)); + Format.createTextContainerFormat( + null, + null, + MimeTypes.APPLICATION_CEA608, + "cea608", + Format.NO_VALUE, + 0, + null, + 1)); } - }, "rawcc/sample.rawcc", getInstrumentation()); + }, + "rawcc/sample.rawcc"); } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java similarity index 67% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java index 31633361db..ec7afeeeab 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java @@ -15,23 +15,26 @@ */ package com.google.android.exoplayer2.extractor.ts; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link Ac3Extractor}. - */ -public final class Ac3ExtractorTest extends InstrumentationTestCase { +/** Unit test for {@link Ac3Extractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class Ac3ExtractorTest { + @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new Ac3Extractor(); - } - }, "ts/sample.ac3", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new Ac3Extractor(); + } + }, + "ts/sample.ac3"); } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java similarity index 67% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java index 9eb65d2091..048a23cd67 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java @@ -15,23 +15,26 @@ */ package com.google.android.exoplayer2.extractor.ts; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link AdtsExtractor}. - */ -public final class AdtsExtractorTest extends InstrumentationTestCase { +/** Unit test for {@link AdtsExtractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class AdtsExtractorTest { + @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new AdtsExtractor(); - } - }, "ts/sample.adts", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new AdtsExtractor(); + } + }, + "ts/sample.adts"); } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java similarity index 69% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java index 1a10d24c94..1098ba7563 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java @@ -23,41 +23,39 @@ import com.google.android.exoplayer2.testutil.FakeTrackOutput; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import java.util.Arrays; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Test for {@link AdtsReader}. - */ -public class AdtsReaderTest extends TestCase { +/** Test for {@link AdtsReader}. */ +@RunWith(RobolectricTestRunner.class) +public class AdtsReaderTest { - public static final byte[] ID3_DATA_1 = TestUtil.createByteArray( - 0x49, 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x54, 0x58, - 0x58, 0x58, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x03, 0x00, 0x20, 0x2a, - 0x2a, 0x2a, 0x20, 0x54, 0x48, 0x49, 0x53, 0x20, 0x49, 0x53, 0x20, 0x54, - 0x69, 0x6d, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, - 0x61, 0x20, 0x40, 0x20, 0x2d, 0x2d, 0x20, 0x30, 0x30, 0x3a, 0x30, 0x30, - 0x3a, 0x30, 0x30, 0x2e, 0x30, 0x20, 0x2a, 0x2a, 0x2a, 0x20, 0x00); + public static final byte[] ID3_DATA_1 = + TestUtil.createByteArray( + 0x49, 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x54, 0x58, 0x58, 0x58, 0x00, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x03, 0x00, 0x20, 0x2a, 0x2a, 0x2a, 0x20, 0x54, 0x48, 0x49, + 0x53, 0x20, 0x49, 0x53, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x74, 0x61, + 0x44, 0x61, 0x74, 0x61, 0x20, 0x40, 0x20, 0x2d, 0x2d, 0x20, 0x30, 0x30, 0x3a, 0x30, 0x30, + 0x3a, 0x30, 0x30, 0x2e, 0x30, 0x20, 0x2a, 0x2a, 0x2a, 0x20, 0x00); - public static final byte[] ID3_DATA_2 = TestUtil.createByteArray( - 0x49, - 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x50, 0x52, 0x49, - 0x56, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, - 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, - 0x6e, 0x67, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0xbb, 0xa0); + public static final byte[] ID3_DATA_2 = + TestUtil.createByteArray( + 0x49, 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x50, 0x52, 0x49, 0x56, 0x00, + 0x00, 0x00, 0x35, 0x00, 0x00, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x70, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0xbb, 0xa0); - public static final byte[] ADTS_HEADER = TestUtil.createByteArray( - 0xff, 0xf1, 0x50, 0x80, 0x01, 0xdf, 0xfc); + public static final byte[] ADTS_HEADER = + TestUtil.createByteArray(0xff, 0xf1, 0x50, 0x80, 0x01, 0xdf, 0xfc); - public static final byte[] ADTS_CONTENT = TestUtil.createByteArray( - 0x20, 0x00, 0x20, 0x00, 0x00, 0x80, 0x0e); + public static final byte[] ADTS_CONTENT = + TestUtil.createByteArray(0x20, 0x00, 0x20, 0x00, 0x00, 0x80, 0x0e); - private static final byte[] TEST_DATA = TestUtil.joinByteArrays( - ID3_DATA_1, - ID3_DATA_2, - ADTS_HEADER, - ADTS_CONTENT); + private static final byte[] TEST_DATA = + TestUtil.joinByteArrays(ID3_DATA_1, ID3_DATA_2, ADTS_HEADER, ADTS_CONTENT); private static final long ADTS_SAMPLE_DURATION = 23219L; @@ -67,9 +65,8 @@ public class AdtsReaderTest extends TestCase { private ParsableByteArray data; private boolean firstFeed; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput(); adtsOutput = fakeExtractorOutput.track(0, C.TRACK_TYPE_AUDIO); id3Output = fakeExtractorOutput.track(1, C.TRACK_TYPE_METADATA); @@ -80,6 +77,7 @@ public class AdtsReaderTest extends TestCase { firstFeed = true; } + @Test public void testSkipToNextSample() throws Exception { for (int i = 1; i <= ID3_DATA_1.length + ID3_DATA_2.length; i++) { data.setPosition(i); @@ -90,50 +88,60 @@ public class AdtsReaderTest extends TestCase { } } + @Test public void testSkipToNextSampleResetsState() throws Exception { - data = new ParsableByteArray(TestUtil.joinByteArrays( - ADTS_HEADER, - ADTS_CONTENT, - // Adts sample missing the first sync byte - Arrays.copyOfRange(ADTS_HEADER, 1, ADTS_HEADER.length), - ADTS_CONTENT)); + data = + new ParsableByteArray( + TestUtil.joinByteArrays( + ADTS_HEADER, + ADTS_CONTENT, + // Adts sample missing the first sync byte + Arrays.copyOfRange(ADTS_HEADER, 1, ADTS_HEADER.length), + ADTS_CONTENT)); feed(); assertSampleCounts(0, 1); adtsOutput.assertSample(0, ADTS_CONTENT, 0, C.BUFFER_FLAG_KEY_FRAME, null); } + @Test public void testNoData() throws Exception { feedLimited(0); assertSampleCounts(0, 0); } + @Test public void testNotEnoughDataForIdentifier() throws Exception { feedLimited(3 - 1); assertSampleCounts(0, 0); } + @Test public void testNotEnoughDataForHeader() throws Exception { feedLimited(10 - 1); assertSampleCounts(0, 0); } + @Test public void testNotEnoughDataForWholeId3Packet() throws Exception { feedLimited(ID3_DATA_1.length - 1); assertSampleCounts(0, 0); } + @Test public void testConsumeWholeId3Packet() throws Exception { feedLimited(ID3_DATA_1.length); assertSampleCounts(1, 0); id3Output.assertSample(0, ID3_DATA_1, 0, C.BUFFER_FLAG_KEY_FRAME, null); } + @Test public void testMultiId3Packet() throws Exception { feedLimited(ID3_DATA_1.length + ID3_DATA_2.length - 1); assertSampleCounts(1, 0); id3Output.assertSample(0, ID3_DATA_1, 0, C.BUFFER_FLAG_KEY_FRAME, null); } + @Test public void testMultiId3PacketConsumed() throws Exception { feedLimited(ID3_DATA_1.length + ID3_DATA_2.length); assertSampleCounts(2, 0); @@ -141,6 +149,7 @@ public class AdtsReaderTest extends TestCase { id3Output.assertSample(1, ID3_DATA_2, 0, C.BUFFER_FLAG_KEY_FRAME, null); } + @Test public void testMultiPacketConsumed() throws Exception { for (int i = 0; i < 10; i++) { data.setPosition(0); @@ -156,6 +165,7 @@ public class AdtsReaderTest extends TestCase { } } + @Test public void testAdtsDataOnly() throws ParserException { data.setPosition(ID3_DATA_1.length + ID3_DATA_2.length); feed(); @@ -185,6 +195,4 @@ public class AdtsReaderTest extends TestCase { id3Output.assertSampleCount(id3SampleCount); adtsOutput.assertSampleCount(adtsSampleCount); } - } - diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java similarity index 67% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java index 78ef05a769..798f1ce5e3 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java @@ -15,23 +15,26 @@ */ package com.google.android.exoplayer2.extractor.ts; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link PsExtractor}. - */ -public final class PsExtractorTest extends InstrumentationTestCase { +/** Unit test for {@link PsExtractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class PsExtractorTest { + @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new PsExtractor(); - } - }, "ts/sample.ps", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new PsExtractor(); + } + }, + "ts/sample.ps"); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java index 56668d5124..713d986d21 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java @@ -30,13 +30,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Test for {@link SectionReader}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class SectionReaderTest { private byte[] packetPayload; diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java similarity index 79% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java index 1ea08df772..8394ed81a5 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.extractor.ts; import static com.google.common.truth.Truth.assertThat; -import android.test.InstrumentationTestCase; import android.util.SparseArray; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -37,27 +36,34 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import java.io.ByteArrayOutputStream; import java.util.Random; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit test for {@link TsExtractor}. - */ -public final class TsExtractorTest extends InstrumentationTestCase { +/** Unit test for {@link TsExtractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class TsExtractorTest { private static final int TS_PACKET_SIZE = 188; private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. + @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new TsExtractor(); - } - }, "ts/sample.ts", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new TsExtractor(); + } + }, + "ts/sample.ts"); } + @Test public void testIncompleteSample() throws Exception { Random random = new Random(0); - byte[] fileData = TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"); + byte[] fileData = TestUtil.getByteArray(RuntimeEnvironment.application, "ts/sample.ts"); ByteArrayOutputStream out = new ByteArrayOutputStream(fileData.length * 2); writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1); out.write(fileData, 0, TS_PACKET_SIZE * 5); @@ -69,23 +75,30 @@ public final class TsExtractorTest extends InstrumentationTestCase { writeJunkData(out, random.nextInt(TS_PACKET_SIZE - 1) + 1); fileData = out.toByteArray(); - ExtractorAsserts.assertOutput(new ExtractorFactory() { - @Override - public Extractor create() { - return new TsExtractor(); - } - }, "ts/sample.ts", fileData, getInstrumentation()); + ExtractorAsserts.assertOutput( + new ExtractorFactory() { + @Override + public Extractor create() { + return new TsExtractor(); + } + }, + "ts/sample.ts", + fileData, + RuntimeEnvironment.application); } + @Test public void testCustomPesReader() throws Exception { CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false); - TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), - factory); - FakeExtractorInput input = new FakeExtractorInput.Builder() - .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts")) - .setSimulateIOErrors(false) - .setSimulateUnknownLength(false) - .setSimulatePartialReads(false).build(); + TsExtractor tsExtractor = + new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory); + FakeExtractorInput input = + new FakeExtractorInput.Builder() + .setData(TestUtil.getByteArray(RuntimeEnvironment.application, "ts/sample.ts")) + .setSimulateIOErrors(false) + .setSimulateUnknownLength(false) + .setSimulatePartialReads(false) + .build(); FakeExtractorOutput output = new FakeExtractorOutput(); tsExtractor.init(output); PositionHolder seekPositionHolder = new PositionHolder(); @@ -101,15 +114,18 @@ public final class TsExtractorTest extends InstrumentationTestCase { .isEqualTo(Format.createTextSampleFormat("1/257", "mime", null, 0, 0, "und", null, 0)); } + @Test public void testCustomInitialSectionReader() throws Exception { CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true); - TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), - factory); - FakeExtractorInput input = new FakeExtractorInput.Builder() - .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample_with_sdt.ts")) - .setSimulateIOErrors(false) - .setSimulateUnknownLength(false) - .setSimulatePartialReads(false).build(); + TsExtractor tsExtractor = + new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0), factory); + FakeExtractorInput input = + new FakeExtractorInput.Builder() + .setData(TestUtil.getByteArray(RuntimeEnvironment.application, "ts/sample_with_sdt.ts")) + .setSimulateIOErrors(false) + .setSimulateUnknownLength(false) + .setSimulatePartialReads(false) + .build(); tsExtractor.init(new FakeExtractorOutput()); PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; @@ -165,7 +181,6 @@ public final class TsExtractorTest extends InstrumentationTestCase { return defaultFactory.createPayloadReader(streamType, esInfo); } } - } private static final class CustomEsReader implements ElementaryStreamReader { @@ -179,24 +194,22 @@ public final class TsExtractorTest extends InstrumentationTestCase { } @Override - public void seek() { - } + public void seek() {} @Override public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { idGenerator.generateNewId(); output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_UNKNOWN); - output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), "mime", null, 0, 0, - language, null, 0)); + output.format( + Format.createTextSampleFormat( + idGenerator.getFormatId(), "mime", null, 0, 0, language, null, 0)); } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { - } + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {} @Override - public void consume(ParsableByteArray data) { - } + public void consume(ParsableByteArray data) {} @Override public void packetFinished() { @@ -206,7 +219,6 @@ public final class TsExtractorTest extends InstrumentationTestCase { public TrackOutput getTrackOutput() { return output; } - } private static final class SdtSectionReader implements SectionPayloadReader { @@ -214,7 +226,9 @@ public final class TsExtractorTest extends InstrumentationTestCase { private int consumedSdts; @Override - public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + public void init( + TimestampAdjuster timestampAdjuster, + ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { // Do nothing. } @@ -248,7 +262,5 @@ public final class TsExtractorTest extends InstrumentationTestCase { } consumedSdts++; } - } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java similarity index 67% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java index 36c05aa72e..e75525bb1e 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java @@ -15,23 +15,26 @@ */ package com.google.android.exoplayer2.extractor.wav; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; -/** - * Unit test for {@link WavExtractor}. - */ -public final class WavExtractorTest extends InstrumentationTestCase { +/** Unit test for {@link WavExtractor}. */ +@RunWith(RobolectricTestRunner.class) +public final class WavExtractorTest { + @Test public void testSample() throws Exception { - ExtractorAsserts.assertBehavior(new ExtractorFactory() { - @Override - public Extractor create() { - return new WavExtractor(); - } - }, "wav/sample.wav", getInstrumentation()); + ExtractorAsserts.assertBehavior( + new ExtractorFactory() { + @Override + public Extractor create() { + return new WavExtractor(); + } + }, + "wav/sample.wav"); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java index 3a6e96b3e8..c6558e3fc9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java @@ -23,13 +23,11 @@ import java.nio.ByteBuffer; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Test for {@link EventMessageDecoder}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class EventMessageDecoderTest { @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java index f0a6d3e19b..7195548fbf 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java @@ -24,13 +24,11 @@ import java.nio.ByteBuffer; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link EventMessageEncoder}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class EventMessageEncoderTest { @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java index 58f2b9f55d..30e1cd6c1f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java @@ -21,13 +21,11 @@ import android.os.Parcel; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Test for {@link EventMessage}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class EventMessageTest { @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java index a42b71731a..714f77a752 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java @@ -21,13 +21,11 @@ import android.os.Parcel; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Test for {@link ChapterFrame}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class ChapterFrameTest { @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java index 9636b04e51..98a99a4219 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java @@ -21,13 +21,11 @@ import android.os.Parcel; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Test for {@link ChapterTocFrame}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class ChapterTocFrameTest { @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java index 06ce330146..4e7ae0eec0 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java @@ -25,13 +25,11 @@ import java.nio.charset.Charset; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Test for {@link Id3Decoder}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class Id3DecoderTest { private static final byte[] TAG_HEADER = new byte[] {73, 68, 51, 4, 0, 0, 0, 0, 0, 0}; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java index 8cd90c7a64..2afe80bb0a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java @@ -28,13 +28,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Test for {@link SpliceInfoDecoder}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class SpliceInfoDecoderTest { private SpliceInfoDecoder decoder; diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java similarity index 77% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java index 4d5b94e88a..1e0d8681c5 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java @@ -16,10 +16,11 @@ package com.google.android.exoplayer2.source; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Window; @@ -30,11 +31,16 @@ import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinit import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; import com.google.android.exoplayer2.testutil.TimelineAsserts; import java.io.IOException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; -/** - * Unit tests for {@link ClippingMediaSource}. - */ -public final class ClippingMediaSourceTest extends InstrumentationTestCase { +/** Unit tests for {@link ClippingMediaSource}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class}) +public final class ClippingMediaSourceTest { private static final long TEST_PERIOD_DURATION_US = 1000000; private static final long TEST_CLIP_AMOUNT_US = 300000; @@ -42,13 +48,13 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { private Window window; private Period period; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { window = new Timeline.Window(); period = new Timeline.Period(); } + @Test public void testNoClipping() throws IOException { Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false); @@ -62,6 +68,7 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { .isEqualTo(TEST_PERIOD_DURATION_US); } + @Test public void testClippingUnseekableWindowThrows() throws IOException { Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), false, false); @@ -76,67 +83,76 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { } } + @Test public void testClippingStart() throws IOException { Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false); - Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, - TEST_PERIOD_DURATION_US); + Timeline clippedTimeline = + getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, TEST_PERIOD_DURATION_US); assertThat(clippedTimeline.getWindow(0, window).getDurationUs()) .isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); assertThat(clippedTimeline.getPeriod(0, period).getDurationUs()) .isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); } + @Test public void testClippingEnd() throws IOException { Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false); - Timeline clippedTimeline = getClippedTimeline(timeline, 0, - TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); + Timeline clippedTimeline = + getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); assertThat(clippedTimeline.getWindow(0, window).getDurationUs()) .isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); assertThat(clippedTimeline.getPeriod(0, period).getDurationUs()) .isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); } + @Test public void testClippingStartAndEndInitial() throws IOException { // Timeline that's dynamic and not seekable. A child source might report such a timeline prior // to it having loaded sufficient data to establish its duration and seekability. Such timelines // should not result in clipping failure. - Timeline timeline = new SinglePeriodTimeline(C.TIME_UNSET, /* isSeekable= */ false, - /* isDynamic= */true); + Timeline timeline = + new SinglePeriodTimeline(C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ true); - Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, - TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US * 2); + Timeline clippedTimeline = + getClippedTimeline( + timeline, TEST_CLIP_AMOUNT_US, TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US * 2); assertThat(clippedTimeline.getWindow(0, window).getDurationUs()) .isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US * 3); assertThat(clippedTimeline.getPeriod(0, period).getDurationUs()) .isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US * 3); } + @Test public void testClippingStartAndEnd() throws IOException { Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false); - Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, - TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US * 2); + Timeline clippedTimeline = + getClippedTimeline( + timeline, TEST_CLIP_AMOUNT_US, TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US * 2); assertThat(clippedTimeline.getWindow(0, window).getDurationUs()) .isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US * 3); assertThat(clippedTimeline.getPeriod(0, period).getDurationUs()) .isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US * 3); } + @Test public void testWindowAndPeriodIndices() throws IOException { - Timeline timeline = new FakeTimeline( - new TimelineWindowDefinition(1, 111, true, false, TEST_PERIOD_DURATION_US)); - Timeline clippedTimeline = getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, - TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition(1, 111, true, false, TEST_PERIOD_DURATION_US)); + Timeline clippedTimeline = + getClippedTimeline( + timeline, TEST_CLIP_AMOUNT_US, TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); TimelineAsserts.assertWindowIds(clippedTimeline, 111); TimelineAsserts.assertPeriodCounts(clippedTimeline, 1); - TimelineAsserts.assertPreviousWindowIndices(clippedTimeline, Player.REPEAT_MODE_OFF, false, - C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + clippedTimeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET); TimelineAsserts.assertPreviousWindowIndices(clippedTimeline, Player.REPEAT_MODE_ONE, false, 0); TimelineAsserts.assertPreviousWindowIndices(clippedTimeline, Player.REPEAT_MODE_ALL, false, 0); - TimelineAsserts.assertNextWindowIndices(clippedTimeline, Player.REPEAT_MODE_OFF, false, - C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + clippedTimeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(clippedTimeline, Player.REPEAT_MODE_ONE, false, 0); TimelineAsserts.assertNextWindowIndices(clippedTimeline, Player.REPEAT_MODE_ALL, false, 0); } @@ -158,5 +174,4 @@ public final class ClippingMediaSourceTest extends InstrumentationTestCase { testRunner.release(); } } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderTest.java index f7e29d2b06..82d7f21852 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/CompositeSequenceableLoaderTest.java @@ -21,13 +21,11 @@ import com.google.android.exoplayer2.C; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link CompositeSequenceableLoader}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class CompositeSequenceableLoaderTest { /** diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java similarity index 70% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index 5b3b684ebb..d7cf8db4bc 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.testutil.FakeMediaSource; @@ -28,13 +29,17 @@ import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinit import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; import com.google.android.exoplayer2.testutil.TimelineAsserts; import java.io.IOException; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; -/** - * Unit tests for {@link ConcatenatingMediaSource}. - */ -public final class ConcatenatingMediaSourceTest extends TestCase { +/** Unit tests for {@link ConcatenatingMediaSource}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class}) +public final class ConcatenatingMediaSourceTest { + @Test public void testEmptyConcatenation() throws IOException { for (boolean atomic : new boolean[] {false, true}) { Timeline timeline = getConcatenatedTimeline(atomic); @@ -48,17 +53,18 @@ public final class ConcatenatingMediaSourceTest extends TestCase { } } + @Test public void testSingleMediaSource() throws IOException { Timeline timeline = getConcatenatedTimeline(false, createFakeTimeline(3, 111)); TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertPeriodCounts(timeline, 3); for (boolean shuffled : new boolean[] {false, true}) { - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0); } @@ -67,17 +73,18 @@ public final class ConcatenatingMediaSourceTest extends TestCase { TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertPeriodCounts(timeline, 3); for (boolean shuffled : new boolean[] {false, true}) { - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 0); } } + @Test public void testMultipleMediaSources() throws IOException { Timeline[] timelines = { createFakeTimeline(3, 111), createFakeTimeline(1, 222), createFakeTimeline(3, 333) @@ -85,20 +92,20 @@ public final class ConcatenatingMediaSourceTest extends TestCase { Timeline timeline = getConcatenatedTimeline(false, timelines); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 3, 1, 3); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET, 0, 1); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 2, 0, 1); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 0); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 1, 2, 0); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, C.INDEX_UNSET, 0, 1); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 1); assertThat(timeline.getFirstWindowIndex(false)).isEqualTo(0); @@ -110,14 +117,14 @@ public final class ConcatenatingMediaSourceTest extends TestCase { TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 3, 1, 3); for (boolean shuffled : new boolean[] {false, true}) { - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - C.INDEX_UNSET, 0, 1); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, - 2, 0, 1); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, - 2, 0, 1); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 2, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 2, 0, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 1, 2, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0); assertThat(timeline.getFirstWindowIndex(shuffled)).isEqualTo(0); @@ -125,34 +132,36 @@ public final class ConcatenatingMediaSourceTest extends TestCase { } } + @Test public void testNestedMediaSources() throws IOException { - Timeline timeline = getConcatenatedTimeline(false, - getConcatenatedTimeline(false, createFakeTimeline(1, 111), createFakeTimeline(1, 222)), - getConcatenatedTimeline(true, createFakeTimeline(1, 333), createFakeTimeline(1, 444))); + Timeline timeline = + getConcatenatedTimeline( + false, + getConcatenatedTimeline(false, createFakeTimeline(1, 111), createFakeTimeline(1, 222)), + getConcatenatedTimeline(true, createFakeTimeline(1, 333), createFakeTimeline(1, 444))); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 444); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - C.INDEX_UNSET, 0, 1, 2); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, - 0, 1, 3, 2); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, - 3, 0, 1, 2); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - 1, 2, 3, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET, 0, 1, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 3, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, false, 3, 0, 1, 2); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, 1, 2, 3, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 3, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 3, 0); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - 1, 3, C.INDEX_UNSET, 2); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, - 0, 1, 3, 2); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, - 1, 3, 0, 2); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - C.INDEX_UNSET, 0, 3, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, 1, 3, C.INDEX_UNSET, 2); + TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 3, 2); + TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 1, 3, 0, 2); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, C.INDEX_UNSET, 0, 3, 1); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 3, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 3, 1); } + @Test public void testEmptyTimelineMediaSources() throws IOException { // Empty timelines in the front, back, and the middle (single and multiple in a row). Timeline[] timelines = { @@ -168,20 +177,20 @@ public final class ConcatenatingMediaSourceTest extends TestCase { Timeline timeline = getConcatenatedTimeline(false, timelines); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET, 0, 1); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 2, 0, 1); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 0); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 1, 2, 0); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, C.INDEX_UNSET, 0, 1); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 1); assertThat(timeline.getFirstWindowIndex(false)).isEqualTo(0); @@ -193,14 +202,14 @@ public final class ConcatenatingMediaSourceTest extends TestCase { TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3); for (boolean shuffled : new boolean[] {false, true}) { - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - C.INDEX_UNSET, 0, 1); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, - 2, 0, 1); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, - 2, 0, 1); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 2, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 2, 0, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 1, 2, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0); assertThat(timeline.getFirstWindowIndex(shuffled)).isEqualTo(0); @@ -208,10 +217,12 @@ public final class ConcatenatingMediaSourceTest extends TestCase { } } + @Test public void testPeriodCreationWithAds() throws IOException, InterruptedException { // Create media source with ad child source. - Timeline timelineContentOnly = new FakeTimeline( - new TimelineWindowDefinition(2, 111, true, false, 10 * C.MICROS_PER_SECOND)); + Timeline timelineContentOnly = + new FakeTimeline( + new TimelineWindowDefinition(2, 111, true, false, 10 * C.MICROS_PER_SECOND)); Timeline timelineWithAds = new FakeTimeline( new TimelineWindowDefinition( @@ -224,8 +235,8 @@ public final class ConcatenatingMediaSourceTest extends TestCase { /* adsPerAdGroup= */ 1, /* adGroupTimesUs= */ 0))); FakeMediaSource mediaSourceContentOnly = new FakeMediaSource(timelineContentOnly, null); FakeMediaSource mediaSourceWithAds = new FakeMediaSource(timelineWithAds, null); - ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(mediaSourceContentOnly, - mediaSourceWithAds); + ConcatenatingMediaSource mediaSource = + new ConcatenatingMediaSource(mediaSourceContentOnly, mediaSourceWithAds); MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null); try { @@ -246,17 +257,18 @@ public final class ConcatenatingMediaSourceTest extends TestCase { } /** - * Wraps the specified timelines in a {@link ConcatenatingMediaSource} and returns - * the concatenated timeline. + * Wraps the specified timelines in a {@link ConcatenatingMediaSource} and returns the + * concatenated timeline. */ - private static Timeline getConcatenatedTimeline(boolean isRepeatOneAtomic, - Timeline... timelines) throws IOException { + private static Timeline getConcatenatedTimeline(boolean isRepeatOneAtomic, Timeline... timelines) + throws IOException { FakeMediaSource[] mediaSources = new FakeMediaSource[timelines.length]; for (int i = 0; i < timelines.length; i++) { mediaSources[i] = new FakeMediaSource(timelines[i], null); } - ConcatenatingMediaSource mediaSource = new ConcatenatingMediaSource(isRepeatOneAtomic, - new FakeShuffleOrder(mediaSources.length), mediaSources); + ConcatenatingMediaSource mediaSource = + new ConcatenatingMediaSource( + isRepeatOneAtomic, new FakeShuffleOrder(mediaSources.length), mediaSources); MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null); try { Timeline timeline = testRunner.prepareSource(); @@ -273,5 +285,4 @@ public final class ConcatenatingMediaSourceTest extends TestCase { private static FakeTimeline createFakeTimeline(int periodCount, int windowId) { return new FakeTimeline(new TimelineWindowDefinition(periodCount, windowId)); } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java similarity index 86% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java index 38ac324e69..a0847bf9ff 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.source; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Mockito.verify; import android.os.ConditionVariable; @@ -23,6 +24,7 @@ import android.os.Handler; import android.os.HandlerThread; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.testutil.FakeMediaSource; @@ -33,32 +35,37 @@ import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; import com.google.android.exoplayer2.testutil.TimelineAsserts; import java.io.IOException; import java.util.Arrays; -import junit.framework.TestCase; +import java.util.concurrent.CountDownLatch; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mockito; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; -/** - * Unit tests for {@link DynamicConcatenatingMediaSource} - */ -public final class DynamicConcatenatingMediaSourceTest extends TestCase { +/** Unit tests for {@link DynamicConcatenatingMediaSource} */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class}) +public final class DynamicConcatenatingMediaSourceTest { private DynamicConcatenatingMediaSource mediaSource; private MediaSourceTestRunner testRunner; - @Override + @Before public void setUp() throws Exception { - super.setUp(); mediaSource = new DynamicConcatenatingMediaSource(/* isAtomic= */ false, new FakeShuffleOrder(0)); testRunner = new MediaSourceTestRunner(mediaSource, null); } - @Override + @After public void tearDown() throws Exception { - super.tearDown(); testRunner.release(); } - public void testPlaylistChangesAfterPreparation() throws IOException { + @Test + public void testPlaylistChangesAfterPreparation() throws IOException, InterruptedException { Timeline timeline = testRunner.prepareSource(); TimelineAsserts.assertEmpty(timeline); @@ -89,8 +96,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 333); // Add bulk. - mediaSource.addMediaSources(3, Arrays.asList(childSources[4], childSources[5], - childSources[6])); + mediaSource.addMediaSources( + 3, Arrays.asList(childSources[4], childSources[5], childSources[6])); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333); @@ -129,22 +136,22 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } // Assert correct next and previous indices behavior after some insertions and removals. - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 0); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET, 0, 1); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 2, 0, 1); assertThat(timeline.getFirstWindowIndex(false)).isEqualTo(0); assertThat(timeline.getLastWindowIndex(false)).isEqualTo(timeline.getWindowCount() - 1); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, C.INDEX_UNSET, 0, 1); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 1); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 1, 2, 0); assertThat(timeline.getFirstWindowIndex(true)).isEqualTo(timeline.getWindowCount() - 1); @@ -174,7 +181,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { childSources[3].assertReleased(); } - public void testPlaylistChangesBeforePreparation() throws IOException { + @Test + public void testPlaylistChangesBeforePreparation() throws IOException, InterruptedException { FakeMediaSource[] childSources = createMediaSources(4); mediaSource.addMediaSource(childSources[0]); mediaSource.addMediaSource(childSources[1]); @@ -188,14 +196,14 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { Timeline timeline = testRunner.prepareSource(); TimelineAsserts.assertPeriodCounts(timeline, 3, 4, 2); TimelineAsserts.assertWindowIds(timeline, 333, 444, 222); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - 1, 2, C.INDEX_UNSET); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - C.INDEX_UNSET, 0, 1); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - C.INDEX_UNSET, 0, 1); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, 1, 2, C.INDEX_UNSET); testRunner.assertPrepareAndReleaseAllPeriods(); mediaSource.releaseSource(); @@ -204,7 +212,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } - public void testPlaylistWithLazyMediaSource() throws IOException { + @Test + public void testPlaylistWithLazyMediaSource() throws IOException, InterruptedException { // Create some normal (immediately preparing) sources and some lazy sources whose timeline // updates need to be triggered. FakeMediaSource[] fastSources = createMediaSources(2); @@ -230,12 +239,13 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { // Trigger source info refresh for lazy source and check that the timeline now contains all // information for all windows. - testRunner.runOnPlaybackThread(new Runnable() { - @Override - public void run() { - lazySources[1].setNewSourceInfo(createFakeTimeline(8), null); - } - }); + testRunner.runOnPlaybackThread( + new Runnable() { + @Override + public void run() { + lazySources[1].setNewSourceInfo(createFakeTimeline(8), null); + } + }); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 1, 9); TimelineAsserts.assertWindowIds(timeline, 111, 999); @@ -259,8 +269,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { // Create a period from an unprepared lazy media source and assert Callback.onPrepared is not // called yet. MediaPeriod lazyPeriod = testRunner.createPeriod(new MediaPeriodId(0)); - ConditionVariable preparedCondition = testRunner.preparePeriod(lazyPeriod, 0); - assertThat(preparedCondition.block(1)).isFalse(); + CountDownLatch preparedCondition = testRunner.preparePeriod(lazyPeriod, 0); + assertThat(preparedCondition.getCount()).isEqualTo(1); // Assert that a second period can also be created and released without problems. MediaPeriod secondLazyPeriod = testRunner.createPeriod(new MediaPeriodId(0)); @@ -268,17 +278,18 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { // Trigger source info refresh for lazy media source. Assert that now all information is // available again and the previously created period now also finished preparing. - testRunner.runOnPlaybackThread(new Runnable() { - @Override - public void run() { - lazySources[3].setNewSourceInfo(createFakeTimeline(7), null); - } - }); + testRunner.runOnPlaybackThread( + new Runnable() { + @Override + public void run() { + lazySources[3].setNewSourceInfo(createFakeTimeline(7), null); + } + }); timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertPeriodCounts(timeline, 8, 1, 2, 9); TimelineAsserts.assertWindowIds(timeline, 888, 111, 222, 999); TimelineAsserts.assertWindowIsDynamic(timeline, false, false, false, false); - assertThat(preparedCondition.block(1)).isTrue(); + assertThat(preparedCondition.getCount()).isEqualTo(0); // Release the period and source. testRunner.releasePeriod(lazyPeriod); @@ -293,7 +304,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } - public void testEmptyTimelineMediaSource() throws IOException { + @Test + public void testEmptyTimelineMediaSource() throws IOException, InterruptedException { Timeline timeline = testRunner.prepareSource(); TimelineAsserts.assertEmpty(timeline); @@ -322,20 +334,20 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { timeline = testRunner.assertTimelineChangeBlocking(); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET, 0, 1); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 2, 0, 1); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, false, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, false, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, false, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, false, 1, 2, 0); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2); TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 1, 2, 0); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, true, - C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, true, C.INDEX_UNSET, 0, 1); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, true, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, true, 2, 0, 1); assertThat(timeline.getFirstWindowIndex(false)).isEqualTo(0); @@ -345,6 +357,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { testRunner.assertPrepareAndReleaseAllPeriods(); } + @Test public void testDynamicChangeOfEmptyTimelines() throws IOException { FakeMediaSource[] childSources = new FakeMediaSource[] { @@ -371,6 +384,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); } + @Test public void testIllegalArguments() { MediaSource validSource = new FakeMediaSource(createFakeTimeline(1), null); @@ -382,7 +396,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { // Expected. } - MediaSource[] mediaSources = { validSource, null }; + MediaSource[] mediaSources = {validSource, null}; try { mediaSource.addMediaSources(Arrays.asList(mediaSources)); fail("Null mediaSource not allowed."); @@ -399,8 +413,8 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { // Expected. } - mediaSources = new MediaSource[] { - new FakeMediaSource(createFakeTimeline(2), null), validSource }; + mediaSources = + new MediaSource[] {new FakeMediaSource(createFakeTimeline(2), null), validSource}; try { mediaSource.addMediaSources(Arrays.asList(mediaSources)); fail("Duplicate mediaSource not allowed."); @@ -409,6 +423,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } + @Test public void testCustomCallbackBeforePreparationAddSingle() { Runnable runnable = Mockito.mock(Runnable.class); @@ -416,14 +431,17 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { verify(runnable).run(); } + @Test public void testCustomCallbackBeforePreparationAddMultiple() { Runnable runnable = Mockito.mock(Runnable.class); - mediaSource.addMediaSources(Arrays.asList( - new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}), runnable); + mediaSource.addMediaSources( + Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}), + runnable); verify(runnable).run(); } + @Test public void testCustomCallbackBeforePreparationAddSingleWithIndex() { Runnable runnable = Mockito.mock(Runnable.class); @@ -431,15 +449,18 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { verify(runnable).run(); } + @Test public void testCustomCallbackBeforePreparationAddMultipleWithIndex() { Runnable runnable = Mockito.mock(Runnable.class); - mediaSource.addMediaSources(/* index */ 0, + mediaSource.addMediaSources( + /* index */ 0, Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}), runnable); verify(runnable).run(); } + @Test public void testCustomCallbackBeforePreparationRemove() { Runnable runnable = Mockito.mock(Runnable.class); @@ -448,6 +469,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { verify(runnable).run(); } + @Test public void testCustomCallbackBeforePreparationMove() { Runnable runnable = Mockito.mock(Runnable.class); @@ -457,17 +479,19 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { verify(runnable).run(); } + @Test public void testCustomCallbackAfterPreparationAddSingle() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { testRunner.prepareSource(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); - dummyMainThread.runOnMainThread(new Runnable() { - @Override - public void run() { - mediaSource.addMediaSource(createFakeMediaSource(), timelineGrabber); - } - }); + dummyMainThread.runOnMainThread( + new Runnable() { + @Override + public void run() { + mediaSource.addMediaSource(createFakeMediaSource(), timelineGrabber); + } + }); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.getWindowCount()).isEqualTo(1); } finally { @@ -475,6 +499,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } + @Test public void testCustomCallbackAfterPreparationAddMultiple() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { @@ -497,17 +522,19 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } + @Test public void testCustomCallbackAfterPreparationAddSingleWithIndex() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { testRunner.prepareSource(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); - dummyMainThread.runOnMainThread(new Runnable() { - @Override - public void run() { - mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(), timelineGrabber); - } - }); + dummyMainThread.runOnMainThread( + new Runnable() { + @Override + public void run() { + mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(), timelineGrabber); + } + }); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.getWindowCount()).isEqualTo(1); } finally { @@ -515,6 +542,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } + @Test public void testCustomCallbackAfterPreparationAddMultipleWithIndex() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { @@ -538,25 +566,28 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } + @Test public void testCustomCallbackAfterPreparationRemove() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { testRunner.prepareSource(); - dummyMainThread.runOnMainThread(new Runnable() { - @Override - public void run() { - mediaSource.addMediaSource(createFakeMediaSource()); - } - }); + dummyMainThread.runOnMainThread( + new Runnable() { + @Override + public void run() { + mediaSource.addMediaSource(createFakeMediaSource()); + } + }); testRunner.assertTimelineChangeBlocking(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); - dummyMainThread.runOnMainThread(new Runnable() { - @Override - public void run() { - mediaSource.removeMediaSource(/* index */ 0, timelineGrabber); - } - }); + dummyMainThread.runOnMainThread( + new Runnable() { + @Override + public void run() { + mediaSource.removeMediaSource(/* index */ 0, timelineGrabber); + } + }); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.getWindowCount()).isEqualTo(0); } finally { @@ -564,6 +595,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } + @Test public void testCustomCallbackAfterPreparationMove() throws IOException { DummyMainThread dummyMainThread = new DummyMainThread(); try { @@ -580,13 +612,13 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { testRunner.assertTimelineChangeBlocking(); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); - dummyMainThread.runOnMainThread(new Runnable() { - @Override - public void run() { - mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0, - timelineGrabber); - } - }); + dummyMainThread.runOnMainThread( + new Runnable() { + @Override + public void run() { + mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0, timelineGrabber); + } + }); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); assertThat(timeline.getWindowCount()).isEqualTo(2); } finally { @@ -594,10 +626,12 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } } + @Test public void testPeriodCreationWithAds() throws IOException, InterruptedException { // Create dynamic media source with ad child source. - Timeline timelineContentOnly = new FakeTimeline( - new TimelineWindowDefinition(2, 111, true, false, 10 * C.MICROS_PER_SECOND)); + Timeline timelineContentOnly = + new FakeTimeline( + new TimelineWindowDefinition(2, 111, true, false, 10 * C.MICROS_PER_SECOND)); Timeline timelineWithAds = new FakeTimeline( new TimelineWindowDefinition( @@ -628,6 +662,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0)); } + @Test public void testAtomicTimelineWindowOrder() throws IOException { // Release default test runner with non-atomic media source and replace with new test runner. testRunner.release(); @@ -668,6 +703,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { assertThat(timeline.getLastWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(2); } + @Test public void testNestedTimeline() throws IOException { DynamicConcatenatingMediaSource nestedSource1 = new DynamicConcatenatingMediaSource(/* isAtomic= */ false, new FakeShuffleOrder(0)); @@ -714,6 +750,7 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { timeline, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true, 2, 0, 3, 1); } + @Test public void testRemoveChildSourceWithActiveMediaPeriod() throws IOException { FakeMediaSource childSource = createFakeMediaSource(); mediaSource.addMediaSource(childSource); @@ -760,20 +797,20 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { */ public void runOnMainThread(final Runnable runnable) { final ConditionVariable finishedCondition = new ConditionVariable(); - handler.post(new Runnable() { - @Override - public void run() { - runnable.run(); - finishedCondition.open(); - } - }); + handler.post( + new Runnable() { + @Override + public void run() { + runnable.run(); + finishedCondition.open(); + } + }); assertThat(finishedCondition.block(MediaSourceTestRunner.TIMEOUT_MS)).isTrue(); } public void release() { thread.quit(); } - } private static final class TimelineGrabber implements Runnable { @@ -806,7 +843,5 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase { } return timeline; } - } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java similarity index 61% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java index 68a9e7676c..f0b4772422 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/LoopingMediaSourceTest.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeTimeline; @@ -24,77 +25,87 @@ import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinit import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; import com.google.android.exoplayer2.testutil.TimelineAsserts; import java.io.IOException; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; -/** - * Unit tests for {@link LoopingMediaSource}. - */ -public class LoopingMediaSourceTest extends TestCase { +/** Unit tests for {@link LoopingMediaSource}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class}) +public class LoopingMediaSourceTest { private FakeTimeline multiWindowTimeline; - @Override + @Before public void setUp() throws Exception { - super.setUp(); - multiWindowTimeline = new FakeTimeline(new TimelineWindowDefinition(1, 111), - new TimelineWindowDefinition(1, 222), new TimelineWindowDefinition(1, 333)); + multiWindowTimeline = + new FakeTimeline( + new TimelineWindowDefinition(1, 111), + new TimelineWindowDefinition(1, 222), + new TimelineWindowDefinition(1, 333)); } + @Test public void testSingleLoop() throws IOException { Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); for (boolean shuffled : new boolean[] {false, true}) { - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - C.INDEX_UNSET, 0, 1); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, - 0, 1, 2); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, - 2, 0, 1); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - 1, 2, C.INDEX_UNSET); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 2, 0, 1); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, C.INDEX_UNSET); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0); } } + @Test public void testMultiLoop() throws IOException { Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1, 1); for (boolean shuffled : new boolean[] {false, true}) { - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - C.INDEX_UNSET, 0, 1, 2, 3, 4, 5, 6, 7, 8); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, - 0, 1, 2, 3, 4, 5, 6, 7, 8); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, - 8, 0, 1, 2, 3, 4, 5, 6, 7); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - 1, 2, 3, 4, 5, 6, 7, 8, C.INDEX_UNSET); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, - 0, 1, 2, 3, 4, 5, 6, 7, 8); - TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, - 1, 2, 3, 4, 5, 6, 7, 8, 0); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, C.INDEX_UNSET, 0, 1, 2, 3, 4, 5, 6, 7, 8); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2, 3, 4, 5, 6, 7, 8); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 8, 0, 1, 2, 3, 4, 5, 6, 7); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, 3, 4, 5, 6, 7, 8, C.INDEX_UNSET); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2, 3, 4, 5, 6, 7, 8); + TimelineAsserts.assertNextWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 3, 4, 5, 6, 7, 8, 0); } } + @Test public void testInfiniteLoop() throws IOException { Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE); TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); for (boolean shuffled : new boolean[] {false, true}) { - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, - 2, 0, 1); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, - 0, 1, 2); - TimelineAsserts.assertPreviousWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, - 2, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_OFF, shuffled, 2, 0, 1); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); + TimelineAsserts.assertPreviousWindowIndices( + timeline, Player.REPEAT_MODE_ALL, shuffled, 2, 0, 1); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_OFF, shuffled, 1, 2, 0); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ONE, shuffled, 0, 1, 2); TimelineAsserts.assertNextWindowIndices(timeline, Player.REPEAT_MODE_ALL, shuffled, 1, 2, 0); } } + @Test public void testEmptyTimelineLoop() throws IOException { Timeline timeline = getLoopingTimeline(Timeline.EMPTY, 1); TimelineAsserts.assertEmpty(timeline); @@ -107,8 +118,7 @@ public class LoopingMediaSourceTest extends TestCase { } /** - * Wraps the specified timeline in a {@link LoopingMediaSource} and returns - * the looping timeline. + * Wraps the specified timeline in a {@link LoopingMediaSource} and returns the looping timeline. */ private static Timeline getLoopingTimeline(Timeline timeline, int loopCount) throws IOException { FakeMediaSource fakeMediaSource = new FakeMediaSource(timeline, null); @@ -123,5 +133,4 @@ public class LoopingMediaSourceTest extends TestCase { testRunner.release(); } } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java similarity index 76% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java index 6c048db09d..b03a76c23e 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/MergingMediaSourceTest.java @@ -16,8 +16,10 @@ package com.google.android.exoplayer2.source; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MergingMediaSource.IllegalMergeException; import com.google.android.exoplayer2.testutil.FakeMediaSource; @@ -25,29 +27,33 @@ import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition; import com.google.android.exoplayer2.testutil.MediaSourceTestRunner; import java.io.IOException; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; -/** - * Unit tests for {@link MergingMediaSource}. - */ -public class MergingMediaSourceTest extends TestCase { +/** Unit tests for {@link MergingMediaSource}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class}) +public class MergingMediaSourceTest { + @Test public void testMergingDynamicTimelines() throws IOException { - FakeTimeline firstTimeline = new FakeTimeline( - new TimelineWindowDefinition(true, true, C.TIME_UNSET)); - FakeTimeline secondTimeline = new FakeTimeline( - new TimelineWindowDefinition(true, true, C.TIME_UNSET)); + FakeTimeline firstTimeline = + new FakeTimeline(new TimelineWindowDefinition(true, true, C.TIME_UNSET)); + FakeTimeline secondTimeline = + new FakeTimeline(new TimelineWindowDefinition(true, true, C.TIME_UNSET)); testMergingMediaSourcePrepare(firstTimeline, secondTimeline); } + @Test public void testMergingStaticTimelines() throws IOException { - FakeTimeline firstTimeline = new FakeTimeline( - new TimelineWindowDefinition(true, false, 20)); - FakeTimeline secondTimeline = new FakeTimeline( - new TimelineWindowDefinition(true, false, 10)); + FakeTimeline firstTimeline = new FakeTimeline(new TimelineWindowDefinition(true, false, 20)); + FakeTimeline secondTimeline = new FakeTimeline(new TimelineWindowDefinition(true, false, 10)); testMergingMediaSourcePrepare(firstTimeline, secondTimeline); } + @Test public void testMergingTimelinesWithDifferentPeriodCounts() throws IOException { FakeTimeline firstTimeline = new FakeTimeline(new TimelineWindowDefinition(1, null)); FakeTimeline secondTimeline = new FakeTimeline(new TimelineWindowDefinition(2, null)); @@ -82,5 +88,4 @@ public class MergingMediaSourceTest extends TestCase { testRunner.release(); } } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java index 6030238131..4d6b6dd72d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java @@ -36,13 +36,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Test for {@link SampleQueue}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class SampleQueueTest { private static final int ALLOCATION_SIZE = 16; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java index 1229e47883..e15c8f0aaa 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java @@ -24,13 +24,11 @@ import com.google.android.exoplayer2.source.ShuffleOrder.UnshuffledShuffleOrder; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link ShuffleOrder}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class ShuffleOrderTest { public static final long RANDOM_SEED = 1234567890L; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java index 94ca8b03f0..2627052cc5 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SinglePeriodTimelineTest.java @@ -25,13 +25,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link SinglePeriodTimeline}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class SinglePeriodTimelineTest { private Window window; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java index ca8bf5d393..a8cc04473d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java @@ -24,11 +24,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** Unit test for {@link AdPlaybackState}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class AdPlaybackStateTest { private static final long[] TEST_AD_GROUP_TMES_US = new long[] {0, C.msToUs(10_000)}; diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java similarity index 83% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java index 49d05e993f..746066cb35 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/ssa/SsaDecoderTest.java @@ -17,15 +17,17 @@ package com.google.android.exoplayer2.text.ssa; import static com.google.common.truth.Truth.assertThat; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.testutil.TestUtil; import java.io.IOException; import java.util.ArrayList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit test for {@link SsaDecoder}. - */ -public final class SsaDecoderTest extends InstrumentationTestCase { +/** Unit test for {@link SsaDecoder}. */ +@RunWith(RobolectricTestRunner.class) +public final class SsaDecoderTest { private static final String EMPTY = "ssa/empty"; private static final String TYPICAL = "ssa/typical"; @@ -35,18 +37,20 @@ public final class SsaDecoderTest extends InstrumentationTestCase { private static final String INVALID_TIMECODES = "ssa/invalid_timecodes"; private static final String NO_END_TIMECODES = "ssa/no_end_timecodes"; + @Test public void testDecodeEmpty() throws IOException { SsaDecoder decoder = new SsaDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, EMPTY); SsaSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(0); assertThat(subtitle.getCues(0).isEmpty()).isTrue(); } + @Test public void testDecodeTypical() throws IOException { SsaDecoder decoder = new SsaDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL); SsaSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -55,14 +59,15 @@ public final class SsaDecoderTest extends InstrumentationTestCase { assertTypicalCue3(subtitle, 4); } + @Test public void testDecodeTypicalWithInitializationData() throws IOException { - byte[] headerBytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_HEADER_ONLY); - byte[] formatBytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FORMAT_ONLY); + byte[] headerBytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_HEADER_ONLY); + byte[] formatBytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_FORMAT_ONLY); ArrayList initializationData = new ArrayList<>(); initializationData.add(formatBytes); initializationData.add(headerBytes); SsaDecoder decoder = new SsaDecoder(initializationData); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_DIALOGUE_ONLY); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_DIALOGUE_ONLY); SsaSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -71,19 +76,21 @@ public final class SsaDecoderTest extends InstrumentationTestCase { assertTypicalCue3(subtitle, 4); } + @Test public void testDecodeInvalidTimecodes() throws IOException { // Parsing should succeed, parsing the third cue only. SsaDecoder decoder = new SsaDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), INVALID_TIMECODES); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, INVALID_TIMECODES); SsaSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(2); assertTypicalCue3(subtitle, 0); } + @Test public void testDecodeNoEndTimecodes() throws IOException { SsaDecoder decoder = new SsaDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, NO_END_TIMECODES); SsaSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(3); @@ -121,5 +128,4 @@ public final class SsaDecoderTest extends InstrumentationTestCase { .isEqualTo("This is the third subtitle, with a comma."); assertThat(subtitle.getEventTime(eventIndex + 1)).isEqualTo(8900000); } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java similarity index 84% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java index c810e3590d..e9abaca075 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java @@ -17,14 +17,16 @@ package com.google.android.exoplayer2.text.subrip; import static com.google.common.truth.Truth.assertThat; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.testutil.TestUtil; import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit test for {@link SubripDecoder}. - */ -public final class SubripDecoderTest extends InstrumentationTestCase { +/** Unit test for {@link SubripDecoder}. */ +@RunWith(RobolectricTestRunner.class) +public final class SubripDecoderTest { private static final String EMPTY_FILE = "subrip/empty"; private static final String TYPICAL_FILE = "subrip/typical"; @@ -36,18 +38,20 @@ public final class SubripDecoderTest extends InstrumentationTestCase { private static final String TYPICAL_UNEXPECTED_END = "subrip/typical_unexpected_end"; private static final String NO_END_TIMECODES_FILE = "subrip/no_end_timecodes"; + @Test public void testDecodeEmpty() throws IOException { SubripDecoder decoder = new SubripDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, EMPTY_FILE); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(0); assertThat(subtitle.getCues(0).isEmpty()).isTrue(); } + @Test public void testDecodeTypical() throws IOException { SubripDecoder decoder = new SubripDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FILE); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_FILE); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -56,9 +60,11 @@ public final class SubripDecoderTest extends InstrumentationTestCase { assertTypicalCue3(subtitle, 4); } + @Test public void testDecodeTypicalWithByteOrderMark() throws IOException { SubripDecoder decoder = new SubripDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_BYTE_ORDER_MARK); + byte[] bytes = + TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_WITH_BYTE_ORDER_MARK); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -67,9 +73,10 @@ public final class SubripDecoderTest extends InstrumentationTestCase { assertTypicalCue3(subtitle, 4); } + @Test public void testDecodeTypicalExtraBlankLine() throws IOException { SubripDecoder decoder = new SubripDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_EXTRA_BLANK_LINE); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_EXTRA_BLANK_LINE); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -78,10 +85,11 @@ public final class SubripDecoderTest extends InstrumentationTestCase { assertTypicalCue3(subtitle, 4); } + @Test public void testDecodeTypicalMissingTimecode() throws IOException { // Parsing should succeed, parsing the first and third cues only. SubripDecoder decoder = new SubripDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_TIMECODE); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_MISSING_TIMECODE); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); @@ -89,10 +97,11 @@ public final class SubripDecoderTest extends InstrumentationTestCase { assertTypicalCue3(subtitle, 2); } + @Test public void testDecodeTypicalMissingSequence() throws IOException { // Parsing should succeed, parsing the first and third cues only. SubripDecoder decoder = new SubripDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_SEQUENCE); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_MISSING_SEQUENCE); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); @@ -100,20 +109,23 @@ public final class SubripDecoderTest extends InstrumentationTestCase { assertTypicalCue3(subtitle, 2); } + @Test public void testDecodeTypicalNegativeTimestamps() throws IOException { // Parsing should succeed, parsing the third cue only. SubripDecoder decoder = new SubripDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_NEGATIVE_TIMESTAMPS); + byte[] bytes = + TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_NEGATIVE_TIMESTAMPS); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(2); assertTypicalCue3(subtitle, 0); } + @Test public void testDecodeTypicalUnexpectedEnd() throws IOException { // Parsing should succeed, parsing the first and second cues only. SubripDecoder decoder = new SubripDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_UNEXPECTED_END); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_UNEXPECTED_END); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); @@ -121,9 +133,10 @@ public final class SubripDecoderTest extends InstrumentationTestCase { assertTypicalCue2(subtitle, 2); } + @Test public void testDecodeNoEndTimecodes() throws IOException { SubripDecoder decoder = new SubripDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, NO_END_TIMECODES_FILE); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getEventTimeCount()).isEqualTo(3); @@ -161,5 +174,4 @@ public final class SubripDecoderTest extends InstrumentationTestCase { .isEqualTo("This is the third subtitle."); assertThat(subtitle.getEventTime(eventIndex + 1)).isEqualTo(8901000); } - } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java similarity index 83% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java index 95f0dfe3c8..fdf454e5df 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.text.ttml; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import android.test.InstrumentationTestCase; import android.text.Layout; import android.text.Spannable; import android.text.SpannableStringBuilder; @@ -38,11 +37,14 @@ import com.google.android.exoplayer2.util.ColorParser; import java.io.IOException; import java.util.List; import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit test for {@link TtmlDecoder}. - */ -public final class TtmlDecoderTest extends InstrumentationTestCase { +/** Unit test for {@link TtmlDecoder}. */ +@RunWith(RobolectricTestRunner.class) +public final class TtmlDecoderTest { private static final String INLINE_ATTRIBUTES_TTML_FILE = "ttml/inline_style_attributes.xml"; private static final String INHERIT_STYLE_TTML_FILE = "ttml/inherit_style.xml"; @@ -62,6 +64,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { private static final String FONT_SIZE_EMPTY_TTML_FILE = "ttml/font_size_empty.xml"; private static final String FRAME_RATE_TTML_FILE = "ttml/frame_rate.xml"; + @Test public void testInlineAttributes() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); @@ -78,82 +81,182 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertThat(firstPStyle.isUnderline()).isTrue(); } + @Test public void testInheritInlineAttributes() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); - assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, - 0xFF00FFFF, ColorParser.parseTtmlColor("lime"), false, true, null); + assertSpans( + subtitle, + 20, + "text 2", + "sansSerif", + TtmlStyle.STYLE_ITALIC, + 0xFF00FFFF, + ColorParser.parseTtmlColor("lime"), + false, + true, + null); } /** - * Regression test for devices on JellyBean where some named colors are not correctly defined - * on framework level. Tests that lime resolves to #FF00FF00 not - * #00FF00. + * Regression test for devices on JellyBean where some named colors are not correctly defined on + * framework level. Tests that lime resolves to #FF00FF00 not #00FF00 + * . * - * @see - * JellyBean Color - * + * @see + * JellyBean Color * Kitkat Color * @throws IOException thrown if reading subtitle file fails. */ + @Test public void testLime() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); - assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, 0xFF00FFFF, 0xFF00FF00, - false, true, null); + assertSpans( + subtitle, + 20, + "text 2", + "sansSerif", + TtmlStyle.STYLE_ITALIC, + 0xFF00FFFF, + 0xFF00FF00, + false, + true, + null); } + @Test public void testInheritGlobalStyle() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(2); - assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF, - 0xFFFFFF00, true, false, null); + assertSpans( + subtitle, + 10, + "text 1", + "serif", + TtmlStyle.STYLE_BOLD_ITALIC, + 0xFF0000FF, + 0xFFFFFF00, + true, + false, + null); } - public void testInheritGlobalStyleOverriddenByInlineAttributes() throws IOException, - SubtitleDecoderException { + @Test + public void testInheritGlobalStyleOverriddenByInlineAttributes() + throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_OVERRIDE_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); - assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF, - 0xFFFFFF00, true, false, null); - assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, 0xFFFF0000, 0xFFFFFF00, - true, false, null); + assertSpans( + subtitle, + 10, + "text 1", + "serif", + TtmlStyle.STYLE_BOLD_ITALIC, + 0xFF0000FF, + 0xFFFFFF00, + true, + false, + null); + assertSpans( + subtitle, + 20, + "text 2", + "sansSerif", + TtmlStyle.STYLE_ITALIC, + 0xFFFF0000, + 0xFFFFFF00, + true, + false, + null); } + @Test public void testInheritGlobalAndParent() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INHERIT_GLOBAL_AND_PARENT_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); - assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_NORMAL, 0xFFFF0000, - ColorParser.parseTtmlColor("lime"), false, true, Layout.Alignment.ALIGN_CENTER); - assertSpans(subtitle, 20, "text 2", "serif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF, - 0xFFFFFF00, true, true, Layout.Alignment.ALIGN_CENTER); + assertSpans( + subtitle, + 10, + "text 1", + "sansSerif", + TtmlStyle.STYLE_NORMAL, + 0xFFFF0000, + ColorParser.parseTtmlColor("lime"), + false, + true, + Layout.Alignment.ALIGN_CENTER); + assertSpans( + subtitle, + 20, + "text 2", + "serif", + TtmlStyle.STYLE_BOLD_ITALIC, + 0xFF0000FF, + 0xFFFFFF00, + true, + true, + Layout.Alignment.ALIGN_CENTER); } + @Test public void testInheritMultipleStyles() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(12); - assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF, - 0xFFFFFF00, false, true, null); + assertSpans( + subtitle, + 10, + "text 1", + "sansSerif", + TtmlStyle.STYLE_BOLD_ITALIC, + 0xFF0000FF, + 0xFFFFFF00, + false, + true, + null); } - public void testInheritMultipleStylesWithoutLocalAttributes() throws IOException, - SubtitleDecoderException { + @Test + public void testInheritMultipleStylesWithoutLocalAttributes() + throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(12); - assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 0xFF0000FF, - 0xFF000000, false, true, null); + assertSpans( + subtitle, + 20, + "text 2", + "sansSerif", + TtmlStyle.STYLE_BOLD_ITALIC, + 0xFF0000FF, + 0xFF000000, + false, + true, + null); } - public void testMergeMultipleStylesWithParentStyle() throws IOException, - SubtitleDecoderException { + @Test + public void testMergeMultipleStylesWithParentStyle() + throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(12); - assertSpans(subtitle, 30, "text 2.5", "sansSerifInline", TtmlStyle.STYLE_ITALIC, 0xFFFF0000, - 0xFFFFFF00, true, true, null); + assertSpans( + subtitle, + 30, + "text 2.5", + "sansSerifInline", + TtmlStyle.STYLE_ITALIC, + 0xFFFF0000, + 0xFFFFFF00, + true, + true, + null); } + @Test public void testMultipleRegions() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(MULTIPLE_REGIONS_TTML_FILE); List output = subtitle.getCues(1000000); @@ -208,6 +311,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertThat(ttmlCue.line).isEqualTo(45f / 100f); } + @Test public void testEmptyStyleAttribute() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(12); @@ -219,6 +323,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertThat(queryChildrenForTag(fourthDiv, TtmlNode.TAG_P, 0).getStyleIds()).isNull(); } + @Test public void testNonexistingStyleId() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(12); @@ -230,8 +335,9 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertThat(queryChildrenForTag(fifthDiv, TtmlNode.TAG_P, 0).getStyleIds()).hasLength(1); } - public void testNonExistingAndExistingStyleIdWithRedundantSpaces() throws IOException, - SubtitleDecoderException { + @Test + public void testNonExistingAndExistingStyleIdWithRedundantSpaces() + throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(12); @@ -243,6 +349,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertThat(styleIds).hasLength(2); } + @Test public void testMultipleChaining() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(CHAIN_MULTIPLE_STYLES_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(2); @@ -265,6 +372,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertThat(style.isLinethrough()).isTrue(); } + @Test public void testNoUnderline() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(NO_UNDERLINE_LINETHROUGH_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); @@ -279,6 +387,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { .isFalse(); } + @Test public void testNoLinethrough() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(NO_UNDERLINE_LINETHROUGH_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); @@ -293,6 +402,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { .isFalse(); } + @Test public void testFontSizeSpans() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(10); @@ -328,6 +438,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertRelativeFontSize(spannable, 0.5f); } + @Test public void testFontSizeWithMissingUnitIsIgnored() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_MISSING_UNIT_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(2); @@ -339,6 +450,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertThat(spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class)).hasLength(0); } + @Test public void testFontSizeWithInvalidValueIsIgnored() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_INVALID_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(6); @@ -365,6 +477,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertThat(spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class)).hasLength(0); } + @Test public void testFontSizeWithEmptyValueIsIgnored() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(FONT_SIZE_EMPTY_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(2); @@ -376,6 +489,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertThat(spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class)).hasLength(0); } + @Test public void testFrameRate() throws IOException, SubtitleDecoderException { TtmlSubtitle subtitle = getSubtitle(FRAME_RATE_TTML_FILE); assertThat(subtitle.getEventTimeCount()).isEqualTo(4); @@ -385,12 +499,19 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { assertThat((double) subtitle.getEventTime(3)).isWithin(2000).of(2_002_000_000); } - private void assertSpans(TtmlSubtitle subtitle, int second, - String text, String font, int fontStyle, - int backgroundColor, int color, boolean isUnderline, - boolean isLinethrough, Layout.Alignment alignment) { + private void assertSpans( + TtmlSubtitle subtitle, + int second, + String text, + String font, + int fontStyle, + int backgroundColor, + int color, + boolean isUnderline, + boolean isLinethrough, + Layout.Alignment alignment) { - long timeUs = second * 1000000; + long timeUs = second * 1000000L; List cues = subtitle.getCues(timeUs); assertThat(cues).hasSize(1); @@ -409,15 +530,15 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { } private void assertAbsoluteFontSize(Spannable spannable, int absoluteFontSize) { - AbsoluteSizeSpan[] absoluteSizeSpans = spannable.getSpans(0, spannable.length(), - AbsoluteSizeSpan.class); + AbsoluteSizeSpan[] absoluteSizeSpans = + spannable.getSpans(0, spannable.length(), AbsoluteSizeSpan.class); assertThat(absoluteSizeSpans).hasLength(1); assertThat(absoluteSizeSpans[0].getSize()).isEqualTo(absoluteFontSize); } private void assertRelativeFontSize(Spannable spannable, float relativeFontSize) { - RelativeSizeSpan[] relativeSizeSpans = spannable.getSpans(0, spannable.length(), - RelativeSizeSpan.class); + RelativeSizeSpan[] relativeSizeSpans = + spannable.getSpans(0, spannable.length(), RelativeSizeSpan.class); assertThat(relativeSizeSpans).hasLength(1); assertThat(relativeSizeSpans[0].getSizeChange()).isEqualTo(relativeFontSize); } @@ -440,8 +561,8 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { } private void assertStrikethrough(Spannable spannable, boolean isStrikethrough) { - StrikethroughSpan[] striketroughSpans = spannable.getSpans(0, spannable.length(), - StrikethroughSpan.class); + StrikethroughSpan[] striketroughSpans = + spannable.getSpans(0, spannable.length(), StrikethroughSpan.class); assertWithMessage(isStrikethrough ? "must be strikethrough" : "must not be strikethrough") .that(striketroughSpans) .hasLength(isStrikethrough ? 1 : 0); @@ -491,8 +612,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { private TtmlSubtitle getSubtitle(String file) throws IOException, SubtitleDecoderException { TtmlDecoder ttmlDecoder = new TtmlDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), file); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, file); return ttmlDecoder.decode(bytes, bytes.length, false); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java index 557611c4ea..536ddbabbc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java @@ -29,13 +29,11 @@ import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link TtmlRenderUtil}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class TtmlRenderUtilTest { @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java index 4c35e259ff..aa46584f54 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java @@ -30,11 +30,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** Unit test for {@link TtmlStyle}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class TtmlStyleTest { private static final String FONT_FAMILY = "serif"; diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java similarity index 84% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java index c5c0df61da..c0fa52f74b 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java @@ -16,10 +16,10 @@ package com.google.android.exoplayer2.text.tx3g; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import android.graphics.Color; import android.graphics.Typeface; -import android.test.InstrumentationTestCase; import android.text.SpannedString; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; @@ -32,11 +32,14 @@ import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.SubtitleDecoderException; import java.io.IOException; import java.util.Collections; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit test for {@link Tx3gDecoder}. - */ -public final class Tx3gDecoderTest extends InstrumentationTestCase { +/** Unit test for {@link Tx3gDecoder}. */ +@RunWith(RobolectricTestRunner.class) +public final class Tx3gDecoderTest { private static final String NO_SUBTITLE = "tx3g/no_subtitle"; private static final String SAMPLE_JUST_TEXT = "tx3g/sample_just_text"; @@ -50,16 +53,18 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { private static final String INITIALIZATION = "tx3g/initialization"; private static final String INITIALIZATION_ALL_DEFAULTS = "tx3g/initialization_all_defaults"; + @Test public void testDecodeNoSubtitle() throws IOException, SubtitleDecoderException { Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_SUBTITLE); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, NO_SUBTITLE); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertThat(subtitle.getCues(0)).isEmpty(); } + @Test public void testDecodeJustText() throws IOException, SubtitleDecoderException { Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_JUST_TEXT); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, SAMPLE_JUST_TEXT); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertThat(text.toString()).isEqualTo("CC Test"); @@ -67,9 +72,10 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } + @Test public void testDecodeWithStyl() throws IOException, SubtitleDecoderException { Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, SAMPLE_WITH_STYL); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertThat(text.toString()).isEqualTo("CC Test"); @@ -82,9 +88,11 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } + @Test public void testDecodeWithStylAllDefaults() throws IOException, SubtitleDecoderException { Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL_ALL_DEFAULTS); + byte[] bytes = + TestUtil.getByteArray(RuntimeEnvironment.application, SAMPLE_WITH_STYL_ALL_DEFAULTS); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertThat(text.toString()).isEqualTo("CC Test"); @@ -92,9 +100,10 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } + @Test public void testDecodeUtf16BeNoStyl() throws IOException, SubtitleDecoderException { Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_UTF16_BE_NO_STYL); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, SAMPLE_UTF16_BE_NO_STYL); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertThat(text.toString()).isEqualTo("你好"); @@ -102,9 +111,10 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } + @Test public void testDecodeUtf16LeNoStyl() throws IOException, SubtitleDecoderException { Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_UTF16_LE_NO_STYL); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, SAMPLE_UTF16_LE_NO_STYL); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertThat(text.toString()).isEqualTo("你好"); @@ -112,9 +122,10 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } + @Test public void testDecodeWithMultipleStyl() throws IOException, SubtitleDecoderException { Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_MULTIPLE_STYL); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, SAMPLE_WITH_MULTIPLE_STYL); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertThat(text.toString()).isEqualTo("Line 2\nLine 3"); @@ -129,9 +140,11 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } + @Test public void testDecodeWithOtherExtension() throws IOException, SubtitleDecoderException { Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_OTHER_EXTENSION); + byte[] bytes = + TestUtil.getByteArray(RuntimeEnvironment.application, SAMPLE_WITH_OTHER_EXTENSION); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertThat(text.toString()).isEqualTo("CC Test"); @@ -143,10 +156,11 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } + @Test public void testInitializationDecodeWithStyl() throws IOException, SubtitleDecoderException { - byte[] initBytes = TestUtil.getByteArray(getInstrumentation(), INITIALIZATION); + byte[] initBytes = TestUtil.getByteArray(RuntimeEnvironment.application, INITIALIZATION); Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes)); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, SAMPLE_WITH_STYL); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertThat(text.toString()).isEqualTo("CC Test"); @@ -163,10 +177,11 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.1f); } + @Test public void testInitializationDecodeWithTbox() throws IOException, SubtitleDecoderException { - byte[] initBytes = TestUtil.getByteArray(getInstrumentation(), INITIALIZATION); + byte[] initBytes = TestUtil.getByteArray(RuntimeEnvironment.application, INITIALIZATION); Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes)); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_TBOX); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, SAMPLE_WITH_TBOX); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertThat(text.toString()).isEqualTo("CC Test"); @@ -181,11 +196,13 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.1875f); } - public void testInitializationAllDefaultsDecodeWithStyl() throws IOException, - SubtitleDecoderException { - byte[] initBytes = TestUtil.getByteArray(getInstrumentation(), INITIALIZATION_ALL_DEFAULTS); + @Test + public void testInitializationAllDefaultsDecodeWithStyl() + throws IOException, SubtitleDecoderException { + byte[] initBytes = + TestUtil.getByteArray(RuntimeEnvironment.application, INITIALIZATION_ALL_DEFAULTS); Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes)); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, SAMPLE_WITH_STYL); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertThat(text.toString()).isEqualTo("CC Test"); @@ -198,8 +215,8 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } - private static T findSpan(SpannedString testObject, int expectedStart, int expectedEnd, - Class expectedType) { + private static T findSpan( + SpannedString testObject, int expectedStart, int expectedEnd, Class expectedType) { T[] spans = testObject.getSpans(0, testObject.length(), expectedType); for (T span : spans) { if (testObject.getSpanStart(span) == expectedStart @@ -216,5 +233,4 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertThat(cue.lineAnchor).isEqualTo(Cue.ANCHOR_TYPE_START); assertThat(Math.abs(expectedFraction - cue.line) < 1e-6).isTrue(); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java index 6ade85be28..b81c0c68c3 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java @@ -24,13 +24,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link CssParser}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class CssParserTest { private CssParser parser; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java index 8937007990..f0c426ea65 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java @@ -25,13 +25,11 @@ import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link Mp4WebvttDecoder}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class Mp4WebvttDecoderTest { private static final byte[] SINGLE_CUE_SAMPLE = { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java index 2a6e461627..b89eb47618 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java @@ -27,13 +27,11 @@ import java.util.Collections; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link WebvttCueParser}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class WebvttCueParserTest { @Test diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java similarity index 75% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java index 3d5e62de76..eec985ef5b 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java @@ -16,9 +16,9 @@ package com.google.android.exoplayer2.text.webvtt; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import android.graphics.Typeface; -import android.test.InstrumentationTestCase; import android.text.Layout.Alignment; import android.text.Spanned; import android.text.style.BackgroundColorSpan; @@ -31,11 +31,14 @@ import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.SubtitleDecoderException; import java.io.IOException; import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit test for {@link WebvttDecoder}. - */ -public class WebvttDecoderTest extends InstrumentationTestCase { +/** Unit test for {@link WebvttDecoder}. */ +@RunWith(RobolectricTestRunner.class) +public class WebvttDecoderTest { private static final String TYPICAL_FILE = "webvtt/typical"; private static final String TYPICAL_WITH_BAD_TIMESTAMPS = "webvtt/typical_with_bad_timestamps"; @@ -48,9 +51,10 @@ public class WebvttDecoderTest extends InstrumentationTestCase { private static final String WITH_CSS_COMPLEX_SELECTORS = "webvtt/with_css_complex_selectors"; private static final String EMPTY_FILE = "webvtt/empty"; + @Test public void testDecodeEmpty() throws IOException { WebvttDecoder decoder = new WebvttDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, EMPTY_FILE); try { decoder.decode(bytes, bytes.length, false); fail(); @@ -59,6 +63,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { } } + @Test public void testDecodeTypical() throws IOException, SubtitleDecoderException { WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_FILE); @@ -70,6 +75,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { assertCue(subtitle, 2, 2345000, 3456000, "This is the second subtitle."); } + @Test public void testDecodeTypicalWithBadTimestamps() throws IOException, SubtitleDecoderException { WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_BAD_TIMESTAMPS); @@ -81,6 +87,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { assertCue(subtitle, 2, 2345000, 3456000, "This is the second subtitle."); } + @Test public void testDecodeTypicalWithIds() throws IOException, SubtitleDecoderException { WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_IDS_FILE); @@ -92,6 +99,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { assertCue(subtitle, 2, 2345000, 3456000, "This is the second subtitle."); } + @Test public void testDecodeTypicalWithComments() throws IOException, SubtitleDecoderException { WebvttSubtitle subtitle = getSubtitleForTestAsset(TYPICAL_WITH_COMMENTS_FILE); @@ -103,6 +111,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { assertCue(subtitle, 2, 2345000, 3456000, "This is the second subtitle."); } + @Test public void testDecodeWithTags() throws IOException, SubtitleDecoderException { WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_TAGS_FILE); @@ -116,30 +125,93 @@ public class WebvttDecoderTest extends InstrumentationTestCase { assertCue(subtitle, 6, 6000000, 7000000, "This is the &subtitle."); } + @Test public void testDecodeWithPositioning() throws IOException, SubtitleDecoderException { WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_POSITIONING_FILE); // Test event count. assertThat(subtitle.getEventTimeCount()).isEqualTo(12); // Test cues. - assertCue(subtitle, 0, 0, 1234000, "This is the first subtitle.", Alignment.ALIGN_NORMAL, - Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, 0.1f, Cue.ANCHOR_TYPE_START, 0.35f); - assertCue(subtitle, 2, 2345000, 3456000, "This is the second subtitle.", - Alignment.ALIGN_OPPOSITE, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET, - Cue.TYPE_UNSET, 0.35f); - assertCue(subtitle, 4, 4000000, 5000000, "This is the third subtitle.", - Alignment.ALIGN_CENTER, 0.45f, Cue.LINE_TYPE_FRACTION, Cue.ANCHOR_TYPE_END, Cue.DIMEN_UNSET, - Cue.TYPE_UNSET, 0.35f); - assertCue(subtitle, 6, 6000000, 7000000, "This is the fourth subtitle.", - Alignment.ALIGN_CENTER, -11f, Cue.LINE_TYPE_NUMBER, Cue.TYPE_UNSET, Cue.DIMEN_UNSET, - Cue.TYPE_UNSET, Cue.DIMEN_UNSET); - assertCue(subtitle, 8, 7000000, 8000000, "This is the fifth subtitle.", - Alignment.ALIGN_OPPOSITE, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, 0.1f, - Cue.ANCHOR_TYPE_END, 0.1f); - assertCue(subtitle, 10, 10000000, 11000000, "This is the sixth subtitle.", - Alignment.ALIGN_CENTER, 0.45f, Cue.LINE_TYPE_FRACTION, Cue.ANCHOR_TYPE_END, Cue.DIMEN_UNSET, - Cue.TYPE_UNSET, 0.35f); + assertCue( + subtitle, + 0, + 0, + 1234000, + "This is the first subtitle.", + Alignment.ALIGN_NORMAL, + Cue.DIMEN_UNSET, + Cue.TYPE_UNSET, + Cue.TYPE_UNSET, + 0.1f, + Cue.ANCHOR_TYPE_START, + 0.35f); + assertCue( + subtitle, + 2, + 2345000, + 3456000, + "This is the second subtitle.", + Alignment.ALIGN_OPPOSITE, + Cue.DIMEN_UNSET, + Cue.TYPE_UNSET, + Cue.TYPE_UNSET, + Cue.DIMEN_UNSET, + Cue.TYPE_UNSET, + 0.35f); + assertCue( + subtitle, + 4, + 4000000, + 5000000, + "This is the third subtitle.", + Alignment.ALIGN_CENTER, + 0.45f, + Cue.LINE_TYPE_FRACTION, + Cue.ANCHOR_TYPE_END, + Cue.DIMEN_UNSET, + Cue.TYPE_UNSET, + 0.35f); + assertCue( + subtitle, + 6, + 6000000, + 7000000, + "This is the fourth subtitle.", + Alignment.ALIGN_CENTER, + -11f, + Cue.LINE_TYPE_NUMBER, + Cue.TYPE_UNSET, + Cue.DIMEN_UNSET, + Cue.TYPE_UNSET, + Cue.DIMEN_UNSET); + assertCue( + subtitle, + 8, + 7000000, + 8000000, + "This is the fifth subtitle.", + Alignment.ALIGN_OPPOSITE, + Cue.DIMEN_UNSET, + Cue.TYPE_UNSET, + Cue.TYPE_UNSET, + 0.1f, + Cue.ANCHOR_TYPE_END, + 0.1f); + assertCue( + subtitle, + 10, + 10000000, + 11000000, + "This is the sixth subtitle.", + Alignment.ALIGN_CENTER, + 0.45f, + Cue.LINE_TYPE_FRACTION, + Cue.ANCHOR_TYPE_END, + Cue.DIMEN_UNSET, + Cue.TYPE_UNSET, + 0.35f); } + @Test public void testDecodeWithBadCueHeader() throws IOException, SubtitleDecoderException { WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_BAD_CUE_HEADER_FILE); @@ -151,6 +223,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { assertCue(subtitle, 2, 4000000, 5000000, "This is the third subtitle."); } + @Test public void testWebvttWithCssStyle() throws IOException, SubtitleDecoderException { WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_CSS_STYLES); @@ -175,6 +248,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { .isEqualTo(Typeface.BOLD); } + @Test public void testWithComplexCssSelectors() throws IOException, SubtitleDecoderException { WebvttSubtitle subtitle = getSubtitleForTestAsset(WITH_CSS_COMPLEX_SELECTORS); Spanned text = getUniqueSpanTextAt(subtitle, 0); @@ -211,10 +285,10 @@ public class WebvttDecoderTest extends InstrumentationTestCase { .isEqualTo(Typeface.ITALIC); } - private WebvttSubtitle getSubtitleForTestAsset(String asset) throws IOException, - SubtitleDecoderException { + private WebvttSubtitle getSubtitleForTestAsset(String asset) + throws IOException, SubtitleDecoderException { WebvttDecoder decoder = new WebvttDecoder(); - byte[] bytes = TestUtil.getByteArray(getInstrumentation(), asset); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, asset); return decoder.decode(bytes, bytes.length, false); } @@ -222,15 +296,36 @@ public class WebvttDecoderTest extends InstrumentationTestCase { return (Spanned) sub.getCues(timeUs).get(0).text; } - private static void assertCue(WebvttSubtitle subtitle, int eventTimeIndex, long startTimeUs, - int endTimeUs, String text) { - assertCue(subtitle, eventTimeIndex, startTimeUs, endTimeUs, text, null, Cue.DIMEN_UNSET, - Cue.TYPE_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); + private static void assertCue( + WebvttSubtitle subtitle, int eventTimeIndex, long startTimeUs, int endTimeUs, String text) { + assertCue( + subtitle, + eventTimeIndex, + startTimeUs, + endTimeUs, + text, + null, + Cue.DIMEN_UNSET, + Cue.TYPE_UNSET, + Cue.TYPE_UNSET, + Cue.DIMEN_UNSET, + Cue.TYPE_UNSET, + Cue.DIMEN_UNSET); } - private static void assertCue(WebvttSubtitle subtitle, int eventTimeIndex, long startTimeUs, - int endTimeUs, String text, Alignment textAlignment, float line, int lineType, int lineAnchor, - float position, int positionAnchor, float size) { + private static void assertCue( + WebvttSubtitle subtitle, + int eventTimeIndex, + long startTimeUs, + int endTimeUs, + String text, + Alignment textAlignment, + float line, + int lineType, + int lineAnchor, + float position, + int positionAnchor, + float size) { assertThat(subtitle.getEventTime(eventTimeIndex)).isEqualTo(startTimeUs); assertThat(subtitle.getEventTime(eventTimeIndex + 1)).isEqualTo(endTimeUs); List cues = subtitle.getCues(subtitle.getEventTime(eventTimeIndex)); @@ -246,5 +341,4 @@ public class WebvttDecoderTest extends InstrumentationTestCase { assertThat(cue.positionAnchor).isEqualTo(positionAnchor); assertThat(cue.size).isEqualTo(size); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java index c3c30e44a8..3074f28b64 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java @@ -26,13 +26,11 @@ import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for {@link WebvttSubtitle}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class WebvttSubtitleTest { private static final String FIRST_SUBTITLE_STRING = "This is the first subtitle."; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java index ea19c72826..956174f43b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java @@ -38,11 +38,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** Unit test for {@link AdaptiveTrackSelection}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class AdaptiveTrackSelectionTest { @Mock private BandwidthMeter mockBandwidthMeter; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 24362d1570..4b2a3a5ad6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -25,13 +25,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit tests for {@link DefaultTrackSelector}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class DefaultTrackSelectorTest { private static final RendererCapabilities ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES = diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java index b9ea0087c7..b80110365c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java @@ -30,13 +30,11 @@ import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit tests for {@link MappingTrackSelector}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class MappingTrackSelectorTest { private static final RendererCapabilities VIDEO_CAPABILITIES = diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java similarity index 58% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java index d582d25ab1..7ffc14d51f 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/AssetDataSourceTest.java @@ -16,28 +16,37 @@ package com.google.android.exoplayer2.upstream; import android.net.Uri; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.testutil.TestUtil; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Unit tests for {@link AssetDataSource}. - */ -public final class AssetDataSourceTest extends InstrumentationTestCase { +/** Unit tests for {@link AssetDataSource}. */ +@RunWith(RobolectricTestRunner.class) +public final class AssetDataSourceTest { private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3"; + @Test public void testReadFileUri() throws Exception { - AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext()); + AssetDataSource dataSource = new AssetDataSource(RuntimeEnvironment.application); DataSpec dataSpec = new DataSpec(Uri.parse("file:///android_asset/" + DATA_PATH)); - TestUtil.assertDataSourceContent(dataSource, dataSpec, - TestUtil.getByteArray(getInstrumentation(), DATA_PATH), true); + TestUtil.assertDataSourceContent( + dataSource, + dataSpec, + TestUtil.getByteArray(RuntimeEnvironment.application, DATA_PATH), + true); } + @Test public void testReadAssetUri() throws Exception { - AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext()); + AssetDataSource dataSource = new AssetDataSource(RuntimeEnvironment.application); DataSpec dataSpec = new DataSpec(Uri.parse("asset:///" + DATA_PATH)); - TestUtil.assertDataSourceContent(dataSource, dataSpec, - TestUtil.getByteArray(getInstrumentation(), DATA_PATH), true); + TestUtil.assertDataSourceContent( + dataSource, + dataSpec, + TestUtil.getByteArray(RuntimeEnvironment.application, DATA_PATH), + true); } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java index a72d060287..f04f01bd5f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java @@ -23,13 +23,11 @@ import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit tests for {@link ByteArrayDataSource}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class ByteArrayDataSourceTest { private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java index 85c4341232..49f865e2b5 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSchemeDataSourceTest.java @@ -27,13 +27,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit tests for {@link DataSchemeDataSource}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class DataSchemeDataSourceTest { private DataSource schemeDataDataSource; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java index 8cd6c23fb1..f47cfc4469 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java @@ -25,13 +25,11 @@ import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit tests for {@link DataSourceInputStream}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class DataSourceInputStreamTest { private static final byte[] TEST_DATA = TestUtil.buildTestData(16); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java index 67fae69b44..09be138abe 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java @@ -37,13 +37,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; /** * Unit tests for {@link CacheDataSource}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class CacheDataSourceTest { private static final byte[] TEST_DATA = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java index 3b8276c731..1e6febd8a9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java @@ -39,13 +39,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; /** * Additional tests for {@link CacheDataSource}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class CacheDataSourceTest2 { private static final String EXO_CACHE_DIR = "exo"; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java index 250e09bab4..7237ecd50d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java @@ -44,13 +44,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; /** * Tests {@link CacheUtil}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class CacheUtilTest { /** diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java similarity index 59% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java rename to library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java index 0b0556e513..50f9cd2ae8 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java @@ -20,29 +20,37 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.when; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.extractor.ChunkIndex; -import com.google.android.exoplayer2.testutil.MockitoUtil; import com.google.android.exoplayer2.util.Util; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.util.TreeSet; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; -/** - * Tests for {@link CachedRegionTracker}. - */ -public final class CachedRegionTrackerTest extends InstrumentationTestCase { +/** Tests for {@link CachedRegionTracker}. */ +@RunWith(RobolectricTestRunner.class) +public final class CachedRegionTrackerTest { private static final String CACHE_KEY = "abc"; private static final long MS_IN_US = 1000; // 5 chunks, each 20 bytes long and 100 ms long. - private static final ChunkIndex CHUNK_INDEX = new ChunkIndex( - new int[] {20, 20, 20, 20, 20}, - new long[] {100, 120, 140, 160, 180}, - new long[] {100 * MS_IN_US, 100 * MS_IN_US, 100 * MS_IN_US, 100 * MS_IN_US, 100 * MS_IN_US}, - new long[] {0, 100 * MS_IN_US, 200 * MS_IN_US, 300 * MS_IN_US, 400 * MS_IN_US}); + private static final ChunkIndex CHUNK_INDEX = + new ChunkIndex( + new int[] {20, 20, 20, 20, 20}, + new long[] {100, 120, 140, 160, 180}, + new long[] { + 100 * MS_IN_US, 100 * MS_IN_US, 100 * MS_IN_US, 100 * MS_IN_US, 100 * MS_IN_US + }, + new long[] {0, 100 * MS_IN_US, 200 * MS_IN_US, 300 * MS_IN_US, 400 * MS_IN_US}); @Mock private Cache cache; private CachedRegionTracker tracker; @@ -50,68 +58,59 @@ public final class CachedRegionTrackerTest extends InstrumentationTestCase { private CachedContentIndex index; private File cacheDir; - @Override - protected void setUp() throws Exception { - super.setUp(); - MockitoUtil.setUpMockito(this); + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); when(cache.addListener(anyString(), any(Cache.Listener.class))) .thenReturn(new TreeSet()); tracker = new CachedRegionTracker(cache, CACHE_KEY, CHUNK_INDEX); - cacheDir = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest"); + cacheDir = Util.createTempDirectory(RuntimeEnvironment.application, "ExoPlayerTest"); index = new CachedContentIndex(cacheDir); } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { Util.recursiveDelete(cacheDir); - super.tearDown(); } + @Test public void testGetRegion_noSpansInCache() { assertThat(tracker.getRegionEndTimeMs(100)).isEqualTo(CachedRegionTracker.NOT_CACHED); assertThat(tracker.getRegionEndTimeMs(150)).isEqualTo(CachedRegionTracker.NOT_CACHED); } + @Test public void testGetRegion_fullyCached() throws Exception { - tracker.onSpanAdded( - cache, - newCacheSpan(100, 100)); + tracker.onSpanAdded(cache, newCacheSpan(100, 100)); assertThat(tracker.getRegionEndTimeMs(101)).isEqualTo(CachedRegionTracker.CACHED_TO_END); assertThat(tracker.getRegionEndTimeMs(121)).isEqualTo(CachedRegionTracker.CACHED_TO_END); } + @Test public void testGetRegion_partiallyCached() throws Exception { - tracker.onSpanAdded( - cache, - newCacheSpan(100, 40)); + tracker.onSpanAdded(cache, newCacheSpan(100, 40)); assertThat(tracker.getRegionEndTimeMs(101)).isEqualTo(200); assertThat(tracker.getRegionEndTimeMs(121)).isEqualTo(200); } + @Test public void testGetRegion_multipleSpanAddsJoinedCorrectly() throws Exception { - tracker.onSpanAdded( - cache, - newCacheSpan(100, 20)); - tracker.onSpanAdded( - cache, - newCacheSpan(120, 20)); + tracker.onSpanAdded(cache, newCacheSpan(100, 20)); + tracker.onSpanAdded(cache, newCacheSpan(120, 20)); assertThat(tracker.getRegionEndTimeMs(101)).isEqualTo(200); assertThat(tracker.getRegionEndTimeMs(121)).isEqualTo(200); } + @Test public void testGetRegion_fullyCachedThenPartiallyRemoved() throws Exception { // Start with the full stream in cache. - tracker.onSpanAdded( - cache, - newCacheSpan(100, 100)); + tracker.onSpanAdded(cache, newCacheSpan(100, 100)); // Remove the middle bit. - tracker.onSpanRemoved( - cache, - newCacheSpan(140, 40)); + tracker.onSpanRemoved(cache, newCacheSpan(140, 40)); assertThat(tracker.getRegionEndTimeMs(101)).isEqualTo(200); assertThat(tracker.getRegionEndTimeMs(121)).isEqualTo(200); @@ -119,17 +118,32 @@ public final class CachedRegionTrackerTest extends InstrumentationTestCase { assertThat(tracker.getRegionEndTimeMs(181)).isEqualTo(CachedRegionTracker.CACHED_TO_END); } + @Test public void testGetRegion_subchunkEstimation() throws Exception { - tracker.onSpanAdded( - cache, - newCacheSpan(100, 10)); + tracker.onSpanAdded(cache, newCacheSpan(100, 10)); assertThat(tracker.getRegionEndTimeMs(101)).isEqualTo(50); assertThat(tracker.getRegionEndTimeMs(111)).isEqualTo(CachedRegionTracker.NOT_CACHED); } private CacheSpan newCacheSpan(int position, int length) throws IOException { - return SimpleCacheSpanTest.createCacheSpan(index, cacheDir, CACHE_KEY, position, length, 0); + int id = index.assignIdForKey(CACHE_KEY); + File cacheFile = createCacheSpanFile(cacheDir, id, position, length, 0); + return SimpleCacheSpan.createCacheEntry(cacheFile, index); } + public static File createCacheSpanFile( + File cacheDir, int id, long offset, int length, long lastAccessTimestamp) throws IOException { + File cacheFile = SimpleCacheSpan.getCacheFile(cacheDir, id, offset, lastAccessTimestamp); + createTestFile(cacheFile, length); + return cacheFile; + } + + private static void createTestFile(File file, int length) throws IOException { + FileOutputStream output = new FileOutputStream(file); + for (int i = 0; i < length; i++) { + output.write(i); + } + output.close(); + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java index 6f7f567ae7..84327e1091 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictorTest.java @@ -21,13 +21,11 @@ import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit tests for {@link LeastRecentlyUsedCacheEvictor}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class LeastRecentlyUsedCacheEvictorTest { @Before diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java index bbcf46a30c..89ace34edc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java @@ -41,13 +41,11 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; /** * Unit tests for {@link SimpleCache}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class SimpleCacheTest { private static final String KEY_1 = "key1"; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java index 833a7e10c1..40b626a7db 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java @@ -26,13 +26,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit tests for {@link AesFlushingCipher}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class AesFlushingCipherTest { private static final int DATA_LENGTH = 65536; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java index dcf3d31eb3..4d80a9647e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/AtomicFileTest.java @@ -27,13 +27,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; /** * Tests {@link AtomicFile}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class AtomicFileTest { private File tempFolder; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java index 13b126090c..af596c35f3 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ColorParserTest.java @@ -27,13 +27,11 @@ import android.graphics.Color; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit test for ColorParser. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class ColorParserTest { // Negative tests. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java index ee77664cce..473e5a8b05 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java @@ -23,13 +23,11 @@ import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Tests for {@link NalUnitUtil}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class NalUnitUtilTest { private static final int TEST_PARTIAL_NAL_POSITION = 4; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java index 0d864f407f..611584a38c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java @@ -21,13 +21,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Tests for {@link ParsableBitArray}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class ParsableBitArrayTest { private static final byte[] TEST_DATA = new byte[] {0x3C, (byte) 0xD2, (byte) 0x5F, (byte) 0x01, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java index 96c29f571d..701f532d6a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java @@ -24,13 +24,11 @@ import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Tests for {@link ParsableByteArray}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class ParsableByteArrayTest { private static final byte[] TEST_DATA = diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java index a3f38abcdb..210c42cfa5 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java @@ -22,13 +22,11 @@ import static org.junit.Assert.fail; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Tests for {@link ParsableNalUnitBitArray}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class ParsableNalUnitBitArrayTest { private static final byte[] NO_ESCAPING_TEST_DATA = createByteArray(0, 3, 0, 1, 3, 0, 0); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java index 8e384bbb10..6c921f0288 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java @@ -21,13 +21,11 @@ import java.io.ByteArrayOutputStream; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Tests {@link ReusableBufferedOutputStream}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class ReusableBufferedOutputStreamTest { private static final byte[] TEST_DATA_1 = "test data 1".getBytes(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java index 52e7a722fb..a52867e1b2 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/UriUtilTest.java @@ -21,13 +21,11 @@ import static com.google.common.truth.Truth.assertThat; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit tests for {@link UriUtil}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public final class UriUtilTest { /** diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java index ca7a3b199d..cdd5d1a696 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java @@ -32,13 +32,11 @@ import java.util.Random; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; /** * Unit tests for {@link Util}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE) public class UtilTest { @Test diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java index bee3334f65..1e4811aadf 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java @@ -17,7 +17,7 @@ package com.google.android.exoplayer2.testutil; import static com.google.common.truth.Truth.assertThat; -import android.app.Instrumentation; +import android.content.Context; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.testutil.FakeExtractorInput.SimulatedIOException; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.lang.reflect.Field; import java.util.Arrays; /** @@ -34,6 +35,22 @@ import java.util.Arrays; */ public final class ExtractorAsserts { + private static Context robolectricContext; + + static { + try { + Class runtimeEnvironmentClass = Class.forName("org.robolectric.RuntimeEnvironment"); + Field applicationField = runtimeEnvironmentClass.getDeclaredField("application"); + robolectricContext = (Context) applicationField.get(null); + } catch (ClassNotFoundException e) { + // Keep Robolectric context at null if not found. + } catch (NoSuchFieldException e) { + // Keep Robolectric context at null if not found. + } catch (IllegalAccessException e) { + // Keep Robolectric context at null if not found. + } + } + /** * A factory for {@link Extractor} instances. */ @@ -45,57 +62,87 @@ public final class ExtractorAsserts { private static final String UNKNOWN_LENGTH_EXTENSION = ".unklen" + DUMP_EXTENSION; /** - * Asserts that an extractor behaves correctly given valid input data: + * Asserts that an extractor behaves correctly given valid input data. Can only be used from + * Robolectric tests. + * *

    *
  • Calls {@link Extractor#seek(long, long)} and {@link Extractor#release()} without calling - * {@link Extractor#init(ExtractorOutput)} to check these calls do not fail.
  • - *
  • Calls {@link #assertOutput(Extractor, String, byte[], Instrumentation, boolean, boolean, - * boolean, boolean)} with all possible combinations of "simulate" parameters.
  • + * {@link Extractor#init(ExtractorOutput)} to check these calls do not fail. + *
  • Calls {@link #assertOutput(Extractor, String, byte[], Context, boolean, boolean, boolean, + * boolean)} with all possible combinations of "simulate" parameters. *
* * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor} * class which is to be tested. * @param file The path to the input sample. - * @param instrumentation To be used to load the sample file. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. */ - public static void assertBehavior(ExtractorFactory factory, String file, - Instrumentation instrumentation) throws IOException, InterruptedException { + public static void assertBehavior(ExtractorFactory factory, String file) + throws IOException, InterruptedException { // Check behavior prior to initialization. Extractor extractor = factory.create(); extractor.seek(0, 0); extractor.release(); // Assert output. - byte[] fileData = TestUtil.getByteArray(instrumentation, file); - assertOutput(factory, file, fileData, instrumentation); + byte[] fileData = TestUtil.getByteArray(robolectricContext, file); + assertOutput(factory, file, fileData, robolectricContext); } /** - * Calls {@link #assertOutput(Extractor, String, byte[], Instrumentation, boolean, boolean, - * boolean, boolean)} with all possible combinations of "simulate" parameters with - * {@code sniffFirst} set to true, and makes one additional call with the "simulate" and - * {@code sniffFirst} parameters all set to false. + * Asserts that an extractor behaves correctly given valid input data: + * + *
    + *
  • Calls {@link Extractor#seek(long, long)} and {@link Extractor#release()} without calling + * {@link Extractor#init(ExtractorOutput)} to check these calls do not fail. + *
  • Calls {@link #assertOutput(Extractor, String, byte[], Context, boolean, boolean, boolean, + * boolean)} with all possible combinations of "simulate" parameters. + *
+ * + * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor} + * class which is to be tested. + * @param file The path to the input sample. + * @param context To be used to load the sample file. + * @throws IOException If reading from the input fails. + * @throws InterruptedException If interrupted while reading from the input. + */ + public static void assertBehavior(ExtractorFactory factory, String file, Context context) + throws IOException, InterruptedException { + // Check behavior prior to initialization. + Extractor extractor = factory.create(); + extractor.seek(0, 0); + extractor.release(); + // Assert output. + byte[] fileData = TestUtil.getByteArray(context, file); + assertOutput(factory, file, fileData, context); + } + + /** + * Calls {@link #assertOutput(Extractor, String, byte[], Context, boolean, boolean, boolean, + * boolean)} with all possible combinations of "simulate" parameters with {@code sniffFirst} set + * to true, and makes one additional call with the "simulate" and {@code sniffFirst} parameters + * all set to false. * * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor} * class which is to be tested. * @param file The path to the input sample. * @param data Content of the input file. - * @param instrumentation To be used to load the sample file. + * @param context To be used to load the sample file. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. */ - public static void assertOutput(ExtractorFactory factory, String file, byte[] data, - Instrumentation instrumentation) throws IOException, InterruptedException { - assertOutput(factory.create(), file, data, instrumentation, true, false, false, false); - assertOutput(factory.create(), file, data, instrumentation, true, false, false, true); - assertOutput(factory.create(), file, data, instrumentation, true, false, true, false); - assertOutput(factory.create(), file, data, instrumentation, true, false, true, true); - assertOutput(factory.create(), file, data, instrumentation, true, true, false, false); - assertOutput(factory.create(), file, data, instrumentation, true, true, false, true); - assertOutput(factory.create(), file, data, instrumentation, true, true, true, false); - assertOutput(factory.create(), file, data, instrumentation, true, true, true, true); - assertOutput(factory.create(), file, data, instrumentation, false, false, false, false); + public static void assertOutput( + ExtractorFactory factory, String file, byte[] data, Context context) + throws IOException, InterruptedException { + assertOutput(factory.create(), file, data, context, true, false, false, false); + assertOutput(factory.create(), file, data, context, true, false, false, true); + assertOutput(factory.create(), file, data, context, true, false, true, false); + assertOutput(factory.create(), file, data, context, true, false, true, true); + assertOutput(factory.create(), file, data, context, true, true, false, false); + assertOutput(factory.create(), file, data, context, true, true, false, true); + assertOutput(factory.create(), file, data, context, true, true, true, false); + assertOutput(factory.create(), file, data, context, true, true, true, true); + assertOutput(factory.create(), file, data, context, false, false, false, false); } /** @@ -107,7 +154,7 @@ public final class ExtractorAsserts { * @param extractor The {@link Extractor} to be tested. * @param file The path to the input sample. * @param data Content of the input file. - * @param instrumentation To be used to load the sample file. + * @param context To be used to load the sample file. * @param sniffFirst Whether to sniff the data by calling {@link Extractor#sniff(ExtractorInput)} * prior to consuming it. * @param simulateIOErrors Whether to simulate IO errors. @@ -117,10 +164,16 @@ public final class ExtractorAsserts { * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. */ - public static FakeExtractorOutput assertOutput(Extractor extractor, String file, byte[] data, - Instrumentation instrumentation, boolean sniffFirst, boolean simulateIOErrors, - boolean simulateUnknownLength, boolean simulatePartialReads) throws IOException, - InterruptedException { + private static FakeExtractorOutput assertOutput( + Extractor extractor, + String file, + byte[] data, + Context context, + boolean sniffFirst, + boolean simulateIOErrors, + boolean simulateUnknownLength, + boolean simulatePartialReads) + throws IOException, InterruptedException { FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data) .setSimulateIOErrors(simulateIOErrors) .setSimulateUnknownLength(simulateUnknownLength) @@ -132,11 +185,10 @@ public final class ExtractorAsserts { } FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, 0, true); - if (simulateUnknownLength - && assetExists(instrumentation, file + UNKNOWN_LENGTH_EXTENSION)) { - extractorOutput.assertOutput(instrumentation, file + UNKNOWN_LENGTH_EXTENSION); + if (simulateUnknownLength && assetExists(context, file + UNKNOWN_LENGTH_EXTENSION)) { + extractorOutput.assertOutput(context, file + UNKNOWN_LENGTH_EXTENSION); } else { - extractorOutput.assertOutput(instrumentation, file + ".0" + DUMP_EXTENSION); + extractorOutput.assertOutput(context, file + ".0" + DUMP_EXTENSION); } SeekMap seekMap = extractorOutput.seekMap; @@ -151,7 +203,7 @@ public final class ExtractorAsserts { } consumeTestData(extractor, input, timeUs, extractorOutput, false); - extractorOutput.assertOutput(instrumentation, file + '.' + j + DUMP_EXTENSION); + extractorOutput.assertOutput(context, file + '.' + j + DUMP_EXTENSION); } } @@ -165,16 +217,19 @@ public final class ExtractorAsserts { * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor} * class which is to be tested. * @param sampleFile The path to the input sample. - * @param instrumentation To be used to load the sample file. + * @param context To be used to load the sample file. * @param expectedThrowable Expected {@link Throwable} class. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. * @see #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean) */ - public static void assertThrows(ExtractorFactory factory, String sampleFile, - Instrumentation instrumentation, Class expectedThrowable) + public static void assertThrows( + ExtractorFactory factory, + String sampleFile, + Context context, + Class expectedThrowable) throws IOException, InterruptedException { - byte[] fileData = TestUtil.getByteArray(instrumentation, sampleFile); + byte[] fileData = TestUtil.getByteArray(context, sampleFile); assertThrows(factory, fileData, expectedThrowable); } @@ -190,8 +245,9 @@ public final class ExtractorAsserts { * @throws InterruptedException If interrupted while reading from the input. * @see #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean) */ - public static void assertThrows(ExtractorFactory factory, byte[] fileData, - Class expectedThrowable) throws IOException, InterruptedException { + private static void assertThrows( + ExtractorFactory factory, byte[] fileData, Class expectedThrowable) + throws IOException, InterruptedException { assertThrows(factory.create(), fileData, expectedThrowable, false, false, false); assertThrows(factory.create(), fileData, expectedThrowable, true, false, false); assertThrows(factory.create(), fileData, expectedThrowable, false, true, false); @@ -214,10 +270,14 @@ public final class ExtractorAsserts { * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. */ - public static void assertThrows(Extractor extractor, byte[] fileData, - Class expectedThrowable, boolean simulateIOErrors, - boolean simulateUnknownLength, boolean simulatePartialReads) throws IOException, - InterruptedException { + private static void assertThrows( + Extractor extractor, + byte[] fileData, + Class expectedThrowable, + boolean simulateIOErrors, + boolean simulateUnknownLength, + boolean simulatePartialReads) + throws IOException, InterruptedException { FakeExtractorInput input = new FakeExtractorInput.Builder().setData(fileData) .setSimulateIOErrors(simulateIOErrors) .setSimulateUnknownLength(simulateUnknownLength) @@ -278,13 +338,11 @@ public final class ExtractorAsserts { } } - private static boolean assetExists(Instrumentation instrumentation, String fileName) - throws IOException { + private static boolean assetExists(Context context, String fileName) throws IOException { int i = fileName.lastIndexOf('/'); String path = i >= 0 ? fileName.substring(0, i) : ""; String file = i >= 0 ? fileName.substring(i + 1) : fileName; - return Arrays.asList(instrumentation.getContext().getResources().getAssets().list(path)) - .contains(file); + return Arrays.asList(context.getResources().getAssets().list(path)).contains(file); } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java index 9b59e48a0e..c6543bd7a5 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java @@ -18,7 +18,7 @@ package com.google.android.exoplayer2.testutil; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import android.app.Instrumentation; +import android.content.Context; import android.util.SparseArray; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.SeekMap; @@ -32,9 +32,9 @@ import java.io.PrintWriter; public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpable { /** - * If true, makes {@link #assertOutput(Instrumentation, String)} method write dump result to - * {@code /sdcard/Android/data/apk_package/ + dumpfile} file instead of comparing it with an - * existing file. + * If true, makes {@link #assertOutput(Context, String)} method write dump result to {@code + * /sdcard/Android/data/apk_package/ + dumpfile} file instead of comparing it with an existing + * file. */ private static final boolean WRITE_DUMP = false; @@ -97,18 +97,18 @@ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpab * actual dump will be written to {@code dumpFile}. This new dump file needs to be copied to the * project, {@code library/src/androidTest/assets} folder manually. */ - public void assertOutput(Instrumentation instrumentation, String dumpFile) throws IOException { + public void assertOutput(Context context, String dumpFile) throws IOException { String actual = new Dumper().add(this).toString(); if (WRITE_DUMP) { - File directory = instrumentation.getContext().getExternalFilesDir(null); + File directory = context.getExternalFilesDir(null); File file = new File(directory, dumpFile); file.getParentFile().mkdirs(); PrintWriter out = new PrintWriter(file); out.print(actual); out.close(); } else { - String expected = TestUtil.getString(instrumentation, dumpFile); + String expected = TestUtil.getString(context, dumpFile); assertWithMessage(dumpFile).that(actual).isEqualTo(expected); } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java index b1598a608c..cf0cc342f8 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; @@ -142,30 +143,38 @@ public class MediaSourceTestRunner { } /** - * Calls {@link MediaPeriod#prepare(MediaPeriod.Callback, long)} on the playback thread. + * Calls {@link MediaPeriod#prepare(MediaPeriod.Callback, long)} on the playback thread and blocks + * until the method has been called. * * @param mediaPeriod The {@link MediaPeriod} to prepare. * @param positionUs The position at which to prepare. - * @return A {@link ConditionVariable} that will be opened when preparation completes. + * @return A {@link CountDownLatch} that will be counted down when preparation completes. */ - public ConditionVariable preparePeriod(final MediaPeriod mediaPeriod, final long positionUs) { - final ConditionVariable preparedCondition = new ConditionVariable(); - runOnPlaybackThread(new Runnable() { - @Override - public void run() { - mediaPeriod.prepare(new MediaPeriod.Callback() { + public CountDownLatch preparePeriod(final MediaPeriod mediaPeriod, final long positionUs) { + final ConditionVariable prepareCalled = new ConditionVariable(); + final CountDownLatch preparedCountDown = new CountDownLatch(1); + runOnPlaybackThread( + new Runnable() { @Override - public void onPrepared(MediaPeriod mediaPeriod) { - preparedCondition.open(); + public void run() { + mediaPeriod.prepare( + new MediaPeriod.Callback() { + @Override + public void onPrepared(MediaPeriod mediaPeriod) { + preparedCountDown.countDown(); + } + + @Override + public void onContinueLoadingRequested(MediaPeriod source) { + // Do nothing. + } + }, + positionUs); + prepareCalled.open(); } - @Override - public void onContinueLoadingRequested(MediaPeriod source) { - // Do nothing. - } - }, positionUs); - } - }); - return preparedCondition; + }); + prepareCalled.block(); + return preparedCountDown; } /** @@ -234,10 +243,10 @@ public class MediaSourceTestRunner { /** * Creates and releases all periods (including ad periods) defined in the last timeline to be - * returned from {@link #prepareSource()}, {@link #assertTimelineChange()} or - * {@link #assertTimelineChangeBlocking()}. + * returned from {@link #prepareSource()}, {@link #assertTimelineChange()} or {@link + * #assertTimelineChangeBlocking()}. */ - public void assertPrepareAndReleaseAllPeriods() { + public void assertPrepareAndReleaseAllPeriods() throws InterruptedException { Timeline.Period period = new Timeline.Period(); for (int i = 0; i < timeline.getPeriodCount(); i++) { assertPrepareAndReleasePeriod(new MediaPeriodId(i)); @@ -250,15 +259,16 @@ public class MediaSourceTestRunner { } } - private void assertPrepareAndReleasePeriod(MediaPeriodId mediaPeriodId) { + private void assertPrepareAndReleasePeriod(MediaPeriodId mediaPeriodId) + throws InterruptedException { MediaPeriod mediaPeriod = createPeriod(mediaPeriodId); - ConditionVariable preparedCondition = preparePeriod(mediaPeriod, 0); - assertThat(preparedCondition.block(TIMEOUT_MS)).isTrue(); + CountDownLatch preparedCondition = preparePeriod(mediaPeriod, 0); + assertThat(preparedCondition.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); // MediaSource is supposed to support multiple calls to createPeriod with the same id without an // intervening call to releasePeriod. MediaPeriod secondMediaPeriod = createPeriod(mediaPeriodId); - ConditionVariable secondPreparedCondition = preparePeriod(secondMediaPeriod, 0); - assertThat(secondPreparedCondition.block(TIMEOUT_MS)).isTrue(); + CountDownLatch secondPreparedCondition = preparePeriod(secondMediaPeriod, 0); + assertThat(secondPreparedCondition.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); // Release the periods. releasePeriod(mediaPeriod); releasePeriod(secondMediaPeriod); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java index 34d951b142..fb50ef131b 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java @@ -150,9 +150,8 @@ public class TestUtil { return context.getResources().getAssets().open(fileName); } - public static String getString(Instrumentation instrumentation, String fileName) - throws IOException { - return new String(getByteArray(instrumentation, fileName)); + public static String getString(Context context, String fileName) throws IOException { + return new String(getByteArray(context, fileName)); } /** From 3ec96aee17983f1daa5f1d254c8e91da19b6dad8 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 12 Feb 2018 11:38:36 -0800 Subject: [PATCH 23/53] Translation console import ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185409304 --- .../src/main/res/values-af/strings.xml | 17 ++++---- .../src/main/res/values-am/strings.xml | 17 ++++---- .../src/main/res/values-ar/strings.xml | 17 ++++---- .../src/main/res/values-b+sr+Latn/strings.xml | 17 ++++---- .../src/main/res/values-bg/strings.xml | 17 ++++---- .../src/main/res/values-ca/strings.xml | 17 ++++---- .../src/main/res/values-cs/strings.xml | 17 ++++---- .../src/main/res/values-da/strings.xml | 17 ++++---- .../src/main/res/values-de/strings.xml | 17 ++++---- .../src/main/res/values-el/strings.xml | 17 ++++---- .../src/main/res/values-en-rAU/strings.xml | 17 ++++---- .../src/main/res/values-en-rGB/strings.xml | 17 ++++---- .../src/main/res/values-en-rIN/strings.xml | 17 ++++---- .../src/main/res/values-es-rUS/strings.xml | 17 ++++---- .../src/main/res/values-es/strings.xml | 17 ++++---- .../src/main/res/values-fa/strings.xml | 17 ++++---- .../src/main/res/values-fi/strings.xml | 17 ++++---- .../src/main/res/values-fr-rCA/strings.xml | 17 ++++---- .../src/main/res/values-fr/strings.xml | 17 ++++---- .../src/main/res/values-hi/strings.xml | 17 ++++---- .../src/main/res/values-hr/strings.xml | 17 ++++---- .../src/main/res/values-hu/strings.xml | 17 ++++---- .../src/main/res/values-in/strings.xml | 17 ++++---- .../src/main/res/values-it/strings.xml | 17 ++++---- .../src/main/res/values-iw/strings.xml | 17 ++++---- .../src/main/res/values-ja/strings.xml | 17 ++++---- .../src/main/res/values-ko/strings.xml | 17 ++++---- .../src/main/res/values-lt/strings.xml | 17 ++++---- .../src/main/res/values-lv/strings.xml | 17 ++++---- .../src/main/res/values-nb/strings.xml | 17 ++++---- .../src/main/res/values-nl/strings.xml | 17 ++++---- .../src/main/res/values-pl/strings.xml | 17 ++++---- .../src/main/res/values-pt-rPT/strings.xml | 17 ++++---- .../src/main/res/values-pt/strings.xml | 17 ++++---- .../src/main/res/values-ro/strings.xml | 17 ++++---- .../src/main/res/values-ru/strings.xml | 17 ++++---- .../src/main/res/values-sk/strings.xml | 17 ++++---- .../src/main/res/values-sl/strings.xml | 17 ++++---- .../src/main/res/values-sr/strings.xml | 14 ++++--- .../src/main/res/values-sv/strings.xml | 17 ++++---- .../src/main/res/values-sw/strings.xml | 17 ++++---- .../src/main/res/values-th/strings.xml | 17 ++++---- .../src/main/res/values-tl/strings.xml | 17 ++++---- .../src/main/res/values-tr/strings.xml | 17 ++++---- .../src/main/res/values-uk/strings.xml | 17 ++++---- .../src/main/res/values-vi/strings.xml | 17 ++++---- .../src/main/res/values-zh-rCN/strings.xml | 17 ++++---- .../src/main/res/values-zh-rHK/strings.xml | 17 ++++---- .../src/main/res/values-zh-rTW/strings.xml | 17 ++++---- .../src/main/res/values-zu/strings.xml | 17 ++++---- library/ui/src/main/res/values-af/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-am/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-ar/strings.xml | 39 +++++++++++-------- .../src/main/res/values-b+sr+Latn/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-bg/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-ca/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-cs/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-da/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-de/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-el/strings.xml | 39 +++++++++++-------- .../ui/src/main/res/values-en-rAU/strings.xml | 38 ++++++++++-------- .../ui/src/main/res/values-en-rGB/strings.xml | 39 +++++++++++-------- .../ui/src/main/res/values-en-rIN/strings.xml | 39 +++++++++++-------- .../ui/src/main/res/values-es-rUS/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-es/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-fa/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-fi/strings.xml | 38 ++++++++++-------- .../ui/src/main/res/values-fr-rCA/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-fr/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-hi/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-hr/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-hu/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-in/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-it/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-iw/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-ja/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-ko/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-lt/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-lv/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-nb/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-nl/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-pl/strings.xml | 39 +++++++++++-------- .../ui/src/main/res/values-pt-rPT/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-pt/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-ro/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-ru/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-sk/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-sl/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-sr/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-sv/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-sw/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-th/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-tl/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-tr/strings.xml | 38 ++++++++++-------- library/ui/src/main/res/values-uk/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-vi/strings.xml | 39 +++++++++++-------- .../ui/src/main/res/values-zh-rCN/strings.xml | 39 +++++++++++-------- .../ui/src/main/res/values-zh-rHK/strings.xml | 39 +++++++++++-------- .../ui/src/main/res/values-zh-rTW/strings.xml | 39 +++++++++++-------- library/ui/src/main/res/values-zu/strings.xml | 39 +++++++++++-------- 100 files changed, 1550 insertions(+), 1229 deletions(-) diff --git a/extensions/mediasession/src/main/res/values-af/strings.xml b/extensions/mediasession/src/main/res/values-af/strings.xml index 4ef78cd84f..65bc1e89d8 100644 --- a/extensions/mediasession/src/main/res/values-af/strings.xml +++ b/extensions/mediasession/src/main/res/values-af/strings.xml @@ -1,6 +1,5 @@ - - - - "Herhaal alles" - "Herhaal niks" - "Herhaal een" + --> + + + "Herhaal niks" + "Herhaal een" + "Herhaal alles" diff --git a/extensions/mediasession/src/main/res/values-am/strings.xml b/extensions/mediasession/src/main/res/values-am/strings.xml index 531f605584..0dc20aaa04 100644 --- a/extensions/mediasession/src/main/res/values-am/strings.xml +++ b/extensions/mediasession/src/main/res/values-am/strings.xml @@ -1,6 +1,5 @@ - - - - "ሁሉንም ድገም" - "ምንም አትድገም" - "አንዱን ድገም" + --> + + + "ምንም አትድገም" + "አንድ ድገም" + "ሁሉንም ድገም" diff --git a/extensions/mediasession/src/main/res/values-ar/strings.xml b/extensions/mediasession/src/main/res/values-ar/strings.xml index 0101a746e0..2776e28356 100644 --- a/extensions/mediasession/src/main/res/values-ar/strings.xml +++ b/extensions/mediasession/src/main/res/values-ar/strings.xml @@ -1,6 +1,5 @@ - - - - "تكرار الكل" - "عدم التكرار" - "تكرار مقطع واحد" + --> + + + "عدم التكرار" + "تكرار مقطع صوتي واحد" + "تكرار الكل" diff --git a/extensions/mediasession/src/main/res/values-b+sr+Latn/strings.xml b/extensions/mediasession/src/main/res/values-b+sr+Latn/strings.xml index 67a51cf85e..d20b16531a 100644 --- a/extensions/mediasession/src/main/res/values-b+sr+Latn/strings.xml +++ b/extensions/mediasession/src/main/res/values-b+sr+Latn/strings.xml @@ -1,6 +1,5 @@ - - - - "Ponovi sve" - "Ne ponavljaj nijednu" - "Ponovi jednu" + --> + + + "Ne ponavljaj nijednu" + "Ponovi jednu" + "Ponovi sve" diff --git a/extensions/mediasession/src/main/res/values-bg/strings.xml b/extensions/mediasession/src/main/res/values-bg/strings.xml index 16910d640a..087eaee8c2 100644 --- a/extensions/mediasession/src/main/res/values-bg/strings.xml +++ b/extensions/mediasession/src/main/res/values-bg/strings.xml @@ -1,6 +1,5 @@ - - - - "Повтаряне на всички" - "Без повтаряне" - "Повтаряне на един елемент" + --> + + + "Без повтаряне" + "Повтаряне на един елемент" + "Повтаряне на всички" diff --git a/extensions/mediasession/src/main/res/values-ca/strings.xml b/extensions/mediasession/src/main/res/values-ca/strings.xml index 89414d736e..4a4d8646a2 100644 --- a/extensions/mediasession/src/main/res/values-ca/strings.xml +++ b/extensions/mediasession/src/main/res/values-ca/strings.xml @@ -1,6 +1,5 @@ - - - - "Repeteix-ho tot" - "No en repeteixis cap" - "Repeteix-ne un" + --> + + + "No en repeteixis cap" + "Repeteix una" + "Repeteix tot" diff --git a/extensions/mediasession/src/main/res/values-cs/strings.xml b/extensions/mediasession/src/main/res/values-cs/strings.xml index 784d872570..c59dcfc874 100644 --- a/extensions/mediasession/src/main/res/values-cs/strings.xml +++ b/extensions/mediasession/src/main/res/values-cs/strings.xml @@ -1,6 +1,5 @@ - - - - "Opakovat vše" - "Neopakovat" - "Opakovat jednu položku" + --> + + + "Neopakovat" + "Opakovat jednu" + "Opakovat vše" diff --git a/extensions/mediasession/src/main/res/values-da/strings.xml b/extensions/mediasession/src/main/res/values-da/strings.xml index 2c9784d122..0d31261f3d 100644 --- a/extensions/mediasession/src/main/res/values-da/strings.xml +++ b/extensions/mediasession/src/main/res/values-da/strings.xml @@ -1,6 +1,5 @@ - - - - "Gentag alle" - "Gentag ingen" - "Gentag en" + --> + + + "Gentag ingen" + "Gentag én" + "Gentag alle" diff --git a/extensions/mediasession/src/main/res/values-de/strings.xml b/extensions/mediasession/src/main/res/values-de/strings.xml index c11e449665..dfa86a54d4 100644 --- a/extensions/mediasession/src/main/res/values-de/strings.xml +++ b/extensions/mediasession/src/main/res/values-de/strings.xml @@ -1,6 +1,5 @@ - - - - "Alle wiederholen" - "Keinen Titel wiederholen" - "Einen Titel wiederholen" + --> + + + "Keinen wiederholen" + "Einen wiederholen" + "Alle wiederholen" diff --git a/extensions/mediasession/src/main/res/values-el/strings.xml b/extensions/mediasession/src/main/res/values-el/strings.xml index 6279af5d64..e73b24592e 100644 --- a/extensions/mediasession/src/main/res/values-el/strings.xml +++ b/extensions/mediasession/src/main/res/values-el/strings.xml @@ -1,6 +1,5 @@ - - - - "Επανάληψη όλων" - "Καμία επανάληψη" - "Επανάληψη ενός στοιχείου" + --> + + + "Καμία επανάληψη" + "Επανάληψη ενός κομματιού" + "Επανάληψη όλων" diff --git a/extensions/mediasession/src/main/res/values-en-rAU/strings.xml b/extensions/mediasession/src/main/res/values-en-rAU/strings.xml index a3fccf8b52..197222473d 100644 --- a/extensions/mediasession/src/main/res/values-en-rAU/strings.xml +++ b/extensions/mediasession/src/main/res/values-en-rAU/strings.xml @@ -1,6 +1,5 @@ - - - - "Repeat all" - "Repeat none" - "Repeat one" + --> + + + "Repeat none" + "Repeat one" + "Repeat all" diff --git a/extensions/mediasession/src/main/res/values-en-rGB/strings.xml b/extensions/mediasession/src/main/res/values-en-rGB/strings.xml index a3fccf8b52..197222473d 100644 --- a/extensions/mediasession/src/main/res/values-en-rGB/strings.xml +++ b/extensions/mediasession/src/main/res/values-en-rGB/strings.xml @@ -1,6 +1,5 @@ - - - - "Repeat all" - "Repeat none" - "Repeat one" + --> + + + "Repeat none" + "Repeat one" + "Repeat all" diff --git a/extensions/mediasession/src/main/res/values-en-rIN/strings.xml b/extensions/mediasession/src/main/res/values-en-rIN/strings.xml index a3fccf8b52..197222473d 100644 --- a/extensions/mediasession/src/main/res/values-en-rIN/strings.xml +++ b/extensions/mediasession/src/main/res/values-en-rIN/strings.xml @@ -1,6 +1,5 @@ - - - - "Repeat all" - "Repeat none" - "Repeat one" + --> + + + "Repeat none" + "Repeat one" + "Repeat all" diff --git a/extensions/mediasession/src/main/res/values-es-rUS/strings.xml b/extensions/mediasession/src/main/res/values-es-rUS/strings.xml index 0fe29d3d5a..192ad2f2ef 100644 --- a/extensions/mediasession/src/main/res/values-es-rUS/strings.xml +++ b/extensions/mediasession/src/main/res/values-es-rUS/strings.xml @@ -1,6 +1,5 @@ - - - - "Repetir todo" - "No repetir" - "Repetir uno" + --> + + + "No repetir" + "Repetir uno" + "Repetir todo" diff --git a/extensions/mediasession/src/main/res/values-es/strings.xml b/extensions/mediasession/src/main/res/values-es/strings.xml index 0fe29d3d5a..192ad2f2ef 100644 --- a/extensions/mediasession/src/main/res/values-es/strings.xml +++ b/extensions/mediasession/src/main/res/values-es/strings.xml @@ -1,6 +1,5 @@ - - - - "Repetir todo" - "No repetir" - "Repetir uno" + --> + + + "No repetir" + "Repetir uno" + "Repetir todo" diff --git a/extensions/mediasession/src/main/res/values-fa/strings.xml b/extensions/mediasession/src/main/res/values-fa/strings.xml index e37a08de64..42b1b14c90 100644 --- a/extensions/mediasession/src/main/res/values-fa/strings.xml +++ b/extensions/mediasession/src/main/res/values-fa/strings.xml @@ -1,6 +1,5 @@ - - - - "تکرار همه" - "تکرار هیچ‌کدام" - "یک‌بار تکرار" + --> + + + "تکرار هیچ‌کدام" + "یکبار تکرار" + "تکرار همه" diff --git a/extensions/mediasession/src/main/res/values-fi/strings.xml b/extensions/mediasession/src/main/res/values-fi/strings.xml index c920827976..68f1b6c93b 100644 --- a/extensions/mediasession/src/main/res/values-fi/strings.xml +++ b/extensions/mediasession/src/main/res/values-fi/strings.xml @@ -1,6 +1,5 @@ - - - - "Toista kaikki" - "Toista ei mitään" - "Toista yksi" + --> + + + "Ei uudelleentoistoa" + "Toista yksi uudelleen" + "Toista kaikki uudelleen" diff --git a/extensions/mediasession/src/main/res/values-fr-rCA/strings.xml b/extensions/mediasession/src/main/res/values-fr-rCA/strings.xml index c5191e74a9..62edf759bb 100644 --- a/extensions/mediasession/src/main/res/values-fr-rCA/strings.xml +++ b/extensions/mediasession/src/main/res/values-fr-rCA/strings.xml @@ -1,6 +1,5 @@ - - - - "Tout lire en boucle" - "Aucune répétition" - "Répéter un élément" + --> + + + "Ne rien lire en boucle" + "Lire une chanson en boucle" + "Tout lire en boucle" diff --git a/extensions/mediasession/src/main/res/values-fr/strings.xml b/extensions/mediasession/src/main/res/values-fr/strings.xml index 1d76358d1f..2ea8653e93 100644 --- a/extensions/mediasession/src/main/res/values-fr/strings.xml +++ b/extensions/mediasession/src/main/res/values-fr/strings.xml @@ -1,6 +1,5 @@ - - - - "Tout lire en boucle" - "Ne rien lire en boucle" - "Lire en boucle un élément" + --> + + + "Ne rien lire en boucle" + "Lire un titre en boucle" + "Tout lire en boucle" diff --git a/extensions/mediasession/src/main/res/values-hi/strings.xml b/extensions/mediasession/src/main/res/values-hi/strings.xml index 8ce336d5e5..79261e4e59 100644 --- a/extensions/mediasession/src/main/res/values-hi/strings.xml +++ b/extensions/mediasession/src/main/res/values-hi/strings.xml @@ -1,6 +1,5 @@ - - - - "सभी को दोहराएं" - "कुछ भी न दोहराएं" - "एक दोहराएं" + --> + + + "किसी को न दोहराएं" + "एक को दोहराएं" + "सभी को दोहराएं" diff --git a/extensions/mediasession/src/main/res/values-hr/strings.xml b/extensions/mediasession/src/main/res/values-hr/strings.xml index 9f995ec15b..81bb428528 100644 --- a/extensions/mediasession/src/main/res/values-hr/strings.xml +++ b/extensions/mediasession/src/main/res/values-hr/strings.xml @@ -1,6 +1,5 @@ - - - - "Ponovi sve" - "Bez ponavljanja" - "Ponovi jedno" + --> + + + "Bez ponavljanja" + "Ponovi jedno" + "Ponovi sve" diff --git a/extensions/mediasession/src/main/res/values-hu/strings.xml b/extensions/mediasession/src/main/res/values-hu/strings.xml index 2335ade72e..8e8369a61f 100644 --- a/extensions/mediasession/src/main/res/values-hu/strings.xml +++ b/extensions/mediasession/src/main/res/values-hu/strings.xml @@ -1,6 +1,5 @@ - - - - "Összes ismétlése" - "Nincs ismétlés" - "Egy ismétlése" + --> + + + "Nincs ismétlés" + "Egy szám ismétlése" + "Összes szám ismétlése" diff --git a/extensions/mediasession/src/main/res/values-in/strings.xml b/extensions/mediasession/src/main/res/values-in/strings.xml index 093a7f8576..a20a6362c8 100644 --- a/extensions/mediasession/src/main/res/values-in/strings.xml +++ b/extensions/mediasession/src/main/res/values-in/strings.xml @@ -1,6 +1,5 @@ - - - - "Ulangi Semua" - "Jangan Ulangi" - "Ulangi Satu" + --> + + + "Jangan ulangi" + "Ulangi 1" + "Ulangi semua" diff --git a/extensions/mediasession/src/main/res/values-it/strings.xml b/extensions/mediasession/src/main/res/values-it/strings.xml index c0682519f9..3a59bb5804 100644 --- a/extensions/mediasession/src/main/res/values-it/strings.xml +++ b/extensions/mediasession/src/main/res/values-it/strings.xml @@ -1,6 +1,5 @@ - - - - "Ripeti tutti" - "Non ripetere nessuno" - "Ripeti uno" + --> + + + "Non ripetere nulla" + "Ripeti uno" + "Ripeti tutto" diff --git a/extensions/mediasession/src/main/res/values-iw/strings.xml b/extensions/mediasession/src/main/res/values-iw/strings.xml index 5cf23d5a4c..f9eac73e59 100644 --- a/extensions/mediasession/src/main/res/values-iw/strings.xml +++ b/extensions/mediasession/src/main/res/values-iw/strings.xml @@ -1,6 +1,5 @@ - - - - "חזור על הכל" - "אל תחזור על כלום" - "חזור על פריט אחד" + --> + + + "אל תחזור על אף פריט" + "חזור על פריט אחד" + "חזור על הכול" diff --git a/extensions/mediasession/src/main/res/values-ja/strings.xml b/extensions/mediasession/src/main/res/values-ja/strings.xml index 6f543fbdee..bcfb6eb7c2 100644 --- a/extensions/mediasession/src/main/res/values-ja/strings.xml +++ b/extensions/mediasession/src/main/res/values-ja/strings.xml @@ -1,6 +1,5 @@ - - - - "全曲を繰り返し" - "繰り返しなし" - "1曲を繰り返し" + --> + + + "リピートなし" + "1 曲をリピート" + "全曲をリピート" diff --git a/extensions/mediasession/src/main/res/values-ko/strings.xml b/extensions/mediasession/src/main/res/values-ko/strings.xml index d269937771..7be13b133a 100644 --- a/extensions/mediasession/src/main/res/values-ko/strings.xml +++ b/extensions/mediasession/src/main/res/values-ko/strings.xml @@ -1,6 +1,5 @@ - - - - "전체 반복" - "반복 안함" - "한 항목 반복" + --> + + + "반복 안함" + "현재 미디어 반복" + "모두 반복" diff --git a/extensions/mediasession/src/main/res/values-lt/strings.xml b/extensions/mediasession/src/main/res/values-lt/strings.xml index ae8f1cf8c3..78d1753ed0 100644 --- a/extensions/mediasession/src/main/res/values-lt/strings.xml +++ b/extensions/mediasession/src/main/res/values-lt/strings.xml @@ -1,6 +1,5 @@ - - - - "Kartoti viską" - "Nekartoti nieko" - "Kartoti vieną" + --> + + + "Nekartoti nieko" + "Kartoti vieną" + "Kartoti viską" diff --git a/extensions/mediasession/src/main/res/values-lv/strings.xml b/extensions/mediasession/src/main/res/values-lv/strings.xml index a69f6a0ad5..085723a271 100644 --- a/extensions/mediasession/src/main/res/values-lv/strings.xml +++ b/extensions/mediasession/src/main/res/values-lv/strings.xml @@ -1,6 +1,5 @@ - - - - "Atkārtot visu" - "Neatkārtot nevienu" - "Atkārtot vienu" + --> + + + "Neatkārtot nevienu" + "Atkārtot vienu" + "Atkārtot visu" diff --git a/extensions/mediasession/src/main/res/values-nb/strings.xml b/extensions/mediasession/src/main/res/values-nb/strings.xml index 10f334b226..2e986733fc 100644 --- a/extensions/mediasession/src/main/res/values-nb/strings.xml +++ b/extensions/mediasession/src/main/res/values-nb/strings.xml @@ -1,6 +1,5 @@ - - - - "Gjenta alle" - "Ikke gjenta noen" - "Gjenta én" + --> + + + "Ikke gjenta noen" + "Gjenta én" + "Gjenta alle" diff --git a/extensions/mediasession/src/main/res/values-nl/strings.xml b/extensions/mediasession/src/main/res/values-nl/strings.xml index 55997be098..4dfc31bb98 100644 --- a/extensions/mediasession/src/main/res/values-nl/strings.xml +++ b/extensions/mediasession/src/main/res/values-nl/strings.xml @@ -1,6 +1,5 @@ - - - - "Alles herhalen" - "Niet herhalen" - "Eén herhalen" + --> + + + "Niets herhalen" + "Eén herhalen" + "Alles herhalen" diff --git a/extensions/mediasession/src/main/res/values-pl/strings.xml b/extensions/mediasession/src/main/res/values-pl/strings.xml index 6a52d58b63..37af4c1616 100644 --- a/extensions/mediasession/src/main/res/values-pl/strings.xml +++ b/extensions/mediasession/src/main/res/values-pl/strings.xml @@ -1,6 +1,5 @@ - - - - "Powtórz wszystkie" - "Nie powtarzaj" - "Powtórz jeden" + --> + + + "Nie powtarzaj" + "Powtórz jeden" + "Powtórz wszystkie" diff --git a/extensions/mediasession/src/main/res/values-pt-rPT/strings.xml b/extensions/mediasession/src/main/res/values-pt-rPT/strings.xml index efb8fc433f..43a4cd9e6a 100644 --- a/extensions/mediasession/src/main/res/values-pt-rPT/strings.xml +++ b/extensions/mediasession/src/main/res/values-pt-rPT/strings.xml @@ -1,6 +1,5 @@ - - - - "Repetir tudo" - "Não repetir" - "Repetir um" + --> + + + "Não repetir nenhum" + "Repetir um" + "Repetir tudo" diff --git a/extensions/mediasession/src/main/res/values-pt/strings.xml b/extensions/mediasession/src/main/res/values-pt/strings.xml index aadebbb3b0..4e7ce248cc 100644 --- a/extensions/mediasession/src/main/res/values-pt/strings.xml +++ b/extensions/mediasession/src/main/res/values-pt/strings.xml @@ -1,6 +1,5 @@ - - - - "Repetir tudo" - "Não repetir" - "Repetir uma" + --> + + + "Não repetir" + "Repetir uma" + "Repetir tudo" diff --git a/extensions/mediasession/src/main/res/values-ro/strings.xml b/extensions/mediasession/src/main/res/values-ro/strings.xml index f6aee447e5..9345a5df35 100644 --- a/extensions/mediasession/src/main/res/values-ro/strings.xml +++ b/extensions/mediasession/src/main/res/values-ro/strings.xml @@ -1,6 +1,5 @@ - - - - "Repetați toate" - "Repetați niciuna" - "Repetați unul" + --> + + + "Nu repetați niciunul" + "Repetați unul" + "Repetați-le pe toate" diff --git a/extensions/mediasession/src/main/res/values-ru/strings.xml b/extensions/mediasession/src/main/res/values-ru/strings.xml index 575ad9f930..8c52ea8395 100644 --- a/extensions/mediasession/src/main/res/values-ru/strings.xml +++ b/extensions/mediasession/src/main/res/values-ru/strings.xml @@ -1,6 +1,5 @@ - - - - "Повторять все" - "Не повторять" - "Повторять один элемент" + --> + + + "Не повторять" + "Повторять трек" + "Повторять все" diff --git a/extensions/mediasession/src/main/res/values-sk/strings.xml b/extensions/mediasession/src/main/res/values-sk/strings.xml index 5d092003e5..9a7cccd096 100644 --- a/extensions/mediasession/src/main/res/values-sk/strings.xml +++ b/extensions/mediasession/src/main/res/values-sk/strings.xml @@ -1,6 +1,5 @@ - - - - "Opakovať všetko" - "Neopakovať" - "Opakovať jednu položku" + --> + + + "Neopakovať" + "Opakovať jednu" + "Opakovať všetko" diff --git a/extensions/mediasession/src/main/res/values-sl/strings.xml b/extensions/mediasession/src/main/res/values-sl/strings.xml index ecac3800c8..7bf20baa19 100644 --- a/extensions/mediasession/src/main/res/values-sl/strings.xml +++ b/extensions/mediasession/src/main/res/values-sl/strings.xml @@ -1,6 +1,5 @@ - - - - "Ponovi vse" - "Ne ponovi" - "Ponovi eno" + --> + + + "Brez ponavljanja" + "Ponavljanje ene" + "Ponavljanje vseh" diff --git a/extensions/mediasession/src/main/res/values-sr/strings.xml b/extensions/mediasession/src/main/res/values-sr/strings.xml index 881cb2703b..b82940da2e 100644 --- a/extensions/mediasession/src/main/res/values-sr/strings.xml +++ b/extensions/mediasession/src/main/res/values-sr/strings.xml @@ -1,6 +1,5 @@ - - - + --> + + + "Не понављај ниједну" + "Понови једну" + "Понови све" diff --git a/extensions/mediasession/src/main/res/values-sv/strings.xml b/extensions/mediasession/src/main/res/values-sv/strings.xml index 3a7bb630aa..13edc46d1f 100644 --- a/extensions/mediasession/src/main/res/values-sv/strings.xml +++ b/extensions/mediasession/src/main/res/values-sv/strings.xml @@ -1,6 +1,5 @@ - - - - "Upprepa alla" - "Upprepa inga" - "Upprepa en" + --> + + + "Upprepa inga" + "Upprepa en" + "Upprepa alla" diff --git a/extensions/mediasession/src/main/res/values-sw/strings.xml b/extensions/mediasession/src/main/res/values-sw/strings.xml index 726012ab88..b40ce1a727 100644 --- a/extensions/mediasession/src/main/res/values-sw/strings.xml +++ b/extensions/mediasession/src/main/res/values-sw/strings.xml @@ -1,6 +1,5 @@ - - - - "Rudia zote" - "Usirudie Yoyote" - "Rudia Moja" + --> + + + "Usirudie yoyote" + "Rudia moja" + "Rudia zote" diff --git a/extensions/mediasession/src/main/res/values-th/strings.xml b/extensions/mediasession/src/main/res/values-th/strings.xml index af502b3a4c..4e40f559d0 100644 --- a/extensions/mediasession/src/main/res/values-th/strings.xml +++ b/extensions/mediasession/src/main/res/values-th/strings.xml @@ -1,6 +1,5 @@ - - - - "เล่นซ้ำทั้งหมด" - "ไม่เล่นซ้ำ" - "เล่นซ้ำรายการเดียว" + --> + + + "ไม่เล่นซ้ำ" + "เล่นซ้ำเพลงเดียว" + "เล่นซ้ำทั้งหมด" diff --git a/extensions/mediasession/src/main/res/values-tl/strings.xml b/extensions/mediasession/src/main/res/values-tl/strings.xml index 239972a4c7..4fff164f9f 100644 --- a/extensions/mediasession/src/main/res/values-tl/strings.xml +++ b/extensions/mediasession/src/main/res/values-tl/strings.xml @@ -1,6 +1,5 @@ - - - - "Ulitin Lahat" - "Walang Uulitin" - "Ulitin ang Isa" + --> + + + "Walang uulitin" + "Mag-ulit ng isa" + "Ulitin lahat" diff --git a/extensions/mediasession/src/main/res/values-tr/strings.xml b/extensions/mediasession/src/main/res/values-tr/strings.xml index 89a98b1ed9..f93fd7fc80 100644 --- a/extensions/mediasession/src/main/res/values-tr/strings.xml +++ b/extensions/mediasession/src/main/res/values-tr/strings.xml @@ -1,6 +1,5 @@ - - - - "Tümünü Tekrarla" - "Hiçbirini Tekrarlama" - "Birini Tekrarla" + --> + + + "Hiçbirini tekrarlama" + "Bir şarkıyı tekrarla" + "Tümünü tekrarla" diff --git a/extensions/mediasession/src/main/res/values-uk/strings.xml b/extensions/mediasession/src/main/res/values-uk/strings.xml index 4e1d25eb8a..fb9d000474 100644 --- a/extensions/mediasession/src/main/res/values-uk/strings.xml +++ b/extensions/mediasession/src/main/res/values-uk/strings.xml @@ -1,6 +1,5 @@ - - - - "Повторити все" - "Не повторювати" - "Повторити один елемент" + --> + + + "Не повторювати" + "Повторити 1" + "Повторити всі" diff --git a/extensions/mediasession/src/main/res/values-vi/strings.xml b/extensions/mediasession/src/main/res/values-vi/strings.xml index dabc9e05d5..379dc36ee6 100644 --- a/extensions/mediasession/src/main/res/values-vi/strings.xml +++ b/extensions/mediasession/src/main/res/values-vi/strings.xml @@ -1,6 +1,5 @@ - - - - "Lặp lại tất cả" - "Không lặp lại" - "Lặp lại một mục" + --> + + + "Không lặp lại" + "Lặp lại một" + "Lặp lại tất cả" diff --git a/extensions/mediasession/src/main/res/values-zh-rCN/strings.xml b/extensions/mediasession/src/main/res/values-zh-rCN/strings.xml index beb3403cb9..6917f75bf9 100644 --- a/extensions/mediasession/src/main/res/values-zh-rCN/strings.xml +++ b/extensions/mediasession/src/main/res/values-zh-rCN/strings.xml @@ -1,6 +1,5 @@ - - - - "重复播放全部" - "不重复播放" - "重复播放单个视频" + --> + + + "不重复播放" + "重复播放一项" + "全部重复播放" diff --git a/extensions/mediasession/src/main/res/values-zh-rHK/strings.xml b/extensions/mediasession/src/main/res/values-zh-rHK/strings.xml index 775cd6441c..b63f103e2a 100644 --- a/extensions/mediasession/src/main/res/values-zh-rHK/strings.xml +++ b/extensions/mediasession/src/main/res/values-zh-rHK/strings.xml @@ -1,6 +1,5 @@ - - - - "重複播放所有媒體項目" - "不重複播放任何媒體項目" - "重複播放一個媒體項目" + --> + + + "不重複播放" + "重複播放一個" + "全部重複播放" diff --git a/extensions/mediasession/src/main/res/values-zh-rTW/strings.xml b/extensions/mediasession/src/main/res/values-zh-rTW/strings.xml index d3789f4145..0a460b9e08 100644 --- a/extensions/mediasession/src/main/res/values-zh-rTW/strings.xml +++ b/extensions/mediasession/src/main/res/values-zh-rTW/strings.xml @@ -1,6 +1,5 @@ - - - - "重複播放所有媒體項目" - "不重複播放" - "重複播放單一媒體項目" + --> + + + "不重複播放" + "重複播放單一項目" + "重複播放所有項目" diff --git a/extensions/mediasession/src/main/res/values-zu/strings.xml b/extensions/mediasession/src/main/res/values-zu/strings.xml index 789b6fecb4..ccf8452d69 100644 --- a/extensions/mediasession/src/main/res/values-zu/strings.xml +++ b/extensions/mediasession/src/main/res/values-zu/strings.xml @@ -1,6 +1,5 @@ - - - - "Phinda konke" - "Ungaphindi lutho" - "Phida okukodwa" + --> + + + "Phinda okungekho" + "Phinda okukodwa" + "Phinda konke" diff --git a/library/ui/src/main/res/values-af/strings.xml b/library/ui/src/main/res/values-af/strings.xml index f1c45cd7f7..46484d96f7 100644 --- a/library/ui/src/main/res/values-af/strings.xml +++ b/library/ui/src/main/res/values-af/strings.xml @@ -1,6 +1,5 @@ - - - - "Vorige snit" - "Volgende snit" - "Wag" - "Speel" - "Stop" - "Spoel terug" - "Vinnig vorentoe" - "Herhaal alles" - "Herhaal niks" - "Herhaal een" - "Skommel" - Volskermmodus + --> + + + "Vorige snit" + "Volgende snit" + "Onderbreek" + "Speel" + "Stop" + "Spoel terug" + "Spoel vorentoe" + "Herhaal niks" + "Herhaal een" + "Herhaal alles" + "Skommel" + "Volskermmodus" + "Aflaai op waglys" + "Laai tans af" + "Aflaai is voltooi" + "Kon nie aflaai nie" diff --git a/library/ui/src/main/res/values-am/strings.xml b/library/ui/src/main/res/values-am/strings.xml index 14d3ff0242..a69d730fa7 100644 --- a/library/ui/src/main/res/values-am/strings.xml +++ b/library/ui/src/main/res/values-am/strings.xml @@ -1,6 +1,5 @@ - - - - "ቀዳሚ ትራክ" - "ቀጣይ ትራክ" - "ለአፍታ አቁም" - "አጫውት" - "አቁም" - "ወደኋላ አጠንጥን" - "በፍጥነት አሳልፍ" - "ሁሉንም ድገም" - "ምንም አትድገም" - "አንዱን ድገም" - "በው" - ባለ ሙሉ ማያ ገጽ ሁኔታ + --> + + + "ቀዳሚ ትራክ" + "ቀጣይ ትራክ" + "ላፍታ አቁም" + "አጫውት" + "አቁም" + "ወደኋላ መልስ" + "በፍጥነት አሳልፍ" + "ምንም አትድገም" + "አንድ ድገም" + "ሁሉንም ድገም" + "በውዝ" + "የሙሉ ማያ ሁነታ" + "ማውረድ ወረፋ ይዟል" + "በማውረድ ላይ" + "ማውረድ ተጠናቋል" + "ማውረድ አልተሳካም" diff --git a/library/ui/src/main/res/values-ar/strings.xml b/library/ui/src/main/res/values-ar/strings.xml index 2cc56abbfa..efc65b3dc0 100644 --- a/library/ui/src/main/res/values-ar/strings.xml +++ b/library/ui/src/main/res/values-ar/strings.xml @@ -1,6 +1,5 @@ - - - - "المقطع الصوتي السابق" - "المقطع الصوتي التالي" - "إيقاف مؤقت" - "تشغيل" - "إيقاف" - "إرجاع" - "تقديم سريع" - "تكرار الكل" - "عدم التكرار" - "تكرار مقطع واحد" - "ترتيب عشوائي" - وضع ملء الشاشة + --> + + + "المقطع الصوتي السابق" + "المقطع الصوتي التالي" + "إيقاف مؤقت" + "تشغيل" + "إيقاف" + "إرجاع" + "تقديم سريع" + "عدم التكرار" + "تكرار مقطع صوتي واحد" + "تكرار الكل" + "ترتيب عشوائي" + "وضع ملء الشاشة" + "التنزيل قيد الانتظار" + "تحميل" + "اكتمل التنزيل" + "تعذّر التنزيل" diff --git a/library/ui/src/main/res/values-b+sr+Latn/strings.xml b/library/ui/src/main/res/values-b+sr+Latn/strings.xml index a9d35e5cb6..3ae9cea4b4 100644 --- a/library/ui/src/main/res/values-b+sr+Latn/strings.xml +++ b/library/ui/src/main/res/values-b+sr+Latn/strings.xml @@ -1,6 +1,5 @@ - - - - "Prethodna pesma" - "Sledeća pesma" - "Pauza" - "Pusti" - "Zaustavi" - "Premotaj unazad" - "Premotaj unapred" - "Ponovi sve" - "Ne ponavljaj nijednu" - "Ponovi jednu" - "Pusti nasumično" + --> + + + "Prethodna pesma" + "Sledeća pesma" + "Pauziraj" + "Pusti" + "Zaustavi" + "Premotaj unazad" + "Premotaj unapred" + "Ne ponavljaj nijednu" + "Ponovi jednu" + "Ponovi sve" + "Pusti nasumično" + "Režim celog ekrana" + "Preuzimanje je na čekanju" + "Preuzimanje" + "Preuzimanje je završeno" + "Preuzimanje nije uspelo" diff --git a/library/ui/src/main/res/values-bg/strings.xml b/library/ui/src/main/res/values-bg/strings.xml index e350479788..2524ded55f 100644 --- a/library/ui/src/main/res/values-bg/strings.xml +++ b/library/ui/src/main/res/values-bg/strings.xml @@ -1,6 +1,5 @@ - - - - "Предишен запис" - "Следващ запис" - "Пауза" - "Пускане" - "Спиране" - "Превъртане назад" - "Превъртане напред" - "Повтаряне на всички" - "Без повтаряне" - "Повтаряне на един елемент" - "Разбъркване" + --> + + + "Предишен запис" + "Следващ запис" + "Поставяне на пауза" + "Възпроизвеждане" + "Спиране" + "Превъртане назад" + "Превъртане напред" + "Без повтаряне" + "Повтаряне на един елемент" + "Повтаряне на всички" + "Разбъркване" + "Режим на цял екран" + "Изтеглянето е в опашката" + "Изтегля се" + "Изтеглянето завърши" + "Изтеглянето не бе успешно" diff --git a/library/ui/src/main/res/values-ca/strings.xml b/library/ui/src/main/res/values-ca/strings.xml index fd76a8e08e..d62f14274a 100644 --- a/library/ui/src/main/res/values-ca/strings.xml +++ b/library/ui/src/main/res/values-ca/strings.xml @@ -1,6 +1,5 @@ - - - - "Ruta anterior" - "Ruta següent" - "Posa en pausa" - "Reprodueix" - "Atura" - "Rebobina" - "Avança ràpidament" - "Repeteix-ho tot" - "No en repeteixis cap" - "Repeteix-ne un" - "Reprodueix aleatòriament" + --> + + + "Pista anterior" + "Pista següent" + "Posa en pausa" + "Reprodueix" + "Atura" + "Rebobina" + "Avança ràpidament" + "No en repeteixis cap" + "Repeteix una" + "Repeteix tot" + "Reprodueix aleatòriament" + "Mode de pantalla completa" + "La baixada s\'ha posat a la cua" + "S\'està baixant" + "S\'ha completat la baixada" + "No s\'ha pogut baixar" diff --git a/library/ui/src/main/res/values-cs/strings.xml b/library/ui/src/main/res/values-cs/strings.xml index 087ab79c25..fd8c7115f0 100644 --- a/library/ui/src/main/res/values-cs/strings.xml +++ b/library/ui/src/main/res/values-cs/strings.xml @@ -1,6 +1,5 @@ - - - - "Předchozí skladba" - "Další skladba" - "Pozastavit" - "Přehrát" - "Zastavit" - "Přetočit zpět" - "Přetočit vpřed" - "Opakovat vše" - "Neopakovat" - "Opakovat jednu položku" - "Náhodně" + --> + + + "Předchozí skladba" + "Další skladba" + "Pozastavit" + "Přehrát" + "Zastavit" + "Přetočit zpět" + "Rychle vpřed" + "Neopakovat" + "Opakovat jednu" + "Opakovat vše" + "Náhodně" + "Režim celé obrazovky" + "Zařazeno do fronty stahování" + "Stahování" + "Stahování bylo dokončeno" + "Stažení se nezdařilo" diff --git a/library/ui/src/main/res/values-da/strings.xml b/library/ui/src/main/res/values-da/strings.xml index 0ae23ee288..a53e9d132c 100644 --- a/library/ui/src/main/res/values-da/strings.xml +++ b/library/ui/src/main/res/values-da/strings.xml @@ -1,6 +1,5 @@ - - - - "Forrige nummer" - "Næste nummer" - "Pause" - "Afspil" - "Stop" - "Spol tilbage" - "Spol frem" - "Gentag alle" - "Gentag ingen" - "Gentag en" - "Bland" + --> + + + "Afspil forrige" + "Afspil næste" + "Sæt på pause" + "Afspil" + "Stop" + "Spol tilbage" + "Spol frem" + "Gentag ingen" + "Gentag én" + "Gentag alle" + "Bland" + "Fuld skærm" + "Downloaden er i kø" + "Download" + "Downloaden er udført" + "Download mislykkedes" diff --git a/library/ui/src/main/res/values-de/strings.xml b/library/ui/src/main/res/values-de/strings.xml index b31ecc93e8..9850520d1f 100644 --- a/library/ui/src/main/res/values-de/strings.xml +++ b/library/ui/src/main/res/values-de/strings.xml @@ -1,6 +1,5 @@ - - - - "Vorheriger Titel" - "Nächster Titel" - "Pausieren" - "Wiedergabe" - "Beenden" - "Zurückspulen" - "Vorspulen" - "Alle wiederholen" - "Keinen Titel wiederholen" - "Einen Titel wiederholen" - "Zufallsmix" - Vollbildmodus + --> + + + "Vorheriger Titel" + "Nächster Titel" + "Pausieren" + "Wiedergeben" + "Beenden" + "Zurückspulen" + "Vorspulen" + "Keinen wiederholen" + "Einen wiederholen" + "Alle wiederholen" + "Zufallsmix" + "Vollbildmodus" + "Download in der Warteschlange" + "Wird heruntergeladen" + "Download abgeschlossen" + "Download fehlgeschlagen" diff --git a/library/ui/src/main/res/values-el/strings.xml b/library/ui/src/main/res/values-el/strings.xml index 9bc6a87889..ebfe7c0cb1 100644 --- a/library/ui/src/main/res/values-el/strings.xml +++ b/library/ui/src/main/res/values-el/strings.xml @@ -1,6 +1,5 @@ - - - - "Προηγούμενο κομμάτι" - "Επόμενο κομμάτι" - "Παύση" - "Αναπαραγωγή" - "Διακοπή" - "Επαναφορά" - "Γρήγορη προώθηση" - "Επανάληψη όλων" - "Καμία επανάληψη" - "Επανάληψη ενός στοιχείου" - "Τυχαία αναπαραγωγή" - Λειτουργία πλήρους οθόνης + --> + + + "Προηγούμενο κομμάτι" + "Επόμενο κομμάτι" + "Παύση" + "Αναπαραγωγή" + "Διακοπή" + "Επαναφορά" + "Γρήγορη προώθηση" + "Καμία επανάληψη" + "Επανάληψη ενός κομματιού" + "Επανάληψη όλων" + "Τυχαία αναπαραγωγή" + "Λειτουργία πλήρους οθόνης" + "Η λήψη προστέθηκε στην ουρά" + "Λήψη" + "Η λήψη ολοκληρώθηκε" + "Η λήψη απέτυχε" diff --git a/library/ui/src/main/res/values-en-rAU/strings.xml b/library/ui/src/main/res/values-en-rAU/strings.xml index 0b4c465853..6c2767cacb 100644 --- a/library/ui/src/main/res/values-en-rAU/strings.xml +++ b/library/ui/src/main/res/values-en-rAU/strings.xml @@ -1,6 +1,5 @@ - - - - "Previous track" - "Next track" - "Pause" - "Play" - "Stop" - "Rewind" - "Fast-forward" - "Repeat all" - "Repeat none" - "Repeat one" - "Shuffle" + --> + + + "Previous track" + "Next track" + "Pause" + "Play" + "Stop" + "Rewind" + "Fast-forward" + "Repeat none" + "Repeat one" + "Repeat all" + "Shuffle" + "Full-screen mode" + "Download queued" + "Downloading" + "Download completed" + "Download failed" diff --git a/library/ui/src/main/res/values-en-rGB/strings.xml b/library/ui/src/main/res/values-en-rGB/strings.xml index e80b2c70c6..6c2767cacb 100644 --- a/library/ui/src/main/res/values-en-rGB/strings.xml +++ b/library/ui/src/main/res/values-en-rGB/strings.xml @@ -1,6 +1,5 @@ - - - - "Previous track" - "Next track" - "Pause" - "Play" - "Stop" - "Rewind" - "Fast-forward" - "Repeat all" - "Repeat none" - "Repeat one" - "Shuffle" - Fullscreen mode + --> + + + "Previous track" + "Next track" + "Pause" + "Play" + "Stop" + "Rewind" + "Fast-forward" + "Repeat none" + "Repeat one" + "Repeat all" + "Shuffle" + "Full-screen mode" + "Download queued" + "Downloading" + "Download completed" + "Download failed" diff --git a/library/ui/src/main/res/values-en-rIN/strings.xml b/library/ui/src/main/res/values-en-rIN/strings.xml index e80b2c70c6..6c2767cacb 100644 --- a/library/ui/src/main/res/values-en-rIN/strings.xml +++ b/library/ui/src/main/res/values-en-rIN/strings.xml @@ -1,6 +1,5 @@ - - - - "Previous track" - "Next track" - "Pause" - "Play" - "Stop" - "Rewind" - "Fast-forward" - "Repeat all" - "Repeat none" - "Repeat one" - "Shuffle" - Fullscreen mode + --> + + + "Previous track" + "Next track" + "Pause" + "Play" + "Stop" + "Rewind" + "Fast-forward" + "Repeat none" + "Repeat one" + "Repeat all" + "Shuffle" + "Full-screen mode" + "Download queued" + "Downloading" + "Download completed" + "Download failed" diff --git a/library/ui/src/main/res/values-es-rUS/strings.xml b/library/ui/src/main/res/values-es-rUS/strings.xml index e6cf3fc6f2..6836cfb577 100644 --- a/library/ui/src/main/res/values-es-rUS/strings.xml +++ b/library/ui/src/main/res/values-es-rUS/strings.xml @@ -1,6 +1,5 @@ - - - - "Pista anterior" - "Siguiente pista" - "Pausar" - "Reproducir" - "Detener" - "Retroceder" - "Avanzar" - "Repetir todo" - "No repetir" - "Repetir uno" - "Reproducir aleatoriamente" + --> + + + "Pista anterior" + "Pista siguiente" + "Pausar" + "Reproducir" + "Detener" + "Retroceder" + "Avanzar" + "No repetir" + "Repetir uno" + "Repetir todo" + "Reproducir aleatoriamente" + "Modo de pantalla completa" + "Descarga en fila" + "Descargando" + "Se completó la descarga" + "No se pudo descargar" diff --git a/library/ui/src/main/res/values-es/strings.xml b/library/ui/src/main/res/values-es/strings.xml index 2029ab833e..ab71863ca2 100644 --- a/library/ui/src/main/res/values-es/strings.xml +++ b/library/ui/src/main/res/values-es/strings.xml @@ -1,6 +1,5 @@ - - - - "Canción anterior" - "Siguiente canción" - "Pausar" - "Reproducir" - "Detener" - "Rebobinar" - "Avance rápido" - "Repetir todo" - "No repetir" - "Repetir uno" - "Reproducción aleatoria" - Modo de pantalla completa + --> + + + "Pista anterior" + "Siguiente pista" + "Pausar" + "Reproducir" + "Detener" + "Rebobinar" + "Avanzar rápidamente" + "No repetir" + "Repetir uno" + "Repetir todo" + "Reproducir aleatoriamente" + "Modo de pantalla completa" + "Descarga en cola" + "Descarga de archivos" + "Descarga de archivos completado" + "No se ha podido descargar" diff --git a/library/ui/src/main/res/values-fa/strings.xml b/library/ui/src/main/res/values-fa/strings.xml index c2303a6e62..80c7340496 100644 --- a/library/ui/src/main/res/values-fa/strings.xml +++ b/library/ui/src/main/res/values-fa/strings.xml @@ -1,6 +1,5 @@ - - - - "آهنگ قبلی" - "آهنگ بعدی" - "مکث" - "پخش" - "توقف" - "عقب بردن" - "جلو بردن سریع" - "تکرار همه" - "تکرار هیچ‌کدام" - "یک‌بار تکرار" - "پخش درهم" - حالت تمام صفحه + --> + + + "آهنگ قبلی" + "آهنگ بعدی" + "مکث" + "پخش" + "توقف" + "عقب بردن" + "جلو بردن سریع" + "تکرار هیچ‌کدام" + "یکبار تکرار" + "تکرار همه" + "درهم" + "حالت تمام‌صفحه" + "درانتظار بارگیری" + "درحال بارگیری" + "بارگیری کامل شد" + "بارگیری نشد" diff --git a/library/ui/src/main/res/values-fi/strings.xml b/library/ui/src/main/res/values-fi/strings.xml index 92feb86683..4eed46ee78 100644 --- a/library/ui/src/main/res/values-fi/strings.xml +++ b/library/ui/src/main/res/values-fi/strings.xml @@ -1,6 +1,5 @@ - - - - "Edellinen raita" - "Seuraava raita" - "Tauko" - "Toista" - "Seis" - "Kelaa taakse" - "Kelaa eteen" - "Toista kaikki" - "Toista ei mitään" - "Toista yksi" - "Toista satunnaisesti" + --> + + + "Edellinen kappale" + "Seuraava kappale" + "Keskeytä" + "Toista" + "Lopeta" + "Kelaa taaksepäin" + "Kelaa eteenpäin" + "Ei uudelleentoistoa" + "Toista yksi uudelleen" + "Toista kaikki uudelleen" + "Satunnaistoisto" + "Koko näytön tila" + "Lataus jonossa" + "Ladataan" + "Lataus valmis" + "Lataus epäonnistui" diff --git a/library/ui/src/main/res/values-fr-rCA/strings.xml b/library/ui/src/main/res/values-fr-rCA/strings.xml index 45fc0a86f9..40258f6480 100644 --- a/library/ui/src/main/res/values-fr-rCA/strings.xml +++ b/library/ui/src/main/res/values-fr-rCA/strings.xml @@ -1,6 +1,5 @@ - - - - "Chanson précédente" - "Chanson suivante" - "Pause" - "Lecture" - "Arrêter" - "Reculer" - "Avance rapide" - "Tout lire en boucle" - "Aucune répétition" - "Répéter un élément" - "Lecture aléatoire" + --> + + + "Chanson précédente" + "Chanson suivante" + "Pause" + "Lire" + "Arrêter" + "Retour arrière" + "Avance rapide" + "Ne rien lire en boucle" + "Lire une chanson en boucle" + "Tout lire en boucle" + "Lecture aléatoire" + "Mode Plein écran" + "File d\'attente de télécharg." + "Téléchargement en cours…" + "Téléchargement terminé" + "Échec du téléchargement" diff --git a/library/ui/src/main/res/values-fr/strings.xml b/library/ui/src/main/res/values-fr/strings.xml index 6617fd6e6a..3641c218d9 100644 --- a/library/ui/src/main/res/values-fr/strings.xml +++ b/library/ui/src/main/res/values-fr/strings.xml @@ -1,6 +1,5 @@ - - - - "Piste précédente" - "Piste suivante" - "Interrompre" - "Lire" - "Arrêter" - "Retour arrière" - "Avance rapide" - "Tout lire en boucle" - "Ne rien lire en boucle" - "Lire en boucle un élément" - "Lire en mode aléatoire" - Mode plein écran + --> + + + "Titre précédent" + "Titre suivant" + "Pause" + "Lecture" + "Arrêter" + "Retour arrière" + "Avance rapide" + "Ne rien lire en boucle" + "Lire un titre en boucle" + "Tout lire en boucle" + "Aléatoire" + "Mode plein écran" + "Téléchargement en attente" + "Téléchargement…" + "Téléchargement terminé" + "Échec du téléchargement" diff --git a/library/ui/src/main/res/values-hi/strings.xml b/library/ui/src/main/res/values-hi/strings.xml index 6545307e8c..9f7ed0d171 100644 --- a/library/ui/src/main/res/values-hi/strings.xml +++ b/library/ui/src/main/res/values-hi/strings.xml @@ -1,6 +1,5 @@ - - - - "पिछला ट्रैक" - "अगला ट्रैक" - "रोकें" - "चलाएं" - "बंद करें" - "रिवाइंड करें" - "फ़ास्ट फ़ॉरवर्ड" - "सभी को दोहराएं" - "कुछ भी न दोहराएं" - "एक दोहराएं" - "शफ़ल करें" - पूर्ण-स्क्रीन मोड + --> + + + "पिछला ट्रैक" + "अगला ट्रैक" + "रोकें" + "चलाएं" + "बंद करें" + "पीछे ले जाएं" + "तेज़ी से आगे बढ़ाएं" + "किसी को न दोहराएं" + "एक को दोहराएं" + "सभी को दोहराएं" + "शफ़ल करें" + "फ़ुलस्क्रीन मोड" + "डाउनलोड को कतार में लगाया गया" + "डाउनलोड हो रहा है" + "डाउनलोड पूरा हुआ" + "डाउनलोड नहीं हो सका" diff --git a/library/ui/src/main/res/values-hr/strings.xml b/library/ui/src/main/res/values-hr/strings.xml index dd7423032b..aeae6d6507 100644 --- a/library/ui/src/main/res/values-hr/strings.xml +++ b/library/ui/src/main/res/values-hr/strings.xml @@ -1,6 +1,5 @@ - - - - "Prethodna pjesma" - "Sljedeća pjesma" - "Pauziraj" - "Reproduciraj" - "Zaustavi" - "Unatrag" - "Brzo unaprijed" - "Ponovi sve" - "Bez ponavljanja" - "Ponovi jedno" - "Reproduciraj nasumično" - Prikaz na cijelom zaslonu + --> + + + "Prethodni zapis" + "Sljedeći zapis" + "Pauza" + "Reproduciraj" + "Zaustavi" + "Unatrag" + "Brzo unaprijed" + "Bez ponavljanja" + "Ponovi jedno" + "Ponovi sve" + "Reproduciraj nasumično" + "Prikaz na cijelom zaslonu" + "Preuzimanje na čekanju" + "Preuzimanje datoteka" + "Preuzimanje je dovršeno" + "Preuzimanje nije uspjelo" diff --git a/library/ui/src/main/res/values-hu/strings.xml b/library/ui/src/main/res/values-hu/strings.xml index abd9f9cfac..b001182a35 100644 --- a/library/ui/src/main/res/values-hu/strings.xml +++ b/library/ui/src/main/res/values-hu/strings.xml @@ -1,6 +1,5 @@ - - - - "Előző szám" - "Következő szám" - "Szünet" - "Lejátszás" - "Leállítás" - "Visszatekerés" - "Előretekerés" - "Összes ismétlése" - "Nincs ismétlés" - "Egy ismétlése" - "Véletlenszerű lejátszás" - Teljes képernyős mód + --> + + + "Előző szám" + "Következő szám" + "Szüneteltetés" + "Lejátszás" + "Leállítás" + "Visszatekerés" + "Előretekerés" + "Nincs ismétlés" + "Egy szám ismétlése" + "Összes szám ismétlése" + "Keverés" + "Teljes képernyős mód" + "Letöltés várólistára helyezve" + "Letöltés folyamatban" + "A letöltés befejeződött" + "Nem sikerült a letöltés" diff --git a/library/ui/src/main/res/values-in/strings.xml b/library/ui/src/main/res/values-in/strings.xml index 09b05815e6..5bac891438 100644 --- a/library/ui/src/main/res/values-in/strings.xml +++ b/library/ui/src/main/res/values-in/strings.xml @@ -1,6 +1,5 @@ - - - - "Lagu sebelumnya" - "Lagu berikutnya" - "Jeda" - "Putar" - "Berhenti" - "Putar Ulang" - "Maju cepat" - "Ulangi Semua" - "Jangan Ulangi" - "Ulangi Satu" - "Acak" + --> + + + "Lagu sebelumnya" + "Lagu berikutnya" + "Jeda" + "Putar" + "Berhenti" + "Putar Ulang" + "Maju cepat" + "Jangan ulangi" + "Ulangi 1" + "Ulangi semua" + "Acak" + "Mode layar penuh" + "Download masih dalam antrean" + "Mendownload" + "Download selesai" + "Download gagal" diff --git a/library/ui/src/main/res/values-it/strings.xml b/library/ui/src/main/res/values-it/strings.xml index 3300224fbb..7ce5dc5f45 100644 --- a/library/ui/src/main/res/values-it/strings.xml +++ b/library/ui/src/main/res/values-it/strings.xml @@ -1,6 +1,5 @@ - - - - "Traccia precedente" - "Traccia successiva" - "Metti in pausa" - "Riproduci" - "Interrompi" - "Riavvolgi" - "Avanti veloce" - "Ripeti tutti" - "Non ripetere nessuno" - "Ripeti uno" - "Riproduci casualmente" - Modalità a schermo intero + --> + + + "Traccia precedente" + "Traccia successiva" + "Pausa" + "Riproduci" + "Interrompi" + "Riavvolgi" + "Avanti veloce" + "Non ripetere nulla" + "Ripeti uno" + "Ripeti tutto" + "Riproduzione casuale" + "Modalità a schermo intero" + "Download aggiunto alla coda" + "Download" + "Download completato" + "Download non riuscito" diff --git a/library/ui/src/main/res/values-iw/strings.xml b/library/ui/src/main/res/values-iw/strings.xml index dd973af50b..036a124adc 100644 --- a/library/ui/src/main/res/values-iw/strings.xml +++ b/library/ui/src/main/res/values-iw/strings.xml @@ -1,6 +1,5 @@ - - - - "הרצועה הקודמת" - "הרצועה הבאה" - "השהה" - "הפעל" - "הפסק" - "הרץ אחורה" - "הרץ קדימה" - "חזור על הכל" - "אל תחזור על כלום" - "חזור על פריט אחד" - "ערבב" + --> + + + "הרצועה הקודמת" + "הרצועה הבאה" + "השהיה" + "הפעלה" + "הפסקה" + "הרצה אחורה" + "הרצה קדימה" + "אל תחזור על אף פריט" + "חזור על פריט אחד" + "חזור על הכול" + "ערבוב" + "מצב מסך מלא" + "ההורדה עדיין לא התחילה" + "מתבצעת הורדה" + "ההורדה הושלמה" + "ההורדה לא הושלמה" diff --git a/library/ui/src/main/res/values-ja/strings.xml b/library/ui/src/main/res/values-ja/strings.xml index 2e0f23a78f..d388bf6f05 100644 --- a/library/ui/src/main/res/values-ja/strings.xml +++ b/library/ui/src/main/res/values-ja/strings.xml @@ -1,6 +1,5 @@ - - - - "前のトラック" - "次のトラック" - "一時停止" - "再生" - "停止" - "巻き戻し" - "早送り" - "全曲を繰り返し" - "繰り返しなし" - "1曲を繰り返し" - "シャッフル" - フルスクリーンモード + --> + + + "前のトラック" + "次のトラック" + "一時停止" + "再生" + "停止" + "巻き戻し" + "早送り" + "リピートなし" + "1 曲をリピート" + "全曲をリピート" + "シャッフル" + "全画面モード" + "ダウンロードを待機しています" + "ダウンロードしています" + "ダウンロードが完了しました" + "ダウンロードに失敗しました" diff --git a/library/ui/src/main/res/values-ko/strings.xml b/library/ui/src/main/res/values-ko/strings.xml index 32d3deeb9e..3acf944f22 100644 --- a/library/ui/src/main/res/values-ko/strings.xml +++ b/library/ui/src/main/res/values-ko/strings.xml @@ -1,6 +1,5 @@ - - - - "이전 트랙" - "다음 트랙" - "일시중지" - "재생" - "중지" - "되감기" - "빨리 감기" - "전체 반복" - "반복 안함" - "한 항목 반복" - "셔플" - 전체 화면 모드 + --> + + + "이전 트랙" + "다음 트랙" + "일시중지" + "재생" + "중지" + "되감기" + "빨리 감기" + "반복 안함" + "현재 미디어 반복" + "모두 반복" + "셔플" + "전체화면 모드" + "다운로드 대기 중" + "다운로드하는 중" + "다운로드 완료" + "다운로드 실패" diff --git a/library/ui/src/main/res/values-lt/strings.xml b/library/ui/src/main/res/values-lt/strings.xml index 091f2384b2..f8b5366eb5 100644 --- a/library/ui/src/main/res/values-lt/strings.xml +++ b/library/ui/src/main/res/values-lt/strings.xml @@ -1,6 +1,5 @@ - - - - "Ankstesnis takelis" - "Kitas takelis" - "Pristabdyti" - "Leisti" - "Stabdyti" - "Sukti atgal" - "Sukti pirmyn" - "Kartoti viską" - "Nekartoti nieko" - "Kartoti vieną" - "Maišyti" - Viso ekrano režimas + --> + + + "Ankstesnis takelis" + "Kitas takelis" + "Pristabdyti" + "Leisti" + "Sustabdyti" + "Sukti atgal" + "Sukti pirmyn" + "Nekartoti nieko" + "Kartoti vieną" + "Kartoti viską" + "Maišyti" + "Viso ekrano režimas" + "Atsisiunč. elem. laukia eilėje" + "Atsisiunčiama" + "Atsisiuntimo procesas baigtas" + "Nepavyko atsisiųsti" diff --git a/library/ui/src/main/res/values-lv/strings.xml b/library/ui/src/main/res/values-lv/strings.xml index af982587bf..3f5f696f86 100644 --- a/library/ui/src/main/res/values-lv/strings.xml +++ b/library/ui/src/main/res/values-lv/strings.xml @@ -1,6 +1,5 @@ - - - - "Iepriekšējais ieraksts" - "Nākamais ieraksts" - "Pārtraukt" - "Atskaņot" - "Apturēt" - "Attīt atpakaļ" - "Ātri patīt" - "Atkārtot visu" - "Neatkārtot nevienu" - "Atkārtot vienu" - "Atskaņot jauktā secībā" - Pilnekrāna režīms + --> + + + "Iepriekšējais ieraksts" + "Nākamais ieraksts" + "Pauzēt" + "Atskaņot" + "Apturēt" + "Attīt atpakaļ" + "Pārtīt uz priekšu" + "Neatkārtot nevienu" + "Atkārtot vienu" + "Atkārtot visu" + "Atskaņot jauktā secībā" + "Pilnekrāna režīms" + "Lejupielāde gaida rindā" + "Notiek lejupielāde" + "Lejupielāde ir pabeigta" + "Lejupielāde neizdevās" diff --git a/library/ui/src/main/res/values-nb/strings.xml b/library/ui/src/main/res/values-nb/strings.xml index 370c759b84..7e7d580e63 100644 --- a/library/ui/src/main/res/values-nb/strings.xml +++ b/library/ui/src/main/res/values-nb/strings.xml @@ -1,6 +1,5 @@ - - - - "Forrige spor" - "Neste spor" - "Sett på pause" - "Spill av" - "Stopp" - "Tilbakespoling" - "Fremoverspoling" - "Gjenta alle" - "Ikke gjenta noen" - "Gjenta én" - "Spill av i tilfeldig rekkefølge" + --> + + + "Forrige spor" + "Neste spor" + "Sett på pause" + "Spill av" + "Stopp" + "Spol tilbake" + "Spol forover" + "Ikke gjenta noen" + "Gjenta én" + "Gjenta alle" + "Tilfeldig rekkefølge" + "Fullskjermmodus" + "Nedlasting står i kø" + "Laster ned" + "Nedlastingen er fullført" + "Nedlastingen mislyktes" diff --git a/library/ui/src/main/res/values-nl/strings.xml b/library/ui/src/main/res/values-nl/strings.xml index a67ab2968c..ad24b79908 100644 --- a/library/ui/src/main/res/values-nl/strings.xml +++ b/library/ui/src/main/res/values-nl/strings.xml @@ -1,6 +1,5 @@ - - - - "Vorig nummer" - "Volgend nummer" - "Onderbreken" - "Afspelen" - "Stoppen" - "Terugspoelen" - "Vooruitspoelen" - "Alles herhalen" - "Niet herhalen" - "Eén herhalen" - "Shuffle" + --> + + + "Vorige track" + "Volgende track" + "Pauzeren" + "Afspelen" + "Stoppen" + "Terugspoelen" + "Vooruitspoelen" + "Niets herhalen" + "Eén herhalen" + "Alles herhalen" + "Shuffle" + "Modus \'Volledig scherm\'" + "Download in de wachtrij" + "Downloaden" + "Downloaden voltooid" + "Downloaden mislukt" diff --git a/library/ui/src/main/res/values-pl/strings.xml b/library/ui/src/main/res/values-pl/strings.xml index 981aa17543..45f930a18c 100644 --- a/library/ui/src/main/res/values-pl/strings.xml +++ b/library/ui/src/main/res/values-pl/strings.xml @@ -1,6 +1,5 @@ - - - - "Poprzedni utwór" - "Następny utwór" - "Wstrzymaj" - "Odtwórz" - "Zatrzymaj" - "Przewiń do tyłu" - "Przewiń do przodu" - "Powtórz wszystkie" - "Nie powtarzaj" - "Powtórz jeden" - "Odtwarzaj losowo" - Tryb pełnoekranowy + --> + + + "Poprzedni utwór" + "Następny utwór" + "Wstrzymaj" + "Odtwórz" + "Zatrzymaj" + "Przewiń do tyłu" + "Przewiń do przodu" + "Nie powtarzaj" + "Powtórz jeden" + "Powtórz wszystkie" + "Odtwarzanie losowe" + "Tryb pełnoekranowy" + "W kolejce pobierania" + "Pobieranie" + "Zakończono pobieranie" + "Nie udało się pobrać" diff --git a/library/ui/src/main/res/values-pt-rPT/strings.xml b/library/ui/src/main/res/values-pt-rPT/strings.xml index f0c3770c51..d728f17347 100644 --- a/library/ui/src/main/res/values-pt-rPT/strings.xml +++ b/library/ui/src/main/res/values-pt-rPT/strings.xml @@ -1,6 +1,5 @@ - - - - "Faixa anterior" - "Faixa seguinte" - "Interromper" - "Reproduzir" - "Parar" - "Rebobinar" - "Avançar" - "Repetir tudo" - "Não repetir" - "Repetir um" - "Reproduzir aleatoriamente" - Modo de ecrã inteiro + --> + + + "Faixa anterior" + "Faixa seguinte" + "Colocar em pausa" + "Reproduzir" + "Parar" + "Recuar" + "Avançar" + "Não repetir nenhum" + "Repetir um" + "Repetir tudo" + "Reproduzir aleatoriamente" + "Modo de ecrã inteiro" + "Transfer. em fila de espera" + "A transferir…" + "Transferência concluída" + "Falha na transferência" diff --git a/library/ui/src/main/res/values-pt/strings.xml b/library/ui/src/main/res/values-pt/strings.xml index 8441e4e1cc..90595f0f81 100644 --- a/library/ui/src/main/res/values-pt/strings.xml +++ b/library/ui/src/main/res/values-pt/strings.xml @@ -1,6 +1,5 @@ - - - - "Faixa anterior" - "Próxima faixa" - "Pausar" - "Reproduzir" - "Parar" - "Retroceder" - "Avançar" - "Repetir tudo" - "Não repetir" - "Repetir uma" - "Reproduzir aleatoriamente" + --> + + + "Faixa anterior" + "Próxima faixa" + "Pausar" + "Reproduzir" + "Parar" + "Retroceder" + "Avançar" + "Não repetir" + "Repetir uma" + "Repetir tudo" + "Aleatório" + "Modo de tela cheia" + "Item na fila de download" + "Fazendo download" + "Download concluído" + "Falha no download" diff --git a/library/ui/src/main/res/values-ro/strings.xml b/library/ui/src/main/res/values-ro/strings.xml index 6b8644e30a..2f173f53ff 100644 --- a/library/ui/src/main/res/values-ro/strings.xml +++ b/library/ui/src/main/res/values-ro/strings.xml @@ -1,6 +1,5 @@ - - - - "Melodia anterioară" - "Melodia următoare" - "Pauză" - "Redați" - "Opriți" - "Derulați" - "Derulați rapid înainte" - "Repetați toate" - "Repetați niciuna" - "Repetați unul" - "Redați aleatoriu" + --> + + + "Melodia anterioară" + "Următoarea înregistrare" + "Întrerupeți" + "Redați" + "Opriți" + "Derulați înapoi" + "Derulați rapid înainte" + "Nu repetați niciunul" + "Repetați unul" + "Repetați-le pe toate" + "Redați aleatoriu" + "Modul Ecran complet" + "Descărcarea este în lista de așteptare" + "Se descarcă" + "Descărcarea a fost finalizată" + "Descărcarea nu a reușit" diff --git a/library/ui/src/main/res/values-ru/strings.xml b/library/ui/src/main/res/values-ru/strings.xml index 51d11d6371..1daee396cc 100644 --- a/library/ui/src/main/res/values-ru/strings.xml +++ b/library/ui/src/main/res/values-ru/strings.xml @@ -1,6 +1,5 @@ - - - - "Предыдущий трек" - "Следующий трек" - "Приостановить" - "Воспроизвести" - "Остановить" - "Перемотать назад" - "Перемотать вперед" - "Повторять все" - "Не повторять" - "Повторять один элемент" - "Перемешать" + --> + + + "Предыдущий трек" + "Следующий трек" + "Приостановить" + "Воспроизвести" + "Остановить" + "Перемотать назад" + "Перемотать вперед" + "Не повторять" + "Повторять трек" + "Повторять все" + "Перемешать" + "Полноэкранный режим" + "В очереди на скачивание" + "Загрузка файлов" + "Скачивание завершено" + "Ошибка скачивания" diff --git a/library/ui/src/main/res/values-sk/strings.xml b/library/ui/src/main/res/values-sk/strings.xml index a289e89d34..15cc6ab00e 100644 --- a/library/ui/src/main/res/values-sk/strings.xml +++ b/library/ui/src/main/res/values-sk/strings.xml @@ -1,6 +1,5 @@ - - - - "Predchádzajúca stopa" - "Ďalšia stopa" - "Pozastaviť" - "Prehrať" - "Zastaviť" - "Pretočiť späť" - "Pretočiť dopredu" - "Opakovať všetko" - "Neopakovať" - "Opakovať jednu položku" - "Náhodne prehrávať" - Režim celej obrazovky + --> + + + "Predchádzajúca skladba" + "Ďalšia skladba" + "Pozastaviť" + "Prehrať" + "Zastaviť" + "Pretočiť späť" + "Pretočiť dopredu" + "Neopakovať" + "Opakovať jednu" + "Opakovať všetko" + "Náhodne prehrávať" + "Režim celej obrazovky" + "Sťahovanie je v poradí" + "Sťahuje sa" + "Sťahovanie bolo dokončené" + "Nepodarilo sa stiahnuť" diff --git a/library/ui/src/main/res/values-sl/strings.xml b/library/ui/src/main/res/values-sl/strings.xml index 8ed731b0d3..19ad13aa81 100644 --- a/library/ui/src/main/res/values-sl/strings.xml +++ b/library/ui/src/main/res/values-sl/strings.xml @@ -1,6 +1,5 @@ - - - - "Prejšnja skladba" - "Naslednja skladba" - "Zaustavi" - "Predvajaj" - "Ustavi" - "Previj nazaj" - "Previj naprej" - "Ponovi vse" - "Ne ponovi" - "Ponovi eno" - "Naključno predvajaj" + --> + + + "Prejšnja skladba" + "Naslednja skladba" + "Zaustavitev" + "Predvajanje" + "Ustavitev" + "Previjanje nazaj" + "Previjanje naprej" + "Brez ponavljanja" + "Ponavljanje ene" + "Ponavljanje vseh" + "Naključno predvajanje" + "Celozaslonski način" + "Prenos je v čakalni vrsti" + "Prenašanje" + "Prenos je končan" + "Prenos ni uspel" diff --git a/library/ui/src/main/res/values-sr/strings.xml b/library/ui/src/main/res/values-sr/strings.xml index 9cff134a61..71fbc7e5c2 100644 --- a/library/ui/src/main/res/values-sr/strings.xml +++ b/library/ui/src/main/res/values-sr/strings.xml @@ -1,6 +1,5 @@ - - - - "Претходна песма" - "Следећа песма" - "Пауза" - "Пусти" - "Заустави" - "Премотај уназад" - "Премотај унапред" - "Понови све" - "Понављање је искључено" - "Понови једну" - "Пусти насумично" - Режим целог екрана + --> + + + "Претходна песма" + "Следећа песма" + "Паузирај" + "Пусти" + "Заустави" + "Премотај уназад" + "Премотај унапред" + "Не понављај ниједну" + "Понови једну" + "Понови све" + "Пусти насумично" + "Режим целог екрана" + "Преузимање је на чекању" + "Преузимање" + "Преузимање је завршено" + "Преузимање није успело" diff --git a/library/ui/src/main/res/values-sv/strings.xml b/library/ui/src/main/res/values-sv/strings.xml index b8fc7a1fff..1f0793ed23 100644 --- a/library/ui/src/main/res/values-sv/strings.xml +++ b/library/ui/src/main/res/values-sv/strings.xml @@ -1,6 +1,5 @@ - - - - "Föregående spår" - "Nästa spår" - "Pausa" - "Spela upp" - "Avbryt" - "Spola tillbaka" - "Snabbspola framåt" - "Upprepa alla" - "Upprepa inga" - "Upprepa en" - "Blanda" - Helskärmsläge + --> + + + "Föregående spår" + "Nästa spår" + "Pausa" + "Spela upp" + "Stoppa" + "Spola tillbaka" + "Snabbspola framåt" + "Upprepa inga" + "Upprepa en" + "Upprepa alla" + "Blanda spår" + "Helskärmsläge" + "Nedladdningen har köplacerats" + "Laddar ned" + "Nedladdningen är klar" + "Nedladdningen misslyckades" diff --git a/library/ui/src/main/res/values-sw/strings.xml b/library/ui/src/main/res/values-sw/strings.xml index 4451ad3c2b..eddaaf8264 100644 --- a/library/ui/src/main/res/values-sw/strings.xml +++ b/library/ui/src/main/res/values-sw/strings.xml @@ -1,6 +1,5 @@ - - - - "Wimbo uliotangulia" - "Wimbo unaofuata" - "Sitisha" - "Cheza" - "Simamisha" - "Rudisha nyuma" - "Peleka mbele kwa kasi" - "Rudia zote" - "Usirudie Yoyote" - "Rudia Moja" - "Changanya" - Hali ya skrini kamili + --> + + + "Wimbo uliotangulia" + "Wimbo unaofuata" + "Sitisha" + "Cheza" + "Simamisha" + "Rudisha nyuma" + "Sogeza mbele haraka" + "Usirudie yoyote" + "Rudia moja" + "Rudia zote" + "Changanya" + "Hali ya skrini nzima" + "Inasubiri kupakuliwa" + "Inapakua" + "Imepakuliwa" + "Imeshindwa kupakua" diff --git a/library/ui/src/main/res/values-th/strings.xml b/library/ui/src/main/res/values-th/strings.xml index 664900e7da..d235e0349a 100644 --- a/library/ui/src/main/res/values-th/strings.xml +++ b/library/ui/src/main/res/values-th/strings.xml @@ -1,6 +1,5 @@ - - - - "แทร็กก่อนหน้า" - "แทร็กถัดไป" - "หยุดชั่วคราว" - "เล่น" - "หยุด" - "กรอกลับ" - "กรอไปข้างหน้า" - "เล่นซ้ำทั้งหมด" - "ไม่เล่นซ้ำ" - "เล่นซ้ำรายการเดียว" - "สุ่มเพลง" - โหมดเต็มหน้าจอ + --> + + + "แทร็กก่อนหน้า" + "แทร็กถัดไป" + "หยุด" + "เล่น" + "หยุด" + "กรอกลับ" + "กรอไปข้างหน้า" + "ไม่เล่นซ้ำ" + "เล่นซ้ำเพลงเดียว" + "เล่นซ้ำทั้งหมด" + "สุ่ม" + "โหมดเต็มหน้าจอ" + "การดาวน์โหลดอยู่ในคิว" + "กำลังดาวน์โหลด" + "การดาวน์โหลดเสร็จสมบูรณ์" + "การดาวน์โหลดล้มเหลว" diff --git a/library/ui/src/main/res/values-tl/strings.xml b/library/ui/src/main/res/values-tl/strings.xml index 471191a81a..b478e9d52f 100644 --- a/library/ui/src/main/res/values-tl/strings.xml +++ b/library/ui/src/main/res/values-tl/strings.xml @@ -1,6 +1,5 @@ - - - - "Nakaraang track" - "Susunod na track" - "I-pause" - "I-play" - "Ihinto" - "I-rewind" - "I-fast forward" - "Ulitin Lahat" - "Walang Uulitin" - "Ulitin ang Isa" - "I-shuffle" - Fullscreen mode + --> + + + "Nakaraang track" + "Susunod na track" + "I-pause" + "I-play" + "Ihinto" + "I-rewind" + "I-fast forward" + "Walang uulitin" + "Mag-ulit ng isa" + "Ulitin lahat" + "I-shuffle" + "Fullscreen mode" + "Naka-queue ang download" + "Nagda-download" + "Tapos na ang pag-download" + "Hindi na-download" diff --git a/library/ui/src/main/res/values-tr/strings.xml b/library/ui/src/main/res/values-tr/strings.xml index cd1bfc5444..e9251e4c79 100644 --- a/library/ui/src/main/res/values-tr/strings.xml +++ b/library/ui/src/main/res/values-tr/strings.xml @@ -1,6 +1,5 @@ - - - - "Önceki parça" - "Sonraki parça" - "Duraklat" - "Çal" - "Durdur" - "Geri sar" - "İleri sar" - "Tümünü Tekrarla" - "Hiçbirini Tekrarlama" - "Birini Tekrarla" - "Karıştır" + --> + + + "Önceki parça" + "Sonraki parça" + "Duraklat" + "Çal" + "Durdur" + "Geri sar" + "İleri sar" + "Hiçbirini tekrarlama" + "Birini tekrarla" + "Tümünü tekrarla" + "Karıştır" + "Tam ekran modu" + "İndirme işlemi sıraya alındı" + "İndiriliyor" + "İndirme işlemi tamamlandı" + "İndirilemedi" diff --git a/library/ui/src/main/res/values-uk/strings.xml b/library/ui/src/main/res/values-uk/strings.xml index 36bfca2a34..e6c2289828 100644 --- a/library/ui/src/main/res/values-uk/strings.xml +++ b/library/ui/src/main/res/values-uk/strings.xml @@ -1,6 +1,5 @@ - - - - "Попередня композиція" - "Наступна композиція" - "Пауза" - "Відтворити" - "Зупинити" - "Перемотати назад" - "Перемотати вперед" - "Повторити все" - "Не повторювати" - "Повторити один елемент" - "Перемішати" - Повноекранний режим + --> + + + "Попередня композиція" + "Наступна композиція" + "Призупинити" + "Відтворити" + "Припинити" + "Перемотати назад" + "Перемотати вперед" + "Не повторювати" + "Повторити 1" + "Повторити всі" + "Перемішати" + "Повноекранний режим" + "Завантаження розміщено в черзі" + "Завантажується" + "Завантаження завершено" + "Не вдалося завантажити" diff --git a/library/ui/src/main/res/values-vi/strings.xml b/library/ui/src/main/res/values-vi/strings.xml index 748de96949..939206b7b9 100644 --- a/library/ui/src/main/res/values-vi/strings.xml +++ b/library/ui/src/main/res/values-vi/strings.xml @@ -1,6 +1,5 @@ - - - - "Bản nhạc trước" - "Bản nhạc tiếp theo" - "Tạm dừng" - "Phát" - "Ngừng" - "Tua lại" - "Tua đi" - "Lặp lại tất cả" - "Không lặp lại" - "Lặp lại một mục" - "Trộn bài" - Chế độ toàn màn hình + --> + + + "Bản nhạc trước" + "Bản nhạc tiếp theo" + "Tạm dừng" + "Phát" + "Dừng" + "Tua lại" + "Tua đi" + "Không lặp lại" + "Lặp lại một" + "Lặp lại tất cả" + "Phát ngẫu nhiên" + "Chế độ toàn màn hình" + "Đã đưa tài nguyên đã tải xuống vào hàng đợi" + "Đang tải xuống" + "Đã hoàn tất tải xuống" + "Không tải xuống được" diff --git a/library/ui/src/main/res/values-zh-rCN/strings.xml b/library/ui/src/main/res/values-zh-rCN/strings.xml index d357152a64..f2749db0af 100644 --- a/library/ui/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui/src/main/res/values-zh-rCN/strings.xml @@ -1,6 +1,5 @@ - - - - "上一曲" - "下一曲" - "暂停" - "播放" - "停止" - "快退" - "快进" - "重复播放全部" - "不重复播放" - "重复播放单个视频" - "随机播放" - 全屏模式 + --> + + + "上一曲" + "下一曲" + "暂停" + "播放" + "停止" + "快退" + "快进" + "不重复播放" + "重复播放一项" + "全部重复播放" + "随机播放" + "全屏模式" + "已加入待下载队列" + "正在下载" + "下载完毕" + "下载失败" diff --git a/library/ui/src/main/res/values-zh-rHK/strings.xml b/library/ui/src/main/res/values-zh-rHK/strings.xml index 3a26b8b5f0..fadf8bf976 100644 --- a/library/ui/src/main/res/values-zh-rHK/strings.xml +++ b/library/ui/src/main/res/values-zh-rHK/strings.xml @@ -1,6 +1,5 @@ - - - - "上一首曲目" - "下一首曲目" - "暫停" - "播放" - "停止" - "倒帶" - "向前快轉" - "重複播放所有媒體項目" - "不重複播放任何媒體項目" - "重複播放一個媒體項目" - "隨機播放" - 全螢幕模式 + --> + + + "上一首曲目" + "下一首曲目" + "暫停" + "播放" + "停止" + "倒轉" + "向前快轉" + "不重複播放" + "重複播放單一項目" + "全部重複播放" + "隨機播放" + "全螢幕模式" + "已加入下載列" + "正在下載" + "下載完畢" + "下載失敗" diff --git a/library/ui/src/main/res/values-zh-rTW/strings.xml b/library/ui/src/main/res/values-zh-rTW/strings.xml index 6f87d143ad..0679f360d1 100644 --- a/library/ui/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui/src/main/res/values-zh-rTW/strings.xml @@ -1,6 +1,5 @@ - - - - "上一首曲目" - "下一首曲目" - "暫停" - "播放" - "停止" - "倒轉" - "快轉" - "重複播放所有媒體項目" - "不重複播放" - "重複播放單一媒體項目" - "隨機播放" - 全螢幕模式 + --> + + + "上一首曲目" + "下一首曲目" + "暫停" + "播放" + "停止" + "倒轉" + "快轉" + "不重複播放" + "重複播放單一項目" + "重複播放所有項目" + "隨機播放" + "全螢幕模式" + "已排入下載佇列" + "下載中" + "下載完成" + "無法下載" diff --git a/library/ui/src/main/res/values-zu/strings.xml b/library/ui/src/main/res/values-zu/strings.xml index aff66ba0cf..5eb5bf9807 100644 --- a/library/ui/src/main/res/values-zu/strings.xml +++ b/library/ui/src/main/res/values-zu/strings.xml @@ -1,6 +1,5 @@ - - - - "Ithrekhi yangaphambilini" - "Ithrekhi elandelayo" - "Misa isikhashana" - "Dlala" - "Misa" - "Buyisela emumva" - "Ukudlulisa ngokushesha" - "Phinda konke" - "Ungaphindi lutho" - "Phida okukodwa" - "Shova" - Imodi yesikrini esiphelele + --> + + + "Ithrekhi yangaphambilini" + "Ithrekhi elandelayo" + "Phumula" + "Dlala" + "Misa" + "Buyisela emuva" + "Dlulisela phambili" + "Phinda okungekho" + "Phinda okukodwa" + "Phinda konke" + "Shova" + "Imodi yesikrini esigcwele" + "Ukulanda kukulayini" + "Iyalanda" + "Ukulanda kuqedile" + "Ukulanda kuhlulekile" From 4fec24294a4e192c3bb021ab14df9f05faa3a49b Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 13 Feb 2018 01:05:08 -0800 Subject: [PATCH 24/53] Fix handling of ad tags where ad groups are out of order IMA's cue points may not be in order, so sort them. It looks like IMA events use time ordered ad indices, so it is not necessary to map between the original cue point order and the time order. Issue: #3716 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185495798 --- RELEASENOTES.md | 2 ++ .../android/exoplayer2/ext/ima/ImaAdsLoader.java | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4c0354fad7..bc43e8f803 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -88,6 +88,8 @@ * Add support for playing non-Extractor content MediaSources in the IMA demo app ([#3676](https://github.com/google/ExoPlayer/issues/3676)). + * Fix handling of ad tags where ad groups are out of order + ([#3716](https://github.com/google/ExoPlayer/issues/3716)). * `EventLogger` moved from the demo app into the core library. * Fix ANR issue on Huawei P8 Lite ([#3724](https://github.com/google/ExoPlayer/issues/3724)). diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index d5e120afe7..b4bbded5b9 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -970,11 +970,17 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A int count = cuePoints.size(); long[] adGroupTimesUs = new long[count]; + int adGroupIndex = 0; for (int i = 0; i < count; i++) { double cuePoint = cuePoints.get(i); - adGroupTimesUs[i] = - cuePoint == -1.0 ? C.TIME_END_OF_SOURCE : (long) (C.MICROS_PER_SECOND * cuePoint); + if (cuePoint == -1.0) { + adGroupTimesUs[count - 1] = C.TIME_END_OF_SOURCE; + } else { + adGroupTimesUs[adGroupIndex++] = (long) (C.MICROS_PER_SECOND * cuePoint); + } } + // Cue points may be out of order, so sort them. + Arrays.sort(adGroupTimesUs, 0, adGroupIndex); return adGroupTimesUs; } From d4693980d6f62ab79713f4707e2c7d3d917c56b6 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 13 Feb 2018 01:58:41 -0800 Subject: [PATCH 25/53] Add Moto C+ to surface switching workaround. Issue:#3835 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185501181 --- .../android/exoplayer2/video/MediaCodecVideoRenderer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 f26db1054f..3fe863a96b 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 @@ -1091,7 +1091,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { && "OMX.qcom.video.decoder.avc".equals(name)) || (("tcl_eu".equals(Util.DEVICE) || "SVP-DTV15".equals(Util.DEVICE) - || "BRAVIA_ATV2".equals(Util.DEVICE)) + || "BRAVIA_ATV2".equals(Util.DEVICE) + || "panell_s".equals(Util.DEVICE)) && "OMX.MTK.VIDEO.DECODER.AVC".equals(name)) || ("OMX.k3.video.decoder.avc".equals(name) && "ALE-L21".equals(Util.MODEL)); } From 99e5a3c9b41a7942dcd62583765cb5ca9a7be0e5 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 13 Feb 2018 10:14:17 -0800 Subject: [PATCH 26/53] Add Y611 to setOutputSurfaceWorkaround Issue: #3724 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185548632 --- RELEASENOTES.md | 5 +++-- .../android/exoplayer2/video/MediaCodecVideoRenderer.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bc43e8f803..320751113b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -91,8 +91,9 @@ * Fix handling of ad tags where ad groups are out of order ([#3716](https://github.com/google/ExoPlayer/issues/3716)). * `EventLogger` moved from the demo app into the core library. -* Fix ANR issue on Huawei P8 Lite - ([#3724](https://github.com/google/ExoPlayer/issues/3724)). +* Fix ANR issue on the Huawei P8 Lite, Huawei Y6II and Moto C+ + ([#3724](https://github.com/google/ExoPlayer/issues/3724), + [#3835](https://github.com/google/ExoPlayer/issues/3835)). * Fix potential NPE when removing media sources from a DynamicConcatenatingMediaSource ([#3796](https://github.com/google/ExoPlayer/issues/3796)). 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 3fe863a96b..a897f3194c 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 @@ -1094,7 +1094,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { || "BRAVIA_ATV2".equals(Util.DEVICE) || "panell_s".equals(Util.DEVICE)) && "OMX.MTK.VIDEO.DECODER.AVC".equals(name)) - || ("OMX.k3.video.decoder.avc".equals(name) && "ALE-L21".equals(Util.MODEL)); + || (("ALE-L21".equals(Util.MODEL) || "CAM-L21".equals(Util.MODEL)) + && "OMX.k3.video.decoder.avc".equals(name)); } /** From 7b5bdd3c445c5a655c3873b9176f564dc979bed3 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 14 Feb 2018 00:41:53 -0800 Subject: [PATCH 27/53] Release Extractors on the loading thread Releasing the player released the internal playback thread once renderers were released. Releasing a MediaPeriod queued a Loader.ReleaseTask on the loading thread which would post back to the playback thread. If the playback thread had been quit by the time this happened, the release task wouldn't be run. Release on the loading thread instead of the playback thread. This avoids needing to block releasing the player until the loading threads have ended, and ensures that release tasks will run eventually. As part of this change, ExtractorMediaPeriod's call to Extractor.release will now run on the loading thread (which means that all Extractor methods are called on that thread) and other cleanup in ReleaseCallback will run on the loading thread instead of the playback thread. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185651320 --- RELEASENOTES.md | 3 ++ .../source/ExtractorMediaPeriod.java | 6 ++-- .../source/chunk/ChunkSampleStream.java | 19 +++++------- .../android/exoplayer2/upstream/Loader.java | 30 +++++-------------- .../source/dash/DashMediaPeriod.java | 7 +++-- .../source/hls/HlsSampleStreamWrapper.java | 4 +-- 6 files changed, 29 insertions(+), 40 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 320751113b..e452bd9cd2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -99,6 +99,9 @@ ([#3796](https://github.com/google/ExoPlayer/issues/3796)). * Check `sys.display-size` on Philips ATVs ([#3807](https://github.com/google/ExoPlayer/issues/3807)). +* Release `Extractor`s on the loading thread to avoid potentially leaking + resources when the playback thread has quit by the time the loading task has + completed. ### 2.6.1 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index f4f59115eb..c771188e3b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -179,24 +179,24 @@ import java.util.Arrays; } public void release() { - boolean releasedSynchronously = loader.release(this); - if (prepared && !releasedSynchronously) { + if (prepared) { // Discard as much as we can synchronously. We only do this if we're prepared, since otherwise // sampleQueues may still be being modified by the loading thread. for (SampleQueue sampleQueue : sampleQueues) { sampleQueue.discardToEnd(); } } + loader.release(this); handler.removeCallbacksAndMessages(null); released = true; } @Override public void onLoaderReleased() { - extractorHolder.release(); for (SampleQueue sampleQueue : sampleQueues) { sampleQueue.reset(); } + extractorHolder.release(); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index e0c5d35996..7096c84c5e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -73,7 +73,7 @@ public class ChunkSampleStream implements SampleStream, S private final BaseMediaChunkOutput mediaChunkOutput; private Format primaryDownstreamTrackFormat; - private ReleaseCallback releaseCallback; + private @Nullable ReleaseCallback releaseCallback; private long pendingResetPositionUs; private long lastSeekPositionUs; /* package */ long decodeOnlyUntilPositionUs; @@ -306,20 +306,17 @@ public class ChunkSampleStream implements SampleStream, S *

This method should be called when the stream is no longer required. Either this method or * {@link #release()} can be used to release this stream. * - * @param callback A callback to be called when the release ends. Will be called synchronously - * from this method if no load is in progress, or asynchronously once the load has been - * canceled otherwise. + * @param callback An optional callback to be called on the loading thread once the loader has + * been released. */ public void release(@Nullable ReleaseCallback callback) { this.releaseCallback = callback; - boolean releasedSynchronously = loader.release(this); - if (!releasedSynchronously) { - // Discard as much as we can synchronously. - primarySampleQueue.discardToEnd(); - for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { - embeddedSampleQueue.discardToEnd(); - } + // Discard as much as we can synchronously. + primarySampleQueue.discardToEnd(); + for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { + embeddedSampleQueue.discardToEnd(); } + loader.release(this); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java index 9e495f42bf..a118f10784 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.support.annotation.Nullable; import android.util.Log; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.TraceUtil; @@ -197,25 +198,17 @@ public final class Loader implements LoaderErrorThrower { * Releases the {@link Loader}. This method should be called when the {@link Loader} is no longer * required. * - * @param callback A callback to be called when the release ends. Will be called synchronously - * from this method if no load is in progress, or asynchronously once the load has been - * canceled otherwise. May be null. - * @return True if {@code callback} was called synchronously. False if it will be called - * asynchronously or if {@code callback} is null. + * @param callback An optional callback to be called on the loading thread once the loader has + * been released. */ - public boolean release(ReleaseCallback callback) { - boolean callbackInvoked = false; + public void release(@Nullable ReleaseCallback callback) { if (currentTask != null) { currentTask.cancel(true); - if (callback != null) { - downloadExecutorService.execute(new ReleaseTask(callback)); - } - } else if (callback != null) { - callback.onLoaderReleased(); - callbackInvoked = true; + } + if (callback != null) { + downloadExecutorService.execute(new ReleaseTask(callback)); } downloadExecutorService.shutdown(); - return callbackInvoked; } // LoaderErrorThrower implementation. @@ -419,7 +412,7 @@ public final class Loader implements LoaderErrorThrower { } - private static final class ReleaseTask extends Handler implements Runnable { + private static final class ReleaseTask implements Runnable { private final ReleaseCallback callback; @@ -429,13 +422,6 @@ public final class Loader implements LoaderErrorThrower { @Override public void run() { - if (getLooper().getThread().isAlive()) { - sendEmptyMessage(0); - } - } - - @Override - public void handleMessage(Message msg) { callback.onLoaderReleased(); } 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 d6d6ca821c..00baf15228 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 @@ -153,7 +153,7 @@ import java.util.List; // ChunkSampleStream.ReleaseCallback implementation. @Override - public void onSampleStreamReleased(ChunkSampleStream stream) { + public synchronized void onSampleStreamReleased(ChunkSampleStream stream) { PlayerTrackEmsgHandler trackEmsgHandler = trackEmsgHandlerBySampleStream.remove(stream); if (trackEmsgHandler != null) { trackEmsgHandler.release(); @@ -565,7 +565,10 @@ import java.util.List; positionUs, minLoadableRetryCount, eventDispatcher); - trackEmsgHandlerBySampleStream.put(stream, trackPlayerEmsgHandler); + synchronized (this) { + // The map is also accessed on the loading thread so synchronize access. + trackEmsgHandlerBySampleStream.put(stream, trackPlayerEmsgHandler); + } return stream; } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 508f2f0f2f..f027ba5b05 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -386,14 +386,14 @@ import java.util.Arrays; } public void release() { - boolean releasedSynchronously = loader.release(this); - if (prepared && !releasedSynchronously) { + if (prepared) { // Discard as much as we can synchronously. We only do this if we're prepared, since otherwise // sampleQueues may still be being modified by the loading thread. for (SampleQueue sampleQueue : sampleQueues) { sampleQueue.discardToEnd(); } } + loader.release(this); handler.removeCallbacksAndMessages(null); released = true; } From 4ca755a3449820566250f25ff264b39705f47d94 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 14 Feb 2018 01:35:45 -0800 Subject: [PATCH 28/53] Skip ads for which the media failed to prepare Also make ad group skipping more robust. After calling onError for an ad, IMA will sometimes trigger an ad group load error, so this needs to be handled in a way that allows some ads to be loaded already for the ad group. This change also fixes calculation of the expected ad index to take into account whether the position is being faked to trigger loading an ad or is the actual player position. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185655844 --- RELEASENOTES.md | 2 + .../exoplayer2/ext/ima/ImaAdsLoader.java | 89 +++++++++++++++---- .../source/DeferredMediaPeriod.java | 48 +++++++++- .../exoplayer2/source/ads/AdsLoader.java | 9 ++ .../exoplayer2/source/ads/AdsMediaSource.java | 23 +++++ 5 files changed, 151 insertions(+), 20 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e452bd9cd2..1003314ada 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -90,6 +90,8 @@ ([#3676](https://github.com/google/ExoPlayer/issues/3676)). * Fix handling of ad tags where ad groups are out of order ([#3716](https://github.com/google/ExoPlayer/issues/3716)). + * Propagate ad media preparation errors to IMA so that the ads can be + skipped. * `EventLogger` moved from the demo app into the core library. * Fix ANR issue on the Huawei P8 Lite, Huawei Y6II and Moto C+ ([#3724](https://github.com/google/ExoPlayer/issues/3724), diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index b4bbded5b9..d714eed98c 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -242,10 +242,15 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A */ private int playingAdIndexInAdGroup; /** - * If a content period has finished but IMA has not yet sent an ad event with - * {@link AdEvent.AdEventType#CONTENT_PAUSE_REQUESTED}, stores the value of - * {@link SystemClock#elapsedRealtime()} when the content stopped playing. This can be used to - * determine a fake, increasing content position. {@link C#TIME_UNSET} otherwise. + * Whether there's a pending ad preparation error which IMA needs to be notified of when it + * transitions from playing content to playing the ad. + */ + private boolean shouldNotifyAdPrepareError; + /** + * If a content period has finished but IMA has not yet sent an ad event with {@link + * AdEvent.AdEventType#CONTENT_PAUSE_REQUESTED}, stores the value of {@link + * SystemClock#elapsedRealtime()} when the content stopped playing. This can be used to determine + * a fake, increasing content position. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressElapsedRealtimeMs; /** @@ -432,6 +437,42 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A } } + @Override + public void handlePrepareError(int adGroupIndex, int adIndexInAdGroup, IOException exception) { + if (player == null) { + return; + } + if (DEBUG) { + Log.d( + TAG, "Prepare error for ad " + adIndexInAdGroup + " in group " + adGroupIndex, exception); + } + if (imaAdState == IMA_AD_STATE_NONE) { + // Send IMA a content position at the ad group so that it will try to play it, at which point + // we can notify that it failed to load. + fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); + fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); + if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { + fakeContentProgressOffsetMs = contentDurationMs; + } + shouldNotifyAdPrepareError = true; + } else { + // We're already playing an ad. + if (adIndexInAdGroup > playingAdIndexInAdGroup) { + // Mark the playing ad as ended so we can notify the error on the next ad and remove it, + // which means that the ad after will load (if any). + for (int i = 0; i < adCallbacks.size(); i++) { + adCallbacks.get(i).onEnded(); + } + } + playingAdIndexInAdGroup = adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay(); + for (int i = 0; i < adCallbacks.size(); i++) { + adCallbacks.get(i).onError(); + } + } + adPlaybackState = adPlaybackState.withAdLoadError(adGroupIndex, adIndexInAdGroup); + updateAdPlaybackState(); + } + // com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener implementation. @Override @@ -566,17 +607,21 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A if (pendingContentPositionMs != C.TIME_UNSET) { sentPendingContentPositionMs = true; contentPositionMs = pendingContentPositionMs; + expectedAdGroupIndex = + adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs)); } else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) { long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs; contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs; + expectedAdGroupIndex = + adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs)); } else if (imaAdState == IMA_AD_STATE_NONE && hasContentDuration) { contentPositionMs = player.getCurrentPosition(); + // Keep track of the ad group index that IMA will load for the current content position. + expectedAdGroupIndex = + adPlaybackState.getAdGroupIndexAfterPositionUs(C.msToUs(contentPositionMs)); } else { return VideoProgressUpdate.VIDEO_TIME_NOT_READY; } - // Keep track of the ad group index that IMA will load for the current content position. - expectedAdGroupIndex = - adPlaybackState.getAdGroupIndexAfterPositionUs(C.msToUs(contentPositionMs)); long contentDurationMs = hasContentDuration ? this.contentDurationMs : IMA_DURATION_UNSET; return new VideoProgressUpdate(contentPositionMs, contentDurationMs); } @@ -641,6 +686,12 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onPlay(); } + if (shouldNotifyAdPrepareError) { + shouldNotifyAdPrepareError = false; + for (int i = 0; i < adCallbacks.size(); i++) { + adCallbacks.get(i).onError(); + } + } break; case IMA_AD_STATE_PAUSED: imaAdState = IMA_AD_STATE_PLAYING; @@ -748,7 +799,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A @Override public void onPlayerError(ExoPlaybackException error) { - if (playingAd) { + if (imaAdState != IMA_AD_STATE_NONE) { for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onError(); } @@ -776,6 +827,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A if (newAdGroupIndex != C.INDEX_UNSET) { sentPendingContentPositionMs = false; pendingContentPositionMs = positionMs; + if (newAdGroupIndex != adGroupIndex) { + shouldNotifyAdPrepareError = false; + } } } } else { @@ -907,16 +961,19 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A int adGroupIndex = this.adGroupIndex == C.INDEX_UNSET ? expectedAdGroupIndex : this.adGroupIndex; AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; - // Ad group load error can be notified more than once, so check if it was already handled. - if (adGroup.count == C.LENGTH_UNSET - || adGroup.states[0] == AdPlaybackState.AD_STATE_UNAVAILABLE) { - if (DEBUG) { - Log.d(TAG, "Removing ad group " + adGroupIndex + " as it failed to load"); - } + if (adGroup.count == C.LENGTH_UNSET) { adPlaybackState = - adPlaybackState.withAdCount(adGroupIndex, 1).withAdLoadError(adGroupIndex, 0); - updateAdPlaybackState(); + adPlaybackState.withAdCount(adGroupIndex, Math.max(1, adGroup.states.length)); } + for (int i = 0; i < adGroup.count; i++) { + if (adGroup.states[i] == AdPlaybackState.AD_STATE_UNAVAILABLE) { + if (DEBUG) { + Log.d(TAG, "Removing ad " + i + " in ad group " + adGroupIndex); + } + adPlaybackState = adPlaybackState.withAdLoadError(adGroupIndex, i); + } + } + updateAdPlaybackState(); } private void checkForContentComplete() { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java index 1895f10d53..e13a563d50 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.trackselection.TrackSelection; @@ -29,6 +30,15 @@ import java.io.IOException; */ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callback { + /** Listener for preparation errors. */ + public interface PrepareErrorListener { + + /** + * Called the first time an error occurs while refreshing source info or preparing the period. + */ + void onPrepareError(IOException exception); + } + public final MediaSource mediaSource; private final MediaPeriodId id; @@ -37,13 +47,33 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb private MediaPeriod mediaPeriod; private Callback callback; private long preparePositionUs; + private @Nullable PrepareErrorListener listener; + private boolean notifiedPrepareError; + /** + * Creates a new deferred media period. + * + * @param mediaSource The media source to wrap. + * @param id The identifier for the media period to create when {@link #createPeriod()} is called. + * @param allocator The allocator used to create the media period. + */ public DeferredMediaPeriod(MediaSource mediaSource, MediaPeriodId id, Allocator allocator) { this.id = id; this.allocator = allocator; this.mediaSource = mediaSource; } + /** + * Sets a listener for preparation errors. + * + * @param listener An listener to be notified of media period preparation errors. If a listener is + * set, {@link #maybeThrowPrepareError()} will not throw but will instead pass the first + * preparation error (if any) to the listener. + */ + public void setPrepareErrorListener(PrepareErrorListener listener) { + this.listener = listener; + } + /** * Calls {@link MediaSource#createPeriod(MediaPeriodId, Allocator)} on the wrapped source then * prepares it if {@link #prepare(Callback, long)} has been called. Call {@link #releasePeriod()} @@ -76,10 +106,20 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb @Override public void maybeThrowPrepareError() throws IOException { - if (mediaPeriod != null) { - mediaPeriod.maybeThrowPrepareError(); - } else { - mediaSource.maybeThrowSourceInfoRefreshError(); + try { + if (mediaPeriod != null) { + mediaPeriod.maybeThrowPrepareError(); + } else { + mediaSource.maybeThrowSourceInfoRefreshError(); + } + } catch (final IOException e) { + if (listener == null) { + throw e; + } + if (!notifiedPrepareError) { + notifiedPrepareError = true; + listener.onPrepareError(e); + } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java index c2dfd91301..91111ec0ea 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java @@ -103,4 +103,13 @@ public interface AdsLoader { */ void release(); + /** + * Notifies the ads loader that the player was not able to prepare media for a given ad. + * Implementations should update the ad playback state as the specified ad has failed to load. + * + * @param adGroupIndex The index of the ad group. + * @param adIndexInAdGroup The index of the ad in the ad group. + * @param exception The preparation error. + */ + void handlePrepareError(int adGroupIndex, int adIndexInAdGroup, IOException exception); } 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 9ddbac1007..8c4d85ff4c 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 @@ -236,6 +236,8 @@ public final class AdsMediaSource extends CompositeMediaSource { MediaSource mediaSource = adGroupMediaSources[adGroupIndex][adIndexInAdGroup]; DeferredMediaPeriod deferredMediaPeriod = new DeferredMediaPeriod(mediaSource, new MediaPeriodId(0), allocator); + deferredMediaPeriod.setPrepareErrorListener( + new AdPrepareErrorListener(adGroupIndex, adIndexInAdGroup)); List mediaPeriods = deferredMediaPeriodByAdMediaSource.get(mediaSource); if (mediaPeriods == null) { deferredMediaPeriod.createPeriod(); @@ -433,4 +435,25 @@ public final class AdsMediaSource extends CompositeMediaSource { } + private final class AdPrepareErrorListener implements DeferredMediaPeriod.PrepareErrorListener { + + private final int adGroupIndex; + private final int adIndexInAdGroup; + + public AdPrepareErrorListener(int adGroupIndex, int adIndexInAdGroup) { + this.adGroupIndex = adGroupIndex; + this.adIndexInAdGroup = adIndexInAdGroup; + } + + @Override + public void onPrepareError(final IOException exception) { + mainHandler.post( + new Runnable() { + @Override + public void run() { + adsLoader.handlePrepareError(adGroupIndex, adIndexInAdGroup, exception); + } + }); + } + } } From 1d7e8efbdf7226cd35113648cc4867f4cf2879d1 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 14 Feb 2018 02:55:37 -0800 Subject: [PATCH 29/53] Add Meizu M5C, Lenovo K4 Note, and Sony Xperia E5 to surface workaround. Also added comments for all existing devices for easier reference. Issue:#3835 Issue:#3236 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185661900 --- RELEASENOTES.md | 3 ++- .../video/MediaCodecVideoRenderer.java | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1003314ada..b4005269c4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -93,7 +93,8 @@ * Propagate ad media preparation errors to IMA so that the ads can be skipped. * `EventLogger` moved from the demo app into the core library. -* Fix ANR issue on the Huawei P8 Lite, Huawei Y6II and Moto C+ +* Fix ANR issue on the Huawei P8 Lite, Huawei Y6II, Moto C+, Meizu M5C, + Lenovo K4 Note and Sony Xperia E5. ([#3724](https://github.com/google/ExoPlayer/issues/3724), [#3835](https://github.com/google/ExoPlayer/issues/3835)). * Fix potential NPE when removing media sources from a 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 a897f3194c..9e6b0c0f3e 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 @@ -1085,16 +1085,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private static boolean codecNeedsSetOutputSurfaceWorkaround(String name) { // Work around https://github.com/google/ExoPlayer/issues/3236, // https://github.com/google/ExoPlayer/issues/3355, - // https://github.com/google/ExoPlayer/issues/3439 and - // https://github.com/google/ExoPlayer/issues/3724. - return (("deb".equals(Util.DEVICE) || "flo".equals(Util.DEVICE)) + // https://github.com/google/ExoPlayer/issues/3439, + // https://github.com/google/ExoPlayer/issues/3724 and + // https://github.com/google/ExoPlayer/issues/3835. + return (("deb".equals(Util.DEVICE) || "flo".equals(Util.DEVICE)) // Nexus 7 (2013) && "OMX.qcom.video.decoder.avc".equals(name)) - || (("tcl_eu".equals(Util.DEVICE) - || "SVP-DTV15".equals(Util.DEVICE) - || "BRAVIA_ATV2".equals(Util.DEVICE) - || "panell_s".equals(Util.DEVICE)) + || (("tcl_eu".equals(Util.DEVICE) // TCL Percee TV + || "SVP-DTV15".equals(Util.DEVICE) // Sony Bravia 4K 2015 + || "BRAVIA_ATV2".equals(Util.DEVICE) // Sony Bravia 4K GB + || "panell_s".equals(Util.DEVICE) // Motorola Moto C Plus + || "F3311".equals(Util.DEVICE) // Sony Xperia E5 + || "M5c".equals(Util.DEVICE) // Meizu M5C + || "A7010a48".equals(Util.DEVICE)) // Lenovo K4 Note && "OMX.MTK.VIDEO.DECODER.AVC".equals(name)) - || (("ALE-L21".equals(Util.MODEL) || "CAM-L21".equals(Util.MODEL)) + || (("ALE-L21".equals(Util.MODEL) // Huawei P8 Lite + || "CAM-L21".equals(Util.MODEL)) // Huawei Y6II && "OMX.k3.video.decoder.avc".equals(name)); } From 5bd744000cde6dd6a9521f75d4824a00be046280 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 14 Feb 2018 03:13:09 -0800 Subject: [PATCH 30/53] Handle VAST_LINEAR_ASSET_MISMATCH This error marks the current ad group as unplayable. Issue: #3801 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185663472 --- RELEASENOTES.md | 2 ++ .../android/exoplayer2/ext/ima/ImaAdsLoader.java | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b4005269c4..0bec6891fe 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -85,6 +85,8 @@ ([#3584](https://github.com/google/ExoPlayer/issues/3584)). * Work around loadAd not being called beore the LOADED AdEvent arrives ([#3552](https://github.com/google/ExoPlayer/issues/3552)). + * Handle asset mismatch errors + ([#3801](https://github.com/google/ExoPlayer/issues/3801)). * Add support for playing non-Extractor content MediaSources in the IMA demo app ([#3676](https://github.com/google/ExoPlayer/issues/3676)). diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index d714eed98c..d11bc920e1 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -25,6 +25,8 @@ import android.view.ViewGroup; import android.webkit.WebView; import com.google.ads.interactivemedia.v3.api.Ad; import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; +import com.google.ads.interactivemedia.v3.api.AdError; +import com.google.ads.interactivemedia.v3.api.AdError.AdErrorCode; import com.google.ads.interactivemedia.v3.api.AdErrorEvent; import com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener; import com.google.ads.interactivemedia.v3.api.AdEvent; @@ -580,14 +582,17 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A @Override public void onAdError(AdErrorEvent adErrorEvent) { + AdError error = adErrorEvent.getError(); if (DEBUG) { - Log.d(TAG, "onAdError " + adErrorEvent); + Log.d(TAG, "onAdError", error); } if (adsManager == null) { // No ads were loaded, so allow playback to start without any ads. pendingAdRequestContext = null; adPlaybackState = new AdPlaybackState(); updateAdPlaybackState(); + } else if (isAdGroupLoadError(error)) { + handleAdGroupLoadError(); } if (pendingAdErrorEvent == null) { pendingAdErrorEvent = adErrorEvent; @@ -1041,4 +1046,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A return adGroupTimesUs; } + private static boolean isAdGroupLoadError(AdError adError) { + // TODO: Find out what other errors need to be handled (if any), and whether each one relates to + // a single ad, ad group or the whole timeline. + return adError.getErrorCode() == AdErrorCode.VAST_LINEAR_ASSET_MISMATCH; + } } From ed527437b7ea97bfb20bab719950c84b12a6e2e1 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 14 Feb 2018 04:25:44 -0800 Subject: [PATCH 31/53] Make ID3 GEOB frames parsing more robust Issue:#3792 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185668919 --- RELEASENOTES.md | 2 + .../exoplayer2/metadata/id3/Id3Decoder.java | 53 ++++++++++--------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0bec6891fe..78ea59a6bf 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -107,6 +107,8 @@ * Release `Extractor`s on the loading thread to avoid potentially leaking resources when the playback thread has quit by the time the loading task has completed. +* ID3: Better handle malformed ID3 data + ([#3792](https://github.com/google/ExoPlayer/issues/3792). ### 2.6.1 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 6b2e5c3675..7646af718d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -405,14 +405,9 @@ public final class Id3Decoder implements MetadataDecoder { int descriptionEndIndex = indexOfEos(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); - String value; int valueStartIndex = descriptionEndIndex + delimiterLength(encoding); - if (valueStartIndex < data.length) { - int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); - value = new String(data, valueStartIndex, valueEndIndex - valueStartIndex, charset); - } else { - value = ""; - } + int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); + String value = decodeStringIfValid(data, valueStartIndex, valueEndIndex, charset); return new TextInformationFrame("TXXX", description, value); } @@ -452,14 +447,9 @@ public final class Id3Decoder implements MetadataDecoder { int descriptionEndIndex = indexOfEos(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); - String url; int urlStartIndex = descriptionEndIndex + delimiterLength(encoding); - if (urlStartIndex < data.length) { - int urlEndIndex = indexOfZeroByte(data, urlStartIndex); - url = new String(data, urlStartIndex, urlEndIndex - urlStartIndex, "ISO-8859-1"); - } else { - url = ""; - } + int urlEndIndex = indexOfZeroByte(data, urlStartIndex); + String url = decodeStringIfValid(data, urlStartIndex, urlEndIndex, "ISO-8859-1"); return new UrlLinkFrame("WXXX", description, url); } @@ -502,13 +492,12 @@ public final class Id3Decoder implements MetadataDecoder { int filenameStartIndex = mimeTypeEndIndex + 1; int filenameEndIndex = indexOfEos(data, filenameStartIndex, encoding); - String filename = new String(data, filenameStartIndex, filenameEndIndex - filenameStartIndex, - charset); + String filename = decodeStringIfValid(data, filenameStartIndex, filenameEndIndex, charset); int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding); int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); - String description = new String(data, descriptionStartIndex, - descriptionEndIndex - descriptionStartIndex, charset); + String description = + decodeStringIfValid(data, descriptionStartIndex, descriptionEndIndex, charset); int objectDataStartIndex = descriptionEndIndex + delimiterLength(encoding); byte[] objectData = copyOfRangeIfValid(data, objectDataStartIndex, data.length); @@ -573,14 +562,9 @@ public final class Id3Decoder implements MetadataDecoder { int descriptionEndIndex = indexOfEos(data, 0, encoding); String description = new String(data, 0, descriptionEndIndex, charset); - String text; int textStartIndex = descriptionEndIndex + delimiterLength(encoding); - if (textStartIndex < data.length) { - int textEndIndex = indexOfEos(data, textStartIndex, encoding); - text = new String(data, textStartIndex, textEndIndex - textStartIndex, charset); - } else { - text = ""; - } + int textEndIndex = indexOfEos(data, textStartIndex, encoding); + String text = decodeStringIfValid(data, textStartIndex, textEndIndex, charset); return new CommentFrame(language, description, text); } @@ -760,6 +744,25 @@ public final class Id3Decoder implements MetadataDecoder { return Arrays.copyOfRange(data, from, to); } + /** + * Returns a string obtained by decoding the specified range of {@code data} using the specified + * {@code charsetName}. An empty string is returned if the range is invalid. + * + * @param data The array from which to decode the string. + * @param from The start of the range. + * @param to The end of the range (exclusive). + * @param charsetName The name of the Charset to use. + * @return The decoded string, or an empty string if the range is invalid. + * @throws UnsupportedEncodingException If the Charset is not supported. + */ + private static String decodeStringIfValid(byte[] data, int from, int to, String charsetName) + throws UnsupportedEncodingException { + if (to <= from || to > data.length) { + return ""; + } + return new String(data, from, to - from, charsetName); + } + private static final class Id3Header { private final int majorVersion; From 8dad8fde7801826f16c47f1d71305bbfc3ee3208 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 14 Feb 2018 05:26:28 -0800 Subject: [PATCH 32/53] Fix CeaUtil's invalid SeiMessage skipping ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185673454 --- .../java/com/google/android/exoplayer2/text/cea/CeaUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java index 0022d37d6c..67271ee218 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java @@ -52,7 +52,7 @@ public final class CeaUtil { if (payloadSize == -1 || payloadSize > seiBuffer.bytesLeft()) { // This might occur if we're trying to read an encrypted SEI NAL unit. Log.w(TAG, "Skipping remainder of malformed SEI NAL unit."); - seiBuffer.setPosition(seiBuffer.limit()); + nextPayloadPosition = seiBuffer.limit(); } else if (payloadType == PAYLOAD_TYPE_CC && payloadSize >= 8) { int countryCode = seiBuffer.readUnsignedByte(); int providerCode = seiBuffer.readUnsignedShort(); From 93e6813f7694512766aa35f8cc236b1195241e49 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 14 Feb 2018 05:31:02 -0800 Subject: [PATCH 33/53] Use stable order for subtitle buffers with identical timestamps Issue: #3782 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185673731 --- RELEASENOTES.md | 8 ++- .../exoplayer2/text/SubtitleInputBuffer.java | 20 +------- .../exoplayer2/text/cea/CeaDecoder.java | 50 +++++++++++++++---- .../exoplayer2/text/cea/CeaOutputBuffer.java | 40 --------------- 4 files changed, 49 insertions(+), 69 deletions(-) delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 78ea59a6bf..b2754c877b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -76,8 +76,12 @@ * Support resampling 24-bit and 32-bit integer to 32-bit float for high resolution output in `DefaultAudioSink` ([#3635](https://github.com/google/ExoPlayer/pull/3635)). -* Captions: Initial support for PGS subtitles - ([#3008](https://github.com/google/ExoPlayer/issues/3008)). +* Captions: + * Initial support for PGS subtitles + ([#3008](https://github.com/google/ExoPlayer/issues/3008)). + * Fix issue handling CEA-608 captions where multiple buffers have the same + presentation timestamp + ([#3782](https://github.com/google/ExoPlayer/issues/3782)). * CacheDataSource: Check periodically if it's possible to read from/write to cache after deciding to bypass cache. * IMA extension: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java index 4b3b61bddf..9866517a58 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java @@ -15,15 +15,11 @@ */ package com.google.android.exoplayer2.text; -import android.support.annotation.NonNull; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -/** - * A {@link DecoderInputBuffer} for a {@link SubtitleDecoder}. - */ -public final class SubtitleInputBuffer extends DecoderInputBuffer - implements Comparable { +/** A {@link DecoderInputBuffer} for a {@link SubtitleDecoder}. */ +public class SubtitleInputBuffer extends DecoderInputBuffer { /** * An offset that must be added to the subtitle's event times after it's been decoded, or @@ -35,16 +31,4 @@ public final class SubtitleInputBuffer extends DecoderInputBuffer super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); } - @Override - public int compareTo(@NonNull SubtitleInputBuffer other) { - if (isEndOfStream() != other.isEndOfStream()) { - return isEndOfStream() ? 1 : -1; - } - long delta = timeUs - other.timeUs; - if (delta == 0) { - return 0; - } - return delta > 0 ? 1 : -1; - } - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java index bb13a7d143..07a55f1a40 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.text.cea; +import android.support.annotation.NonNull; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.text.Subtitle; @@ -34,21 +35,22 @@ import java.util.PriorityQueue; private static final int NUM_INPUT_BUFFERS = 10; private static final int NUM_OUTPUT_BUFFERS = 2; - private final LinkedList availableInputBuffers; + private final LinkedList availableInputBuffers; private final LinkedList availableOutputBuffers; - private final PriorityQueue queuedInputBuffers; + private final PriorityQueue queuedInputBuffers; - private SubtitleInputBuffer dequeuedInputBuffer; + private CeaInputBuffer dequeuedInputBuffer; private long playbackPositionUs; + private long queuedInputBufferCount; public CeaDecoder() { availableInputBuffers = new LinkedList<>(); for (int i = 0; i < NUM_INPUT_BUFFERS; i++) { - availableInputBuffers.add(new SubtitleInputBuffer()); + availableInputBuffers.add(new CeaInputBuffer()); } availableOutputBuffers = new LinkedList<>(); for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) { - availableOutputBuffers.add(new CeaOutputBuffer(this)); + availableOutputBuffers.add(new CeaOutputBuffer()); } queuedInputBuffers = new PriorityQueue<>(); } @@ -77,9 +79,10 @@ import java.util.PriorityQueue; if (inputBuffer.isDecodeOnly()) { // We can drop this buffer early (i.e. before it would be decoded) as the CEA formats allow // for decoding to begin mid-stream. - releaseInputBuffer(inputBuffer); + releaseInputBuffer(dequeuedInputBuffer); } else { - queuedInputBuffers.add(inputBuffer); + dequeuedInputBuffer.queuedInputBufferCount = queuedInputBufferCount++; + queuedInputBuffers.add(dequeuedInputBuffer); } dequeuedInputBuffer = null; } @@ -94,7 +97,7 @@ import java.util.PriorityQueue; // be deferred until they would be applicable while (!queuedInputBuffers.isEmpty() && queuedInputBuffers.peek().timeUs <= playbackPositionUs) { - SubtitleInputBuffer inputBuffer = queuedInputBuffers.poll(); + CeaInputBuffer inputBuffer = queuedInputBuffers.poll(); // If the input buffer indicates we've reached the end of the stream, we can // return immediately with an output buffer propagating that @@ -126,7 +129,7 @@ import java.util.PriorityQueue; return null; } - private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) { + private void releaseInputBuffer(CeaInputBuffer inputBuffer) { inputBuffer.clear(); availableInputBuffers.add(inputBuffer); } @@ -138,6 +141,7 @@ import java.util.PriorityQueue; @Override public void flush() { + queuedInputBufferCount = 0; playbackPositionUs = 0; while (!queuedInputBuffers.isEmpty()) { releaseInputBuffer(queuedInputBuffers.poll()); @@ -169,4 +173,32 @@ import java.util.PriorityQueue; */ protected abstract void decode(SubtitleInputBuffer inputBuffer); + private static final class CeaInputBuffer extends SubtitleInputBuffer + implements Comparable { + + private long queuedInputBufferCount; + + @Override + public int compareTo(@NonNull CeaInputBuffer other) { + if (isEndOfStream() != other.isEndOfStream()) { + return isEndOfStream() ? 1 : -1; + } + long delta = timeUs - other.timeUs; + if (delta == 0) { + delta = queuedInputBufferCount - other.queuedInputBufferCount; + if (delta == 0) { + return 0; + } + } + return delta > 0 ? 1 : -1; + } + } + + private final class CeaOutputBuffer extends SubtitleOutputBuffer { + + @Override + public final void release() { + releaseOutputBuffer(this); + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java deleted file mode 100644 index 4cc32bb9e4..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2016 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.text.cea; - -import com.google.android.exoplayer2.text.SubtitleOutputBuffer; - -/** - * A {@link SubtitleOutputBuffer} for {@link CeaDecoder}s. - */ -public final class CeaOutputBuffer extends SubtitleOutputBuffer { - - private final CeaDecoder owner; - - /** - * @param owner The decoder that owns this buffer. - */ - public CeaOutputBuffer(CeaDecoder owner) { - super(); - this.owner = owner; - } - - @Override - public final void release() { - owner.releaseOutputBuffer(this); - } - -} From 0ae56cc117a26158ec81910b753591825dba1b0a Mon Sep 17 00:00:00 2001 From: eguven Date: Tue, 13 Feb 2018 07:18:07 -0800 Subject: [PATCH 34/53] Fix SmoothStreaming manifest url for downloading ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185526653 --- .../exoplayer2/source/dash/DashUtil.java | 6 ++-- .../source/hls/offline/HlsDownloader.java | 6 ++-- .../source/smoothstreaming/SsMediaSource.java | 6 ++-- .../smoothstreaming/manifest/SsUtil.java | 33 +++++++++++++++++++ .../smoothstreaming/offline/SsDownloader.java | 9 +++-- 5 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsUtil.java diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java index 57632225a5..2227044da7 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java @@ -53,10 +53,8 @@ public final class DashUtil { */ public static DashManifest loadManifest(DataSource dataSource, Uri uri) throws IOException { - DataSpec dataSpec = new DataSpec(uri, - DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH | DataSpec.FLAG_ALLOW_GZIP); - ParsingLoadable loadable = new ParsingLoadable<>(dataSource, dataSpec, - C.DATA_TYPE_MANIFEST, new DashManifestParser()); + ParsingLoadable loadable = + new ParsingLoadable<>(dataSource, uri, C.DATA_TYPE_MANIFEST, new DashManifestParser()); loadable.load(); return loadable.getResult(); } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java index a7bf35f2d1..3d14283e86 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java @@ -104,10 +104,8 @@ public final class HlsDownloader extends SegmentDownloader loadable = new ParsingLoadable<>(dataSource, dataSpec, - C.DATA_TYPE_MANIFEST, new HlsPlaylistParser()); + ParsingLoadable loadable = + new ParsingLoadable<>(dataSource, uri, C.DATA_TYPE_MANIFEST, new HlsPlaylistParser()); loadable.load(); return loadable.getResult(); } 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 c20ab6b6ae..da9024a5b5 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 @@ -36,13 +36,13 @@ import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; +import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; @@ -403,9 +403,7 @@ public final class SsMediaSource implements MediaSource, MediaSourceEventListener eventListener) { Assertions.checkState(manifest == null || !manifest.isLive); this.manifest = manifest; - this.manifestUri = manifestUri == null ? null - : Util.toLowerInvariant(manifestUri.getLastPathSegment()).matches("manifest(\\(.+\\))?") - ? manifestUri : Uri.withAppendedPath(manifestUri, "Manifest"); + this.manifestUri = manifestUri == null ? null : SsUtil.fixManifestUri(manifestUri); this.manifestDataSourceFactory = manifestDataSourceFactory; this.manifestParser = manifestParser; this.chunkSourceFactory = chunkSourceFactory; diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsUtil.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsUtil.java new file mode 100644 index 0000000000..4adf6acff7 --- /dev/null +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsUtil.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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.source.smoothstreaming.manifest; + +import android.net.Uri; +import com.google.android.exoplayer2.util.Util; + +/** SmoothStreaming related utility methods. */ +public final class SsUtil { + + /** Returns a fixed SmoothStreaming client manifest {@link Uri}. */ + public static Uri fixManifestUri(Uri manifestUri) { + if (Util.toLowerInvariant(manifestUri.getLastPathSegment()).matches("manifest(\\(.+\\))?")) { + return manifestUri; + } + return Uri.withAppendedPath(manifestUri, "Manifest"); + } + + private SsUtil() {} +} diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java index 5b97101fc6..12cfe2ee36 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.offline.SegmentDownloader; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; +import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil; import com.google.android.exoplayer2.source.smoothstreaming.manifest.TrackKey; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; @@ -64,7 +65,7 @@ public final class SsDownloader extends SegmentDownloader * @see SegmentDownloader#SegmentDownloader(Uri, DownloaderConstructorHelper) */ public SsDownloader(Uri manifestUri, DownloaderConstructorHelper constructorHelper) { - super(manifestUri, constructorHelper); + super(SsUtil.fixManifestUri(manifestUri), constructorHelper); } @Override @@ -82,10 +83,8 @@ public final class SsDownloader extends SegmentDownloader @Override protected SsManifest getManifest(DataSource dataSource, Uri uri) throws IOException { - DataSpec dataSpec = new DataSpec(uri, - DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH | DataSpec.FLAG_ALLOW_GZIP); - ParsingLoadable loadable = new ParsingLoadable<>(dataSource, dataSpec, - C.DATA_TYPE_MANIFEST, new SsManifestParser()); + ParsingLoadable loadable = + new ParsingLoadable<>(dataSource, uri, C.DATA_TYPE_MANIFEST, new SsManifestParser()); loadable.load(); return loadable.getResult(); } From 4276a199baf4f1b7fc1e8e9fad6f693bcac79809 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 14 Feb 2018 06:14:36 -0800 Subject: [PATCH 35/53] Add initial support for EXT-X-GAP ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185676652 --- RELEASENOTES.md | 1 + .../playlist/HlsMediaPlaylistParserTest.java | 158 +++++++++++------- .../exoplayer2/source/hls/HlsChunkSource.java | 26 ++- .../exoplayer2/source/hls/HlsMediaChunk.java | 10 +- .../source/hls/playlist/HlsMediaPlaylist.java | 25 ++- .../hls/playlist/HlsPlaylistParser.java | 27 ++- 6 files changed, 165 insertions(+), 82 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b2754c877b..a1ab55ebb0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -69,6 +69,7 @@ ([#3622](https://github.com/google/ExoPlayer/issues/3622)). * Use long for media sequence numbers ([#3747](https://github.com/google/ExoPlayer/issues/3747)) + * Add initial support for the EXT-X-GAP tag. * New Cast extension: Simplifies toggling between local and Cast playbacks. * Audio: * Support TrueHD passthrough for rechunked samples in Matroska files diff --git a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java index add631c39b..97a5386b04 100644 --- a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java @@ -33,7 +33,7 @@ import junit.framework.TestCase; */ public class HlsMediaPlaylistParserTest extends TestCase { - public void testParseMediaPlaylist() { + public void testParseMediaPlaylist() throws IOException { Uri playlistUri = Uri.parse("https://example.com/test.m3u8"); String playlistString = "#EXTM3U\n" + "#EXT-X-VERSION:3\n" @@ -69,76 +69,106 @@ public class HlsMediaPlaylistParserTest extends TestCase { + "#EXT-X-ENDLIST"; InputStream inputStream = new ByteArrayInputStream( playlistString.getBytes(Charset.forName(C.UTF8_NAME))); - try { - HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream); - assertThat(playlist).isNotNull(); + HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream); - HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; - assertThat(mediaPlaylist.playlistType).isEqualTo(HlsMediaPlaylist.PLAYLIST_TYPE_VOD); - assertThat(mediaPlaylist.startOffsetUs).isEqualTo(mediaPlaylist.durationUs - 25000000); + HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; + assertThat(mediaPlaylist.playlistType).isEqualTo(HlsMediaPlaylist.PLAYLIST_TYPE_VOD); + assertThat(mediaPlaylist.startOffsetUs).isEqualTo(mediaPlaylist.durationUs - 25000000); - assertThat(mediaPlaylist.mediaSequence).isEqualTo(2679); - assertThat(mediaPlaylist.version).isEqualTo(3); - assertThat(mediaPlaylist.hasEndTag).isTrue(); - List segments = mediaPlaylist.segments; - assertThat(segments).isNotNull(); - assertThat(segments).hasSize(5); + assertThat(mediaPlaylist.mediaSequence).isEqualTo(2679); + assertThat(mediaPlaylist.version).isEqualTo(3); + assertThat(mediaPlaylist.hasEndTag).isTrue(); + List segments = mediaPlaylist.segments; + assertThat(segments).isNotNull(); + assertThat(segments).hasSize(5); - Segment segment = segments.get(0); - assertThat(mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence) - .isEqualTo(4); - assertThat(segment.durationUs).isEqualTo(7975000); - assertThat(segment.fullSegmentEncryptionKeyUri).isNull(); - assertThat(segment.encryptionIV).isNull(); - assertThat(segment.byterangeLength).isEqualTo(51370); - assertThat(segment.byterangeOffset).isEqualTo(0); - assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2679.ts"); + Segment segment = segments.get(0); + assertThat(mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence) + .isEqualTo(4); + assertThat(segment.durationUs).isEqualTo(7975000); + assertThat(segment.fullSegmentEncryptionKeyUri).isNull(); + assertThat(segment.encryptionIV).isNull(); + assertThat(segment.byterangeLength).isEqualTo(51370); + assertThat(segment.byterangeOffset).isEqualTo(0); + assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2679.ts"); - segment = segments.get(1); - assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0); - assertThat(segment.durationUs).isEqualTo(7975000); - assertThat(segment.fullSegmentEncryptionKeyUri) - .isEqualTo("https://priv.example.com/key.php?r=2680"); - assertThat(segment.encryptionIV).isEqualTo("0x1566B"); - assertThat(segment.byterangeLength).isEqualTo(51501); - assertThat(segment.byterangeOffset).isEqualTo(2147483648L); - assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2680.ts"); + segment = segments.get(1); + assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0); + assertThat(segment.durationUs).isEqualTo(7975000); + assertThat(segment.fullSegmentEncryptionKeyUri) + .isEqualTo("https://priv.example.com/key.php?r=2680"); + assertThat(segment.encryptionIV).isEqualTo("0x1566B"); + assertThat(segment.byterangeLength).isEqualTo(51501); + assertThat(segment.byterangeOffset).isEqualTo(2147483648L); + assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2680.ts"); - segment = segments.get(2); - assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0); - assertThat(segment.durationUs).isEqualTo(7941000); - assertThat(segment.fullSegmentEncryptionKeyUri).isNull(); - assertThat(segment.encryptionIV).isEqualTo(null); - assertThat(segment.byterangeLength).isEqualTo(51501); - assertThat(segment.byterangeOffset).isEqualTo(2147535149L); - assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2681.ts"); + segment = segments.get(2); + assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0); + assertThat(segment.durationUs).isEqualTo(7941000); + assertThat(segment.fullSegmentEncryptionKeyUri).isNull(); + assertThat(segment.encryptionIV).isEqualTo(null); + assertThat(segment.byterangeLength).isEqualTo(51501); + assertThat(segment.byterangeOffset).isEqualTo(2147535149L); + assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2681.ts"); - segment = segments.get(3); - assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1); - assertThat(segment.durationUs).isEqualTo(7975000); - assertThat(segment.fullSegmentEncryptionKeyUri) - .isEqualTo("https://priv.example.com/key.php?r=2682"); - // 0xA7A == 2682. - assertThat(segment.encryptionIV).isNotNull(); - assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7A"); - assertThat(segment.byterangeLength).isEqualTo(51740); - assertThat(segment.byterangeOffset).isEqualTo(2147586650L); - assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2682.ts"); + segment = segments.get(3); + assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1); + assertThat(segment.durationUs).isEqualTo(7975000); + assertThat(segment.fullSegmentEncryptionKeyUri) + .isEqualTo("https://priv.example.com/key.php?r=2682"); + // 0xA7A == 2682. + assertThat(segment.encryptionIV).isNotNull(); + assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7A"); + assertThat(segment.byterangeLength).isEqualTo(51740); + assertThat(segment.byterangeOffset).isEqualTo(2147586650L); + assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2682.ts"); - segment = segments.get(4); - assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1); - assertThat(segment.durationUs).isEqualTo(7975000); - assertThat(segment.fullSegmentEncryptionKeyUri) - .isEqualTo("https://priv.example.com/key.php?r=2682"); - // 0xA7B == 2683. - assertThat(segment.encryptionIV).isNotNull(); - assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7B"); - assertThat(segment.byterangeLength).isEqualTo(C.LENGTH_UNSET); - assertThat(segment.byterangeOffset).isEqualTo(0); - assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts"); - } catch (IOException exception) { - fail(exception.getMessage()); - } + segment = segments.get(4); + assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1); + assertThat(segment.durationUs).isEqualTo(7975000); + assertThat(segment.fullSegmentEncryptionKeyUri) + .isEqualTo("https://priv.example.com/key.php?r=2682"); + // 0xA7B == 2683. + assertThat(segment.encryptionIV).isNotNull(); + assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7B"); + assertThat(segment.byterangeLength).isEqualTo(C.LENGTH_UNSET); + assertThat(segment.byterangeOffset).isEqualTo(0); + assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts"); + } + + public void testGapTag() throws IOException { + Uri playlistUri = Uri.parse("https://example.com/test2.m3u8"); + String playlistString = + "#EXTM3U\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-TARGETDURATION:5\n" + + "#EXT-X-PLAYLIST-TYPE:VOD\n" + + "#EXT-X-MEDIA-SEQUENCE:0\n" + + "#EXT-X-PROGRAM-DATE-TIME:2016-09-22T02:00:01+00:00\n" + + "#EXT-X-KEY:METHOD=AES-128,URI=\"https://example.com/key?value=something\"\n" + + "#EXTINF:5.005,\n" + + "02/00/27.ts\n" + + "#EXTINF:5.005,\n" + + "02/00/32.ts\n" + + "#EXT-X-KEY:METHOD=NONE\n" + + "#EXTINF:5.005,\n" + + "#EXT-X-GAP \n" + + "../dummy.ts\n" + + "#EXT-X-KEY:METHOD=AES-128,URI=\"https://key-service.bamgrid.com/1.0/key?" + + "hex-value=9FB8989D15EEAAF8B21B860D7ED3072A\",IV=0x410C8AC18AA42EFA18B5155484F5FC34\n" + + "#EXTINF:5.005,\n" + + "02/00/42.ts\n" + + "#EXTINF:5.005,\n" + + "02/00/47.ts\n"; + InputStream inputStream = + new ByteArrayInputStream(playlistString.getBytes(Charset.forName(C.UTF8_NAME))); + HlsMediaPlaylist playlist = + (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); + + assertThat(playlist.hasEndTag).isFalse(); + assertThat(playlist.segments.get(1).hasGapTag).isFalse(); + assertThat(playlist.segments.get(2).hasGapTag).isTrue(); + assertThat(playlist.segments.get(3).hasGapTag).isFalse(); } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index 04f61810bc..db0db47aee 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -330,11 +330,27 @@ import java.util.List; Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, null); - out.chunk = new HlsMediaChunk(extractorFactory, mediaDataSource, dataSpec, initDataSpec, - selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(), - trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs, - chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous, - mediaPlaylist.drmInitData, encryptionKey, encryptionIv); + out.chunk = + new HlsMediaChunk( + extractorFactory, + mediaDataSource, + dataSpec, + initDataSpec, + selectedUrl, + muxedCaptionFormats, + trackSelection.getSelectionReason(), + trackSelection.getSelectionData(), + startTimeUs, + startTimeUs + segment.durationUs, + chunkMediaSequence, + discontinuitySequence, + segment.hasGapTag, + isTimestampMaster, + timestampAdjuster, + previous, + mediaPlaylist.drmInitData, + encryptionKey, + encryptionIv); } /** diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 4be758993d..9e993aa27b 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -66,6 +66,7 @@ import java.util.concurrent.atomic.AtomicInteger; private final DataSpec initDataSpec; private final boolean isEncrypted; private final boolean isMasterTimestampSource; + private final boolean hasGapTag; private final TimestampAdjuster timestampAdjuster; private final boolean shouldSpliceIn; private final Extractor extractor; @@ -97,6 +98,7 @@ import java.util.concurrent.atomic.AtomicInteger; * @param endTimeUs The end time of the chunk in microseconds. * @param chunkMediaSequence The media sequence number of the chunk. * @param discontinuitySequenceNumber The discontinuity sequence number of the chunk. + * @param hasGapTag Whether the chunk is tagged with EXT-X-GAP. * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null. @@ -119,6 +121,7 @@ import java.util.concurrent.atomic.AtomicInteger; long endTimeUs, long chunkMediaSequence, int discontinuitySequenceNumber, + boolean hasGapTag, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, @@ -141,6 +144,7 @@ import java.util.concurrent.atomic.AtomicInteger; this.timestampAdjuster = timestampAdjuster; // Note: this.dataSource and dataSource may be different. this.isEncrypted = this.dataSource instanceof Aes128DataSource; + this.hasGapTag = hasGapTag; Extractor previousExtractor = null; if (previousChunk != null) { shouldSpliceIn = previousChunk.hlsUrl != hlsUrl; @@ -211,7 +215,10 @@ import java.util.concurrent.atomic.AtomicInteger; public void load() throws IOException, InterruptedException { maybeLoadInitData(); if (!loadCanceled) { - loadMedia(); + if (!hasGapTag) { + loadMedia(); + } + loadCompleted = true; } } @@ -283,7 +290,6 @@ import java.util.concurrent.atomic.AtomicInteger; } finally { Util.closeQuietly(dataSource); } - loadCompleted = true; } /** diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index 77a4c9ed1d..9a9517e2d4 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -69,8 +69,16 @@ public final class HlsMediaPlaylist extends HlsPlaylist { */ public final long byterangeLength; + /** Whether the segment is tagged with #EXT-X-GAP. */ + public final boolean hasGapTag; + + /** + * @param uri See {@link #url}. + * @param byterangeOffset See {@link #byterangeOffset}. + * @param byterangeLength See {@link #byterangeLength}. + */ public Segment(String uri, long byterangeOffset, long byterangeLength) { - this(uri, 0, -1, C.TIME_UNSET, null, null, byterangeOffset, byterangeLength); + this(uri, 0, -1, C.TIME_UNSET, null, null, byterangeOffset, byterangeLength, false); } /** @@ -82,10 +90,18 @@ public final class HlsMediaPlaylist extends HlsPlaylist { * @param encryptionIV See {@link #encryptionIV}. * @param byterangeOffset See {@link #byterangeOffset}. * @param byterangeLength See {@link #byterangeLength}. + * @param hasGapTag See {@link #hasGapTag}. */ - public Segment(String url, long durationUs, int relativeDiscontinuitySequence, - long relativeStartTimeUs, String fullSegmentEncryptionKeyUri, - String encryptionIV, long byterangeOffset, long byterangeLength) { + public Segment( + String url, + long durationUs, + int relativeDiscontinuitySequence, + long relativeStartTimeUs, + String fullSegmentEncryptionKeyUri, + String encryptionIV, + long byterangeOffset, + long byterangeLength, + boolean hasGapTag) { this.url = url; this.durationUs = durationUs; this.relativeDiscontinuitySequence = relativeDiscontinuitySequence; @@ -94,6 +110,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { this.encryptionIV = encryptionIV; this.byterangeOffset = byterangeOffset; this.byterangeLength = byterangeLength; + this.hasGapTag = hasGapTag; } @Override 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 4deddc1869..acd0746e72 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 @@ -67,6 +67,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser Date: Wed, 14 Feb 2018 07:03:40 -0800 Subject: [PATCH 36/53] Fix handling of ad tags with only preroll and postroll ads Content progress is only polled if there are midroll ad groups. If the ad tag had only preroll/postroll ads, the pending content position was not provided to IMA, which meant that the postroll ad could never play. Only set the pending content position if there are midroll ads. Issue: #3715 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185680803 --- RELEASENOTES.md | 2 ++ .../exoplayer2/ext/ima/ImaAdsLoader.java | 22 ++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a1ab55ebb0..7d15a0c70d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -97,6 +97,8 @@ ([#3676](https://github.com/google/ExoPlayer/issues/3676)). * Fix handling of ad tags where ad groups are out of order ([#3716](https://github.com/google/ExoPlayer/issues/3716)). + * Fix handling of ad tags with only preroll/postroll ad groups + ([#3715](https://github.com/google/ExoPlayer/issues/3715)). * Propagate ad media preparation errors to IMA so that the ads can be skipped. * `EventLogger` moved from the demo app into the core library. diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index d11bc920e1..0a79acb617 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -851,15 +851,14 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A adsRenderingSettings.setMimeTypes(supportedMimeTypes); // Set up the ad playback state, skipping ads based on the start position as required. - pendingContentPositionMs = player.getCurrentPosition(); long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints()); adPlaybackState = new AdPlaybackState(adGroupTimesUs); + long contentPositionMs = player.getCurrentPosition(); int adGroupIndexForPosition = - adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(pendingContentPositionMs)); + adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs)); if (adGroupIndexForPosition == 0) { podIndexOffset = 0; } else if (adGroupIndexForPosition == C.INDEX_UNSET) { - pendingContentPositionMs = C.TIME_UNSET; // There is no preroll and midroll pod indices start at 1. podIndexOffset = -1; } else /* adGroupIndexForPosition > 0 */ { @@ -879,6 +878,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A podIndexOffset = adGroupIndexForPosition - 1; } + if (hasMidrollAdGroups(adGroupTimesUs)) { + // IMA will poll the content position, so provide the player's initial position like a seek. + pendingContentPositionMs = contentPositionMs; + } + // Start ad playback. adsManager.init(adsRenderingSettings); updateAdPlaybackState(); @@ -1051,4 +1055,16 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A // a single ad, ad group or the whole timeline. return adError.getErrorCode() == AdErrorCode.VAST_LINEAR_ASSET_MISMATCH; } + + private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) { + int count = adGroupTimesUs.length; + if (count == 1) { + return adGroupTimesUs[0] != 0 && adGroupTimesUs[0] != C.TIME_END_OF_SOURCE; + } else if (count == 2) { + return adGroupTimesUs[0] != 0 || adGroupTimesUs[1] != C.TIME_END_OF_SOURCE; + } else { + // There's at least one midroll ad group, as adGroupTimesUs is never empty. + return true; + } + } } From bc792c3202d0bf75e972ed7b15b2e8e9d29090d6 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 14 Feb 2018 07:11:38 -0800 Subject: [PATCH 37/53] Broaden the surface switching workaround to other Moto C+ variants. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185681751 --- .../android/exoplayer2/video/MediaCodecVideoRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9e6b0c0f3e..62a7657ea7 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 @@ -1093,7 +1093,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { || (("tcl_eu".equals(Util.DEVICE) // TCL Percee TV || "SVP-DTV15".equals(Util.DEVICE) // Sony Bravia 4K 2015 || "BRAVIA_ATV2".equals(Util.DEVICE) // Sony Bravia 4K GB - || "panell_s".equals(Util.DEVICE) // Motorola Moto C Plus + || Util.DEVICE.startsWith("panell_") // Motorola Moto C Plus || "F3311".equals(Util.DEVICE) // Sony Xperia E5 || "M5c".equals(Util.DEVICE) // Meizu M5C || "A7010a48".equals(Util.DEVICE)) // Lenovo K4 Note From 1c140603d4bf6f51532734c52e44fa12bf7c5105 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 15 Feb 2018 00:44:09 -0800 Subject: [PATCH 38/53] Fix handling of ad group load errors IMA sometimes delivers an ad group load error just after the time of the ad group, which is problematic now that we set the expected ad group index based on the last returned content progress. Only update the expected ad group index once we are within a fixed preloading threshold of the next ad. Also fix updating the ad group to use the new ad count, and check for ad group load errors when we have no expected ad group defensively. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185803086 --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 0a79acb617..312cb046ba 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -163,6 +163,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A */ private static final long END_OF_CONTENT_POSITION_THRESHOLD_MS = 5000; + /** The maximum duration before an ad break that IMA may start preloading the next ad. */ + private static final long MAXIMUM_PRELOAD_DURATION_MS = 8000; + /** * The "Skip ad" button rendered in the IMA WebView does not gain focus by default and cannot be * clicked via a keypress event. Workaround this issue by calling focus() on the HTML element in @@ -621,9 +624,17 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs)); } else if (imaAdState == IMA_AD_STATE_NONE && hasContentDuration) { contentPositionMs = player.getCurrentPosition(); - // Keep track of the ad group index that IMA will load for the current content position. - expectedAdGroupIndex = + // Update the expected ad group index for the current content position. The update is delayed + // until MAXIMUM_PRELOAD_DURATION_MS before the ad so that an ad group load error delivered + // just after an ad group isn't incorrectly attributed to the next ad group. + int nextAdGroupIndex = adPlaybackState.getAdGroupIndexAfterPositionUs(C.msToUs(contentPositionMs)); + if (nextAdGroupIndex != expectedAdGroupIndex + && nextAdGroupIndex != C.INDEX_UNSET + && C.usToMs(adPlaybackState.adGroupTimesUs[nextAdGroupIndex]) - contentPositionMs + < MAXIMUM_PRELOAD_DURATION_MS) { + expectedAdGroupIndex = nextAdGroupIndex; + } } else { return VideoProgressUpdate.VIDEO_TIME_NOT_READY; } @@ -969,10 +980,15 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A private void handleAdGroupLoadError() { int adGroupIndex = this.adGroupIndex == C.INDEX_UNSET ? expectedAdGroupIndex : this.adGroupIndex; + if (adGroupIndex == C.INDEX_UNSET) { + // Drop the error, as we don't know which ad group it relates to. + return; + } AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; if (adGroup.count == C.LENGTH_UNSET) { adPlaybackState = adPlaybackState.withAdCount(adGroupIndex, Math.max(1, adGroup.states.length)); + adGroup = adPlaybackState.adGroups[adGroupIndex]; } for (int i = 0; i < adGroup.count; i++) { if (adGroup.states[i] == AdPlaybackState.AD_STATE_UNAVAILABLE) { From fe2b01c57e21db84a74a7bad814e581aea8eda8b Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 15 Feb 2018 04:10:53 -0800 Subject: [PATCH 39/53] Fix content progress reporting if there is no preroll If there was no preroll and the pending content position was set before the first midroll, the pending content position was never cleared so loading the ad was never triggered. Only set a pending content position if we know that we need to trigger playing an ad for the current position and IMA will poll for an ad (because there is a midroll ad group). Clearing the pending content position happens when IMA pauses content to play the ad. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185818315 --- .../com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 312cb046ba..43e3c0d93d 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -889,8 +889,8 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A podIndexOffset = adGroupIndexForPosition - 1; } - if (hasMidrollAdGroups(adGroupTimesUs)) { - // IMA will poll the content position, so provide the player's initial position like a seek. + if (adGroupIndexForPosition != C.INDEX_UNSET && hasMidrollAdGroups(adGroupTimesUs)) { + // Provide the player's initial position to trigger loading and playing the ad. pendingContentPositionMs = contentPositionMs; } From 454ec5a2d7b6af94a39cd1b196979d7911ab9721 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 15 Feb 2018 06:29:45 -0800 Subject: [PATCH 40/53] Add window sequence number to MediaPeriodId. All media periods are part of a queue of windows buffered and played by ExoPlayer. When repeating windows, the current MediaPeriodId is insufficient to distinguish between the repetitions of the same period. This makes it hard to see to which media period load events belong to, and it is also difficult to determine whether two media periods belong to the same logical window or whether they are part of different repetitions of the same window. Therefore this change adds a unique sequence number to each window in the sequence of windows and this sequence number is saved as part of the MediaPeriodId. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185829509 --- .../android/exoplayer2/MediaPeriodQueue.java | 124 +++++++++++++++--- .../android/exoplayer2/PlaybackInfo.java | 2 +- .../exoplayer2/source/MediaSource.java | 47 +++++-- .../exoplayer2/source/ads/AdsMediaSource.java | 5 +- .../android/exoplayer2/ExoPlayerTest.java | 5 +- .../source/ConcatenatingMediaSourceTest.java | 26 +++- .../DynamicConcatenatingMediaSourceTest.java | 38 ++++-- .../testutil/MediaSourceTestRunner.java | 8 +- 8 files changed, 200 insertions(+), 55 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 208a235777..3efff58f5d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -44,6 +44,7 @@ import com.google.android.exoplayer2.util.Assertions; private final Timeline.Period period; private final Timeline.Window window; + private long nextWindowSequenceNumber; private Timeline timeline; private @RepeatMode int repeatMode; private boolean shuffleModeEnabled; @@ -368,18 +369,70 @@ import com.google.android.exoplayer2.util.Assertions; * @return The identifier for the first media period to play, taking into account unplayed ads. */ public MediaPeriodId resolveMediaPeriodIdForAds(int periodIndex, long positionUs) { - timeline.getPeriod(periodIndex, period); - int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs); - if (adGroupIndex == C.INDEX_UNSET) { - return new MediaPeriodId(periodIndex); - } else { - int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); - return new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup); - } + long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(periodIndex); + return resolveMediaPeriodIdForAds(periodIndex, positionUs, windowSequenceNumber); } // Internal methods. + /** + * Resolves the specified timeline period and position to a {@link MediaPeriodId} that should be + * played, returning an identifier for an ad group if one needs to be played before the specified + * position, or an identifier for a content media period if not. + * + * @param periodIndex The index of the timeline period to play. + * @param positionUs The next content position in the period to play. + * @param windowSequenceNumber The sequence number of the window in the buffered sequence of + * windows this period is part of. + * @return The identifier for the first media period to play, taking into account unplayed ads. + */ + private MediaPeriodId resolveMediaPeriodIdForAds( + int periodIndex, long positionUs, long windowSequenceNumber) { + timeline.getPeriod(periodIndex, period); + int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs); + if (adGroupIndex == C.INDEX_UNSET) { + return new MediaPeriodId(periodIndex, windowSequenceNumber); + } else { + int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); + return new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); + } + } + + /** + * Resolves the specified period index to a corresponding window sequence number. Either by + * reusing the window sequence number of an existing matching media period or by creating a new + * window sequence number. + * + * @param periodIndex The index of the timeline period. + * @return A window sequence number for a media period created for this timeline period. + */ + private long resolvePeriodIndexToWindowSequenceNumber(int periodIndex) { + Object periodUid = timeline.getPeriod(periodIndex, period, /* setIds= */ true).uid; + MediaPeriodHolder mediaPeriodHolder = getFrontPeriod(); + while (mediaPeriodHolder != null) { + if (mediaPeriodHolder.uid.equals(periodUid)) { + // Reuse window sequence number of first exact period match. + return mediaPeriodHolder.info.id.windowSequenceNumber; + } + mediaPeriodHolder = mediaPeriodHolder.next; + } + int windowIndex = period.windowIndex; + mediaPeriodHolder = getFrontPeriod(); + while (mediaPeriodHolder != null) { + int indexOfHolderInTimeline = timeline.getIndexOfPeriod(mediaPeriodHolder.uid); + if (indexOfHolderInTimeline != C.INDEX_UNSET) { + int holderWindowIndex = timeline.getPeriod(indexOfHolderInTimeline, period).windowIndex; + if (holderWindowIndex == windowIndex) { + // As an alternative, try to match other periods of the same window. + return mediaPeriodHolder.info.id.windowSequenceNumber; + } + } + mediaPeriodHolder = mediaPeriodHolder.next; + } + // If no match is found, create new sequence number. + return nextWindowSequenceNumber++; + } + /** * Returns whether {@code periodHolder} can be kept for playing the media period described by * {@code info}. @@ -466,7 +519,10 @@ import com.google.android.exoplayer2.util.Assertions; } long startPositionUs; - int nextWindowIndex = timeline.getPeriod(nextPeriodIndex, period).windowIndex; + int nextWindowIndex = + timeline.getPeriod(nextPeriodIndex, period, /* setIds= */ true).windowIndex; + Object nextPeriodUid = period.uid; + long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber; if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) { // We're starting to buffer a new window. When playback transitions to this window we'll // want it to be from its default start position. The expected delay until playback @@ -487,10 +543,16 @@ import com.google.android.exoplayer2.util.Assertions; } nextPeriodIndex = defaultPosition.first; startPositionUs = defaultPosition.second; + if (mediaPeriodHolder.next != null && mediaPeriodHolder.next.uid.equals(nextPeriodUid)) { + windowSequenceNumber = mediaPeriodHolder.next.info.id.windowSequenceNumber; + } else { + windowSequenceNumber = nextWindowSequenceNumber++; + } } else { startPositionUs = 0; } - MediaPeriodId periodId = resolveMediaPeriodIdForAds(nextPeriodIndex, startPositionUs); + MediaPeriodId periodId = + resolveMediaPeriodIdForAds(nextPeriodIndex, startPositionUs, windowSequenceNumber); return getMediaPeriodInfo(periodId, startPositionUs, startPositionUs); } @@ -512,11 +574,14 @@ import com.google.android.exoplayer2.util.Assertions; currentPeriodId.periodIndex, adGroupIndex, nextAdIndexInAdGroup, - mediaPeriodInfo.contentPositionUs); + mediaPeriodInfo.contentPositionUs, + currentPeriodId.windowSequenceNumber); } else { // Play content from the ad group position. return getMediaPeriodInfoForContent( - currentPeriodId.periodIndex, mediaPeriodInfo.contentPositionUs); + currentPeriodId.periodIndex, + mediaPeriodInfo.contentPositionUs, + currentPeriodId.windowSequenceNumber); } } else if (mediaPeriodInfo.endPositionUs != C.TIME_END_OF_SOURCE) { // Play the next ad group if it's available. @@ -524,7 +589,9 @@ import com.google.android.exoplayer2.util.Assertions; if (nextAdGroupIndex == C.INDEX_UNSET) { // The next ad group can't be played. Play content from the ad group position instead. return getMediaPeriodInfoForContent( - currentPeriodId.periodIndex, mediaPeriodInfo.endPositionUs); + currentPeriodId.periodIndex, + mediaPeriodInfo.endPositionUs, + currentPeriodId.windowSequenceNumber); } int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex); return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup) @@ -533,7 +600,8 @@ import com.google.android.exoplayer2.util.Assertions; currentPeriodId.periodIndex, nextAdGroupIndex, adIndexInAdGroup, - mediaPeriodInfo.endPositionUs); + mediaPeriodInfo.endPositionUs, + currentPeriodId.windowSequenceNumber); } else { // Check if the postroll ad should be played. int adGroupCount = period.getAdGroupCount(); @@ -551,7 +619,11 @@ import com.google.android.exoplayer2.util.Assertions; } long contentDurationUs = period.getDurationUs(); return getMediaPeriodInfoForAd( - currentPeriodId.periodIndex, adGroupIndex, adIndexInAdGroup, contentDurationUs); + currentPeriodId.periodIndex, + adGroupIndex, + adIndexInAdGroup, + contentDurationUs, + currentPeriodId.windowSequenceNumber); } } @@ -583,15 +655,24 @@ import com.google.android.exoplayer2.util.Assertions; return null; } return getMediaPeriodInfoForAd( - id.periodIndex, id.adGroupIndex, id.adIndexInAdGroup, contentPositionUs); + id.periodIndex, + id.adGroupIndex, + id.adIndexInAdGroup, + contentPositionUs, + id.windowSequenceNumber); } else { - return getMediaPeriodInfoForContent(id.periodIndex, startPositionUs); + return getMediaPeriodInfoForContent(id.periodIndex, startPositionUs, id.windowSequenceNumber); } } private MediaPeriodInfo getMediaPeriodInfoForAd( - int periodIndex, int adGroupIndex, int adIndexInAdGroup, long contentPositionUs) { - MediaPeriodId id = new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup); + int periodIndex, + int adGroupIndex, + int adIndexInAdGroup, + long contentPositionUs, + long windowSequenceNumber) { + MediaPeriodId id = + new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); boolean isLastInPeriod = isLastInPeriod(id, C.TIME_END_OF_SOURCE); boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); long durationUs = @@ -612,8 +693,9 @@ import com.google.android.exoplayer2.util.Assertions; isLastInTimeline); } - private MediaPeriodInfo getMediaPeriodInfoForContent(int periodIndex, long startPositionUs) { - MediaPeriodId id = new MediaPeriodId(periodIndex); + private MediaPeriodInfo getMediaPeriodInfoForContent( + int periodIndex, long startPositionUs, long windowSequenceNumber) { + MediaPeriodId id = new MediaPeriodId(periodIndex, windowSequenceNumber); timeline.getPeriod(id.periodIndex, period); int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); long endUs = diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index bb39bf3d0b..3ff2ec9461 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -41,7 +41,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; this( timeline, /* manifest= */ null, - new MediaPeriodId(0), + new MediaPeriodId(/* periodIndex= */ 0), startPositionUs, /* contentPositionUs =*/ C.TIME_UNSET, Player.STATE_IDLE, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index 25da60cb74..02bd0cdbc7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -63,12 +63,6 @@ public interface MediaSource { */ final class MediaPeriodId { - /** - * Value for unset media period identifiers. - */ - public static final MediaPeriodId UNSET = - new MediaPeriodId(C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET); - /** * The timeline period index. */ @@ -86,13 +80,32 @@ public interface MediaSource { */ public final int adIndexInAdGroup; + /** + * The sequence number of the window in the buffered sequence of windows this media period is + * part of. {@link C#INDEX_UNSET} if the media period id is not part of a buffered sequence of + * windows. + */ + public final long windowSequenceNumber; + + /** + * Creates a media period identifier for a dummy period which is not part of a buffered sequence + * of windows. + * + * @param periodIndex The period index. + */ + public MediaPeriodId(int periodIndex) { + this(periodIndex, C.INDEX_UNSET); + } + /** * Creates a media period identifier for the specified period in the timeline. * * @param periodIndex The timeline period index. + * @param windowSequenceNumber The sequence number of the window in the buffered sequence of + * windows this media period is part of. */ - public MediaPeriodId(int periodIndex) { - this(periodIndex, C.INDEX_UNSET, C.INDEX_UNSET); + public MediaPeriodId(int periodIndex, long windowSequenceNumber) { + this(periodIndex, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber); } /** @@ -102,19 +115,24 @@ public interface MediaSource { * @param periodIndex The index of the timeline period that contains the ad group. * @param adGroupIndex The index of the ad group. * @param adIndexInAdGroup The index of the ad in the ad group. + * @param windowSequenceNumber The sequence number of the window in the buffered sequence of + * windows this media period is part of. */ - public MediaPeriodId(int periodIndex, int adGroupIndex, int adIndexInAdGroup) { + public MediaPeriodId( + int periodIndex, int adGroupIndex, int adIndexInAdGroup, long windowSequenceNumber) { this.periodIndex = periodIndex; this.adGroupIndex = adGroupIndex; this.adIndexInAdGroup = adIndexInAdGroup; + this.windowSequenceNumber = windowSequenceNumber; } /** * Returns a copy of this period identifier but with {@code newPeriodIndex} as its period index. */ public MediaPeriodId copyWithPeriodIndex(int newPeriodIndex) { - return periodIndex == newPeriodIndex ? this - : new MediaPeriodId(newPeriodIndex, adGroupIndex, adIndexInAdGroup); + return periodIndex == newPeriodIndex + ? this + : new MediaPeriodId(newPeriodIndex, adGroupIndex, adIndexInAdGroup, windowSequenceNumber); } /** @@ -134,8 +152,10 @@ public interface MediaSource { } MediaPeriodId periodId = (MediaPeriodId) obj; - return periodIndex == periodId.periodIndex && adGroupIndex == periodId.adGroupIndex - && adIndexInAdGroup == periodId.adIndexInAdGroup; + return periodIndex == periodId.periodIndex + && adGroupIndex == periodId.adGroupIndex + && adIndexInAdGroup == periodId.adIndexInAdGroup + && windowSequenceNumber == periodId.windowSequenceNumber; } @Override @@ -144,6 +164,7 @@ public interface MediaSource { result = 31 * result + periodIndex; result = 31 * result + adGroupIndex; result = 31 * result + adIndexInAdGroup; + result = 31 * result + (int) windowSequenceNumber; return result; } 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 8c4d85ff4c..854be90d1c 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 @@ -235,7 +235,10 @@ public final class AdsMediaSource extends CompositeMediaSource { } MediaSource mediaSource = adGroupMediaSources[adGroupIndex][adIndexInAdGroup]; DeferredMediaPeriod deferredMediaPeriod = - new DeferredMediaPeriod(mediaSource, new MediaPeriodId(0), allocator); + new DeferredMediaPeriod( + mediaSource, + new MediaPeriodId(/* periodIndex= */ 0, id.windowSequenceNumber), + allocator); deferredMediaPeriod.setPrepareErrorListener( new AdPrepareErrorListener(adGroupIndex, adIndexInAdGroup)); List mediaPeriods = deferredMediaPeriodByAdMediaSource.get(mediaSource); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 7faa349705..3d0cde5df8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -1802,7 +1802,10 @@ public final class ExoPlayerTest { testRunner.assertPlayedPeriodIndices(0, 1); // Assert that the second period was re-created from the new timeline. assertThat(mediaSource.getCreatedMediaPeriods()) - .containsExactly(new MediaPeriodId(0), new MediaPeriodId(1), new MediaPeriodId(1)) + .containsExactly( + new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0), + new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 1), + new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 2)) .inOrder(); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index d7cf8db4bc..ccc3ddea46 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -245,12 +245,26 @@ public final class ConcatenatingMediaSourceTest { // Create all periods and assert period creation of child media sources has been called. testRunner.assertPrepareAndReleaseAllPeriods(); - mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(0)); - mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(1)); - mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0)); - mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1)); - mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0, 0, 0)); - mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0)); + mediaSourceContentOnly.assertMediaPeriodCreated( + new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + mediaSourceContentOnly.assertMediaPeriodCreated( + new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 0)); + mediaSourceWithAds.assertMediaPeriodCreated( + new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 1)); + mediaSourceWithAds.assertMediaPeriodCreated( + new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 1)); + mediaSourceWithAds.assertMediaPeriodCreated( + new MediaPeriodId( + /* periodIndex= */ 0, + /* adGroupIndex= */ 0, + /* adIndexInAdGroup= */ 0, + /* windowSequenceNumber= */ 1)); + mediaSourceWithAds.assertMediaPeriodCreated( + new MediaPeriodId( + /* periodIndex= */ 1, + /* adGroupIndex= */ 0, + /* adIndexInAdGroup= */ 0, + /* windowSequenceNumber= */ 1)); } finally { testRunner.release(); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java index a0847bf9ff..c1537c50d3 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java @@ -268,12 +268,16 @@ public final class DynamicConcatenatingMediaSourceTest { // Create a period from an unprepared lazy media source and assert Callback.onPrepared is not // called yet. - MediaPeriod lazyPeriod = testRunner.createPeriod(new MediaPeriodId(0)); + MediaPeriod lazyPeriod = + testRunner.createPeriod( + new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); CountDownLatch preparedCondition = testRunner.preparePeriod(lazyPeriod, 0); assertThat(preparedCondition.getCount()).isEqualTo(1); // Assert that a second period can also be created and released without problems. - MediaPeriod secondLazyPeriod = testRunner.createPeriod(new MediaPeriodId(0)); + MediaPeriod secondLazyPeriod = + testRunner.createPeriod( + new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); testRunner.releasePeriod(secondLazyPeriod); // Trigger source info refresh for lazy media source. Assert that now all information is @@ -654,12 +658,26 @@ public final class DynamicConcatenatingMediaSourceTest { // Create all periods and assert period creation of child media sources has been called. testRunner.assertPrepareAndReleaseAllPeriods(); - mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(0)); - mediaSourceContentOnly.assertMediaPeriodCreated(new MediaPeriodId(1)); - mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0)); - mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1)); - mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(0, 0, 0)); - mediaSourceWithAds.assertMediaPeriodCreated(new MediaPeriodId(1, 0, 0)); + mediaSourceContentOnly.assertMediaPeriodCreated( + new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); + mediaSourceContentOnly.assertMediaPeriodCreated( + new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 0)); + mediaSourceWithAds.assertMediaPeriodCreated( + new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 1)); + mediaSourceWithAds.assertMediaPeriodCreated( + new MediaPeriodId(/* periodIndex= */ 1, /* windowSequenceNumber= */ 1)); + mediaSourceWithAds.assertMediaPeriodCreated( + new MediaPeriodId( + /* periodIndex= */ 0, + /* adGroupIndex= */ 0, + /* adIndexInAdGroup= */ 0, + /* windowSequenceNumber= */ 1)); + mediaSourceWithAds.assertMediaPeriodCreated( + new MediaPeriodId( + /* periodIndex= */ 1, + /* adGroupIndex= */ 0, + /* adIndexInAdGroup= */ 0, + /* windowSequenceNumber= */ 1)); } @Test @@ -755,7 +773,9 @@ public final class DynamicConcatenatingMediaSourceTest { FakeMediaSource childSource = createFakeMediaSource(); mediaSource.addMediaSource(childSource); testRunner.prepareSource(); - MediaPeriod mediaPeriod = testRunner.createPeriod(new MediaPeriodId(/* periodIndex= */ 0)); + MediaPeriod mediaPeriod = + testRunner.createPeriod( + new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0)); mediaSource.removeMediaSource(/* index= */ 0); testRunner.assertTimelineChangeBlocking(); testRunner.releasePeriod(mediaPeriod); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java index cf0cc342f8..16389112ca 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java @@ -244,16 +244,18 @@ public class MediaSourceTestRunner { /** * Creates and releases all periods (including ad periods) defined in the last timeline to be * returned from {@link #prepareSource()}, {@link #assertTimelineChange()} or {@link - * #assertTimelineChangeBlocking()}. + * #assertTimelineChangeBlocking()}. The {@link MediaPeriodId#windowSequenceNumber} is set to the + * index of the window. */ public void assertPrepareAndReleaseAllPeriods() throws InterruptedException { Timeline.Period period = new Timeline.Period(); for (int i = 0; i < timeline.getPeriodCount(); i++) { - assertPrepareAndReleasePeriod(new MediaPeriodId(i)); timeline.getPeriod(i, period); + assertPrepareAndReleasePeriod(new MediaPeriodId(i, period.windowIndex)); for (int adGroupIndex = 0; adGroupIndex < period.getAdGroupCount(); adGroupIndex++) { for (int adIndex = 0; adIndex < period.getAdCountInAdGroup(adGroupIndex); adIndex++) { - assertPrepareAndReleasePeriod(new MediaPeriodId(i, adGroupIndex, adIndex)); + assertPrepareAndReleasePeriod( + new MediaPeriodId(i, adGroupIndex, adIndex, period.windowIndex)); } } } From b36db1a87e16e3f6d8ccc4b9ce09e59a0cab3ccc Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 16 Feb 2018 03:05:55 -0800 Subject: [PATCH 41/53] Add little endian and 14-bit mode support for DtsReader Issue:#3340 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185973510 --- RELEASENOTES.md | 2 + .../android/exoplayer2/audio/DtsUtil.java | 118 ++++++++++++++++-- .../exoplayer2/extractor/ts/DtsReader.java | 16 ++- .../exoplayer2/util/ParsableBitArray.java | 34 +++++ .../exoplayer2/util/ParsableBitArrayTest.java | 67 ++++++++++ 5 files changed, 219 insertions(+), 18 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7d15a0c70d..ec46844982 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -116,6 +116,8 @@ completed. * ID3: Better handle malformed ID3 data ([#3792](https://github.com/google/ExoPlayer/issues/3792). +* Support 14-bit mode and little endianness in DTS PES packets + ([#3340](https://github.com/google/ExoPlayer/issues/3340)). ### 2.6.1 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java index 9e9b927fab..dc07b1a646 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java @@ -20,12 +20,22 @@ import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableBitArray; import java.nio.ByteBuffer; +import java.util.Arrays; /** * Utility methods for parsing DTS frames. */ public final class DtsUtil { + private static final int SYNC_VALUE_BE = 0x7FFE8001; + private static final int SYNC_VALUE_14B_BE = 0x1FFFE800; + private static final int SYNC_VALUE_LE = 0xFE7F0180; + private static final int SYNC_VALUE_14B_LE = 0xFF1F00E8; + private static final byte FIRST_BYTE_BE = (byte) (SYNC_VALUE_BE >>> 24); + private static final byte FIRST_BYTE_14B_BE = (byte) (SYNC_VALUE_14B_BE >>> 24); + private static final byte FIRST_BYTE_LE = (byte) (SYNC_VALUE_LE >>> 24); + private static final byte FIRST_BYTE_14B_LE = (byte) (SYNC_VALUE_14B_LE >>> 24); + /** * Maps AMODE to the number of channels. See ETSI TS 102 114 table 5.4. */ @@ -45,6 +55,20 @@ public final class DtsUtil { 384, 448, 512, 640, 768, 896, 1024, 1152, 1280, 1536, 1920, 2048, 2304, 2560, 2688, 2816, 2823, 2944, 3072, 3840, 4096, 6144, 7680}; + /** + * Returns whether a given integer matches a DTS sync word. Synchronization and storage modes are + * defined in ETSI TS 102 114 V1.1.1 (2002-08), Section 5.3. + * + * @param word An integer. + * @return Whether a given integer matches a DTS sync word. + */ + public static boolean isSyncWord(int word) { + return word == SYNC_VALUE_BE + || word == SYNC_VALUE_LE + || word == SYNC_VALUE_14B_BE + || word == SYNC_VALUE_14B_LE; + } + /** * Returns the DTS format given {@code data} containing the DTS frame according to ETSI TS 102 114 * subsections 5.3/5.4. @@ -57,8 +81,8 @@ public final class DtsUtil { */ public static Format parseDtsFormat(byte[] frame, String trackId, String language, DrmInitData drmInitData) { - ParsableBitArray frameBits = new ParsableBitArray(frame); - frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE + ParsableBitArray frameBits = getNormalizedFrameHeader(frame); + frameBits.skipBits(32 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE int amode = frameBits.readBits(6); int channelCount = CHANNELS_BY_AMODE[amode]; int sfreq = frameBits.readBits(4); @@ -79,8 +103,21 @@ public final class DtsUtil { * @return The number of audio samples represented by the frame. */ public static int parseDtsAudioSampleCount(byte[] data) { - // See ETSI TS 102 114 subsection 5.4.1. - int nblks = ((data[4] & 0x01) << 6) | ((data[5] & 0xFC) >> 2); + int nblks; + switch (data[0]) { + case FIRST_BYTE_LE: + nblks = ((data[5] & 0x01) << 6) | ((data[4] & 0xFC) >> 2); + break; + case FIRST_BYTE_14B_LE: + nblks = ((data[4] & 0x07) << 4) | ((data[7] & 0x3C) >> 2); + break; + case FIRST_BYTE_14B_BE: + nblks = ((data[5] & 0x07) << 4) | ((data[6] & 0x3C) >> 2); + break; + default: + // We blindly assume FIRST_BYTE_BE if none of the others match. + nblks = ((data[4] & 0x01) << 6) | ((data[5] & 0xFC) >> 2); + } return (nblks + 1) * 32; } @@ -94,8 +131,21 @@ public final class DtsUtil { public static int parseDtsAudioSampleCount(ByteBuffer buffer) { // See ETSI TS 102 114 subsection 5.4.1. int position = buffer.position(); - int nblks = ((buffer.get(position + 4) & 0x01) << 6) - | ((buffer.get(position + 5) & 0xFC) >> 2); + int nblks; + switch (buffer.get(position)) { + case FIRST_BYTE_LE: + nblks = ((buffer.get(position + 5) & 0x01) << 6) | ((buffer.get(position + 4) & 0xFC) >> 2); + break; + case FIRST_BYTE_14B_LE: + nblks = ((buffer.get(position + 4) & 0x07) << 4) | ((buffer.get(position + 7) & 0x3C) >> 2); + break; + case FIRST_BYTE_14B_BE: + nblks = ((buffer.get(position + 5) & 0x07) << 4) | ((buffer.get(position + 6) & 0x3C) >> 2); + break; + default: + // We blindly assume FIRST_BYTE_BE if none of the others match. + nblks = ((buffer.get(position + 4) & 0x01) << 6) | ((buffer.get(position + 5) & 0xFC) >> 2); + } return (nblks + 1) * 32; } @@ -106,9 +156,59 @@ public final class DtsUtil { * @return The frame's size in bytes. */ public static int getDtsFrameSize(byte[] data) { - return (((data[5] & 0x02) << 12) - | ((data[6] & 0xFF) << 4) - | ((data[7] & 0xF0) >> 4)) + 1; + int fsize; + boolean uses14BitPerWord = false; + switch (data[0]) { + case FIRST_BYTE_14B_BE: + fsize = (((data[6] & 0x03) << 12) | ((data[7] & 0xFF) << 4) | ((data[8] & 0x3C) >> 2)) + 1; + uses14BitPerWord = true; + break; + case FIRST_BYTE_LE: + fsize = (((data[4] & 0x03) << 12) | ((data[7] & 0xFF) << 4) | ((data[6] & 0xF0) >> 4)) + 1; + break; + case FIRST_BYTE_14B_LE: + fsize = (((data[7] & 0x03) << 12) | ((data[6] & 0xFF) << 4) | ((data[9] & 0x3C) >> 2)) + 1; + uses14BitPerWord = true; + break; + default: + // We blindly assume FIRST_BYTE_BE if none of the others match. + fsize = (((data[5] & 0x03) << 12) | ((data[6] & 0xFF) << 4) | ((data[7] & 0xF0) >> 4)) + 1; + } + + // If the frame is stored in 14-bit mode, adjust the frame size to reflect the actual byte size. + return uses14BitPerWord ? fsize * 16 / 14 : fsize; + } + + private static ParsableBitArray getNormalizedFrameHeader(byte[] frameHeader) { + if (frameHeader[0] == FIRST_BYTE_BE) { + // The frame is already 16-bit mode, big endian. + return new ParsableBitArray(frameHeader); + } + // Data is not normalized, but we don't want to modify frameHeader. + frameHeader = Arrays.copyOf(frameHeader, frameHeader.length); + if (isLittleEndianFrameHeader(frameHeader)) { + // Change endianness. + for (int i = 0; i < frameHeader.length - 1; i += 2) { + byte temp = frameHeader[i]; + frameHeader[i] = frameHeader[i + 1]; + frameHeader[i + 1] = temp; + } + } + ParsableBitArray frameBits = new ParsableBitArray(frameHeader); + if (frameHeader[0] == (byte) (SYNC_VALUE_14B_BE >> 24)) { + // Discard the 2 most significant bits of each 16 bit word. + ParsableBitArray scratchBits = new ParsableBitArray(frameHeader); + while (scratchBits.bitsLeft() >= 16) { + scratchBits.skipBits(2); + frameBits.putInt(scratchBits.readBits(14), 14); + } + } + frameBits.reset(frameHeader); + return frameBits; + } + + private static boolean isLittleEndianFrameHeader(byte[] frameHeader) { + return frameHeader[0] == FIRST_BYTE_LE || frameHeader[0] == FIRST_BYTE_14B_LE; } private DtsUtil() {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java index df1e8816f0..0fc3383015 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java @@ -32,9 +32,7 @@ public final class DtsReader implements ElementaryStreamReader { private static final int STATE_READING_HEADER = 1; private static final int STATE_READING_SAMPLE = 2; - private static final int HEADER_SIZE = 15; - private static final int SYNC_VALUE = 0x7FFE8001; - private static final int SYNC_VALUE_SIZE = 4; + private static final int HEADER_SIZE = 18; private final ParsableByteArray headerScratchBytes; private final String language; @@ -63,10 +61,6 @@ public final class DtsReader implements ElementaryStreamReader { */ public DtsReader(String language) { headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]); - headerScratchBytes.data[0] = (byte) ((SYNC_VALUE >> 24) & 0xFF); - headerScratchBytes.data[1] = (byte) ((SYNC_VALUE >> 16) & 0xFF); - headerScratchBytes.data[2] = (byte) ((SYNC_VALUE >> 8) & 0xFF); - headerScratchBytes.data[3] = (byte) (SYNC_VALUE & 0xFF); state = STATE_FINDING_SYNC; this.language = language; } @@ -96,7 +90,6 @@ public final class DtsReader implements ElementaryStreamReader { switch (state) { case STATE_FINDING_SYNC: if (skipToNextSync(data)) { - bytesRead = SYNC_VALUE_SIZE; state = STATE_READING_HEADER; } break; @@ -154,7 +147,12 @@ public final class DtsReader implements ElementaryStreamReader { while (pesBuffer.bytesLeft() > 0) { syncBytes <<= 8; syncBytes |= pesBuffer.readUnsignedByte(); - if (syncBytes == SYNC_VALUE) { + if (DtsUtil.isSyncWord(syncBytes)) { + headerScratchBytes.data[0] = (byte) ((syncBytes >> 24) & 0xFF); + headerScratchBytes.data[1] = (byte) ((syncBytes >> 16) & 0xFF); + headerScratchBytes.data[2] = (byte) ((syncBytes >> 8) & 0xFF); + headerScratchBytes.data[3] = (byte) (syncBytes & 0xFF); + bytesRead = 4; syncBytes = 0; return true; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java index 19b303484f..fb5f9525e9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java @@ -263,6 +263,40 @@ public final class ParsableBitArray { assertValidOffset(); } + /** + * Overwrites {@code numBits} from this array using the {@code numBits} least significant bits + * from {@code value}. Bits are written in order from most significant to least significant. The + * read position is advanced by {@code numBits}. + * + * @param value The integer whose {@code numBits} least significant bits are written into {@link + * #data}. + * @param numBits The number of bits to write. + */ + public void putInt(int value, int numBits) { + int remainingBitsToRead = numBits; + if (numBits < 32) { + value &= (1 << numBits) - 1; + } + int firstByteReadSize = Math.min(8 - bitOffset, numBits); + int firstByteRightPaddingSize = 8 - bitOffset - firstByteReadSize; + int firstByteBitmask = (0xFF00 >> bitOffset) | ((1 << firstByteRightPaddingSize) - 1); + data[byteOffset] &= firstByteBitmask; + int firstByteInputBits = value >>> (numBits - firstByteReadSize); + data[byteOffset] |= firstByteInputBits << firstByteRightPaddingSize; + remainingBitsToRead -= firstByteReadSize; + int currentByteIndex = byteOffset + 1; + while (remainingBitsToRead > 8) { + data[currentByteIndex++] = (byte) (value >>> (remainingBitsToRead - 8)); + remainingBitsToRead -= 8; + } + int lastByteRightPaddingSize = 8 - remainingBitsToRead; + data[currentByteIndex] &= (1 << lastByteRightPaddingSize) - 1; + int lastByteInput = value & ((1 << remainingBitsToRead) - 1); + data[currentByteIndex] |= lastByteInput << lastByteRightPaddingSize; + skipBits(numBits); + assertValidOffset(); + } + private void assertValidOffset() { // It is fine for position to be at the end of the array, but no further. Assertions.checkState(byteOffset >= 0 diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java index 611584a38c..438643b933 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java @@ -169,6 +169,73 @@ public final class ParsableBitArrayTest { assertReadBitsToEnd(16); } + @Test + public void testPutBitsWithinByte() { + ParsableBitArray output = new ParsableBitArray(new byte[4]); + output.skipBits(1); + + output.putInt(0x3F, 5); + + output.setPosition(0); + assertThat(output.readBits(8)).isEqualTo(0x1F << 2); // Check that only 5 bits are modified. + } + + @Test + public void testPutBitsAcrossTwoBytes() { + ParsableBitArray output = new ParsableBitArray(new byte[4]); + output.setPosition(12); + + output.putInt(0xFF, 8); + output.setPosition(8); + + assertThat(output.readBits(16)).isEqualTo(0x0FF0); + } + + @Test + public void testPutBitsAcrossMultipleBytes() { + ParsableBitArray output = new ParsableBitArray(new byte[8]); + output.setPosition(31); // Writing starts at 31 to test the 30th bit is not modified. + + output.putInt(0xFF146098, 30); // Write only 30 to test the 61st bit is not modified. + + output.setPosition(30); + assertThat(output.readBits(32)).isEqualTo(0x3F146098 << 1); + } + + @Test + public void testPut32Bits() { + ParsableBitArray output = new ParsableBitArray(new byte[5]); + output.setPosition(4); + + output.putInt(0xFF146098, 32); + + output.setPosition(4); + assertThat(output.readBits(32)).isEqualTo(0xFF146098); + } + + @Test + public void testPutFullBytes() { + ParsableBitArray output = new ParsableBitArray(new byte[2]); + + output.putInt(0x81, 8); + + output.setPosition(0); + assertThat(output.readBits(8)).isEqualTo(0x81); + } + + @Test + public void testNoOverwriting() { + ParsableBitArray output = + new ParsableBitArray( + new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}); + output.setPosition(1); + + output.putInt(0, 30); + + output.setPosition(0); + assertThat(output.readBits(32)).isEqualTo(0x80000001); + } + private void assertReadBitsToEnd(int expectedStartPosition) { int position = testArray.getPosition(); assertThat(position).isEqualTo(expectedStartPosition); From 20dc5dc096da5b737ce29e01dec8c85a106a29e3 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 16 Feb 2018 06:07:55 -0800 Subject: [PATCH 42/53] Catch exceptions in all IMA callbacks If an exception is thrown in an IMA callback it crashes the process with lots of logging from WebView (including several stack traces, etc.). This change wraps ImaAdsLoader code that might throw, skips any remaining ads (as the errors are not recoverable, in general) and notifies a new load error callback so that the application can implement its own handling. The intention is to make the loader robust to unexpected requests from IMA and avoid crashes. Also handle IMA loading an ad in an ad group that has no available ads. In rare cases IMA will try to load an ad for which an error was previously notified, so this drops those load requests allowing playback of the content to continue. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185985850 --- RELEASENOTES.md | 1 + .../exoplayer2/ext/ima/ImaAdsLoader.java | 280 +++++++++++------- .../exoplayer2/source/ads/AdsLoader.java | 12 +- .../exoplayer2/source/ads/AdsMediaSource.java | 33 ++- .../android/exoplayer2/util/EventLogger.java | 5 + 5 files changed, 212 insertions(+), 119 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ec46844982..6dff63ecc3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -101,6 +101,7 @@ ([#3715](https://github.com/google/ExoPlayer/issues/3715)). * Propagate ad media preparation errors to IMA so that the ads can be skipped. + * Handle exceptions in IMA callbacks so that can be logged less verbosely. * `EventLogger` moved from the demo app into the core library. * Fix ANR issue on the Huawei P8 Lite, Huawei Y6II, Moto C+, Meizu M5C, Lenovo K4 Note and Sony Xperia E5. diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 43e3c0d93d..a9eb14c57e 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -401,7 +401,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A lastContentProgress = null; adDisplayContainer.setAdContainer(adUiViewGroup); player.addListener(this); - maybeNotifyAdError(); + maybeNotifyPendingAdLoadError(); if (adPlaybackState != null) { // Pass the ad playback state to the player, and resume ads if necessary. eventListener.onAdPlaybackState(adPlaybackState); @@ -447,35 +447,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A if (player == null) { return; } - if (DEBUG) { - Log.d( - TAG, "Prepare error for ad " + adIndexInAdGroup + " in group " + adGroupIndex, exception); + try { + handleAdPrepareError(adGroupIndex, adIndexInAdGroup, exception); + } catch (Exception e) { + maybeNotifyInternalError("handlePrepareError", e); } - if (imaAdState == IMA_AD_STATE_NONE) { - // Send IMA a content position at the ad group so that it will try to play it, at which point - // we can notify that it failed to load. - fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); - fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); - if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { - fakeContentProgressOffsetMs = contentDurationMs; - } - shouldNotifyAdPrepareError = true; - } else { - // We're already playing an ad. - if (adIndexInAdGroup > playingAdIndexInAdGroup) { - // Mark the playing ad as ended so we can notify the error on the next ad and remove it, - // which means that the ad after will load (if any). - for (int i = 0; i < adCallbacks.size(); i++) { - adCallbacks.get(i).onEnded(); - } - } - playingAdIndexInAdGroup = adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay(); - for (int i = 0; i < adCallbacks.size(); i++) { - adCallbacks.get(i).onError(); - } - } - adPlaybackState = adPlaybackState.withAdLoadError(adGroupIndex, adIndexInAdGroup); - updateAdPlaybackState(); } // com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener implementation. @@ -493,7 +469,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A adsManager.addAdEventListener(this); if (player != null) { // If a player is attached already, start playback immediately. - startAdPlayback(); + try { + startAdPlayback(); + } catch (Exception e) { + maybeNotifyInternalError("onAdsManagerLoaded", e); + } } } @@ -509,75 +489,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A Log.w(TAG, "Dropping ad event after release: " + adEvent); return; } - Ad ad = adEvent.getAd(); - switch (adEvent.getType()) { - case LOADED: - // The ad position is not always accurate when using preloading. See [Internal: b/62613240]. - AdPodInfo adPodInfo = ad.getAdPodInfo(); - int podIndex = adPodInfo.getPodIndex(); - adGroupIndex = - podIndex == -1 ? (adPlaybackState.adGroupCount - 1) : (podIndex + podIndexOffset); - int adPosition = adPodInfo.getAdPosition(); - int adCount = adPodInfo.getTotalAds(); - adsManager.start(); - if (DEBUG) { - Log.d(TAG, "Loaded ad " + adPosition + " of " + adCount + " in group " + adGroupIndex); - } - int oldAdCount = adPlaybackState.adGroups[adGroupIndex].count; - if (adCount != oldAdCount) { - if (oldAdCount == C.LENGTH_UNSET) { - adPlaybackState = adPlaybackState.withAdCount(adGroupIndex, adCount); - updateAdPlaybackState(); - } else { - // IMA sometimes unexpectedly decreases the ad count in an ad group. - Log.w(TAG, "Unexpected ad count in LOADED, " + adCount + ", expected " + oldAdCount); - } - } - if (adGroupIndex != expectedAdGroupIndex) { - Log.w( - TAG, - "Expected ad group index " - + expectedAdGroupIndex - + ", actual ad group index " - + adGroupIndex); - expectedAdGroupIndex = adGroupIndex; - } - break; - case CONTENT_PAUSE_REQUESTED: - // After CONTENT_PAUSE_REQUESTED, IMA will playAd/pauseAd/stopAd to show one or more ads - // before sending CONTENT_RESUME_REQUESTED. - imaPausedContent = true; - pauseContentInternal(); - break; - case STARTED: - if (ad.isSkippable()) { - focusSkipButton(); - } - break; - case TAPPED: - if (eventListener != null) { - eventListener.onAdTapped(); - } - break; - case CLICKED: - if (eventListener != null) { - eventListener.onAdClicked(); - } - break; - case CONTENT_RESUME_REQUESTED: - imaPausedContent = false; - resumeContentInternal(); - break; - case LOG: - Map adData = adEvent.getAdData(); - Log.i(TAG, "Log AdEvent: " + adData); - if ("adLoadError".equals(adData.get("type"))) { - handleAdGroupLoadError(); - } - break; - case ALL_ADS_COMPLETED: - default: - break; + try { + handleAdEvent(adEvent); + } catch (Exception e) { + maybeNotifyInternalError("onAdEvent", e); } } @@ -595,12 +510,16 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A adPlaybackState = new AdPlaybackState(); updateAdPlaybackState(); } else if (isAdGroupLoadError(error)) { - handleAdGroupLoadError(); + try { + handleAdGroupLoadError(); + } catch (Exception e) { + maybeNotifyInternalError("onAdError", e); + } } if (pendingAdErrorEvent == null) { pendingAdErrorEvent = adErrorEvent; } - maybeNotifyAdError(); + maybeNotifyPendingAdLoadError(); } // ContentProgressProvider implementation. @@ -670,10 +589,18 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A if (DEBUG) { Log.d(TAG, "loadAd in ad group " + adGroupIndex); } - int adIndexInAdGroup = getAdIndexInAdGroupToLoad(adGroupIndex); - adPlaybackState = - adPlaybackState.withAdUri(adGroupIndex, adIndexInAdGroup, Uri.parse(adUriString)); - updateAdPlaybackState(); + try { + int adIndexInAdGroup = getAdIndexInAdGroupToLoad(adGroupIndex); + if (adIndexInAdGroup == C.INDEX_UNSET) { + Log.w(TAG, "Unexpected loadAd in an ad group with no remaining unavailable ads"); + return; + } + adPlaybackState = + adPlaybackState.withAdUri(adGroupIndex, adIndexInAdGroup, Uri.parse(adUriString)); + updateAdPlaybackState(); + } catch (Exception e) { + maybeNotifyInternalError("loadAd", e); + } } @Override @@ -739,7 +666,11 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A Log.w(TAG, "Unexpected stopAd"); return; } - stopAdInternal(); + try { + stopAdInternal(); + } catch (Exception e) { + maybeNotifyInternalError("stopAd", e); + } } @Override @@ -760,7 +691,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A @Override public void resumeAd() { // This method is never called. See [Internal: b/18931719]. - throw new IllegalStateException(); + maybeNotifyInternalError("resumeAd", new IllegalStateException("Unexpected call to resumeAd")); } // Player.EventListener implementation. @@ -902,12 +833,76 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A } } - private void maybeNotifyAdError() { - if (eventListener != null && pendingAdErrorEvent != null) { - IOException exception = - new IOException("Ad error: " + pendingAdErrorEvent, pendingAdErrorEvent.getError()); - eventListener.onLoadError(exception); - pendingAdErrorEvent = null; + private void handleAdEvent(AdEvent adEvent) { + Ad ad = adEvent.getAd(); + switch (adEvent.getType()) { + case LOADED: + // The ad position is not always accurate when using preloading. See [Internal: b/62613240]. + AdPodInfo adPodInfo = ad.getAdPodInfo(); + int podIndex = adPodInfo.getPodIndex(); + adGroupIndex = + podIndex == -1 ? (adPlaybackState.adGroupCount - 1) : (podIndex + podIndexOffset); + int adPosition = adPodInfo.getAdPosition(); + int adCount = adPodInfo.getTotalAds(); + adsManager.start(); + if (DEBUG) { + Log.d(TAG, "Loaded ad " + adPosition + " of " + adCount + " in group " + adGroupIndex); + } + int oldAdCount = adPlaybackState.adGroups[adGroupIndex].count; + if (adCount != oldAdCount) { + if (oldAdCount == C.LENGTH_UNSET) { + adPlaybackState = adPlaybackState.withAdCount(adGroupIndex, adCount); + updateAdPlaybackState(); + } else { + // IMA sometimes unexpectedly decreases the ad count in an ad group. + Log.w(TAG, "Unexpected ad count in LOADED, " + adCount + ", expected " + oldAdCount); + } + } + if (adGroupIndex != expectedAdGroupIndex) { + Log.w( + TAG, + "Expected ad group index " + + expectedAdGroupIndex + + ", actual ad group index " + + adGroupIndex); + expectedAdGroupIndex = adGroupIndex; + } + break; + case CONTENT_PAUSE_REQUESTED: + // After CONTENT_PAUSE_REQUESTED, IMA will playAd/pauseAd/stopAd to show one or more ads + // before sending CONTENT_RESUME_REQUESTED. + imaPausedContent = true; + pauseContentInternal(); + break; + case STARTED: + if (ad.isSkippable()) { + focusSkipButton(); + } + break; + case TAPPED: + if (eventListener != null) { + eventListener.onAdTapped(); + } + break; + case CLICKED: + if (eventListener != null) { + eventListener.onAdClicked(); + } + break; + case CONTENT_RESUME_REQUESTED: + imaPausedContent = false; + resumeContentInternal(); + break; + case LOG: + Map adData = adEvent.getAdData(); + Log.i(TAG, "Log AdEvent: " + adData); + if ("adLoadError".equals(adData.get("type"))) { + handleAdGroupLoadError(); + } + break; + case ALL_ADS_COMPLETED: + default: + break; } } @@ -1001,6 +996,38 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A updateAdPlaybackState(); } + private void handleAdPrepareError(int adGroupIndex, int adIndexInAdGroup, Exception exception) { + if (DEBUG) { + Log.d( + TAG, "Prepare error for ad " + adIndexInAdGroup + " in group " + adGroupIndex, exception); + } + if (imaAdState == IMA_AD_STATE_NONE) { + // Send IMA a content position at the ad group so that it will try to play it, at which point + // we can notify that it failed to load. + fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); + fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); + if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { + fakeContentProgressOffsetMs = contentDurationMs; + } + shouldNotifyAdPrepareError = true; + } else { + // We're already playing an ad. + if (adIndexInAdGroup > playingAdIndexInAdGroup) { + // Mark the playing ad as ended so we can notify the error on the next ad and remove it, + // which means that the ad after will load (if any). + for (int i = 0; i < adCallbacks.size(); i++) { + adCallbacks.get(i).onEnded(); + } + } + playingAdIndexInAdGroup = adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay(); + for (int i = 0; i < adCallbacks.size(); i++) { + adCallbacks.get(i).onError(); + } + } + adPlaybackState = adPlaybackState.withAdLoadError(adGroupIndex, adIndexInAdGroup); + updateAdPlaybackState(); + } + private void checkForContentComplete() { if (contentDurationMs != C.TIME_UNSET && pendingContentPositionMs == C.TIME_UNSET && player.getContentPosition() + END_OF_CONTENT_POSITION_THRESHOLD_MS >= contentDurationMs @@ -1044,6 +1071,33 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A return adIndexInAdGroup == states.length ? C.INDEX_UNSET : adIndexInAdGroup; } + private void maybeNotifyPendingAdLoadError() { + if (pendingAdErrorEvent != null) { + if (eventListener != null) { + eventListener.onAdLoadError( + new IOException("Ad error: " + pendingAdErrorEvent, pendingAdErrorEvent.getError())); + } + pendingAdErrorEvent = null; + } + } + + private void maybeNotifyInternalError(String name, Exception cause) { + String message = "Internal error in " + name; + Log.e(TAG, message, cause); + if (eventListener != null) { + eventListener.onInternalAdLoadError(new RuntimeException(message, cause)); + } + // We can't recover from an unexpected error in general, so skip all remaining ads. + if (adPlaybackState == null) { + adPlaybackState = new AdPlaybackState(); + } else { + for (int i = 0; i < adPlaybackState.adGroupCount; i++) { + adPlaybackState = adPlaybackState.withSkippedAdGroup(i); + } + } + updateAdPlaybackState(); + } + private static long[] getAdGroupTimesUs(List cuePoints) { if (cuePoints.isEmpty()) { // If no cue points are specified, there is a preroll ad. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java index 91111ec0ea..6295ca4229 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java @@ -54,11 +54,19 @@ public interface AdsLoader { void onAdPlaybackState(AdPlaybackState adPlaybackState); /** - * Called when there was an error loading ads. + * Called when there was an error loading ads. The loader will skip the problematic ad(s). * * @param error The error. */ - void onLoadError(IOException error); + void onAdLoadError(IOException error); + + /** + * Called when an unexpected internal error is encountered while loading ads. The loader will + * skip all remaining ads, as the error is not recoverable. + * + * @param error The error. + */ + void onInternalAdLoadError(RuntimeException error); /** * Called when the user clicks through an ad (for example, following a 'learn more' link). 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 854be90d1c..64bab7ed96 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 @@ -73,14 +73,21 @@ public final class AdsMediaSource extends CompositeMediaSource { public interface EventListener extends MediaSourceEventListener { /** - * Called if there was an error loading ads. The media source will load the content without ads - * if ads can't be loaded, so listen for this event if you need to implement additional handling - * (for example, stopping the player). + * Called if there was an error loading one or more ads. The loader will skip the problematic + * ad(s). * * @param error The error. */ void onAdLoadError(IOException error); + /** + * Called when an unexpected internal error is encountered while loading ads. The loader will + * skip all remaining ads, as the error is not recoverable. + * + * @param error The error. + */ + void onInternalAdLoadError(RuntimeException error); + /** * Called when the user clicks through an ad (for example, following a 'learn more' link). */ @@ -418,7 +425,7 @@ public final class AdsMediaSource extends CompositeMediaSource { } @Override - public void onLoadError(final IOException error) { + public void onAdLoadError(final IOException error) { if (released) { return; } @@ -436,6 +443,24 @@ public final class AdsMediaSource extends CompositeMediaSource { } } + @Override + public void onInternalAdLoadError(final RuntimeException error) { + if (released) { + return; + } + Log.w(TAG, "Internal ad load error", error); + if (eventHandler != null && eventListener != null) { + eventHandler.post( + new Runnable() { + @Override + public void run() { + if (!released) { + eventListener.onInternalAdLoadError(error); + } + } + }); + } + } } private final class AdPrepareErrorListener implements DeferredMediaPeriod.PrepareErrorListener { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java index 3a178a7f4a..d95f387996 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java @@ -382,6 +382,11 @@ public class EventLogger printInternalError("adLoadError", error); } + @Override + public void onInternalAdLoadError(RuntimeException error) { + printInternalError("internalAdLoadError", error); + } + @Override public void onAdClicked() { // Do nothing. From da27e7d22c0dc75691c163e943ba6aa554a6e5c5 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 16 Feb 2018 07:36:08 -0800 Subject: [PATCH 43/53] Fix content progress updates and position faking Occasionally the player could transition from playing content to playing an ad after IMA called playAd. The discontinuity triggered faking the content position, and the fake position was passed to IMA when content resumed causing the wrong ad group to be loaded. Fix this by only faking the position if the player transitions before playAd. Also fix the calculation of the expected ad group index for a postroll ad, and wait for the player to transition back from ad to content before passing a content progress update. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185994229 --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index a9eb14c57e..8ab05c574d 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -252,10 +252,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A */ private boolean shouldNotifyAdPrepareError; /** - * If a content period has finished but IMA has not yet sent an ad event with {@link - * AdEvent.AdEventType#CONTENT_PAUSE_REQUESTED}, stores the value of {@link - * SystemClock#elapsedRealtime()} when the content stopped playing. This can be used to determine - * a fake, increasing content position. {@link C#TIME_UNSET} otherwise. + * If a content period has finished but IMA has not yet called {@link #playAd()}, stores the value + * of {@link SystemClock#elapsedRealtime()} when the content stopped playing. This can be used to + * determine a fake, increasing content position. {@link C#TIME_UNSET} otherwise. */ private long fakeContentProgressElapsedRealtimeMs; /** @@ -541,18 +540,21 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs; expectedAdGroupIndex = adPlaybackState.getAdGroupIndexForPositionUs(C.msToUs(contentPositionMs)); - } else if (imaAdState == IMA_AD_STATE_NONE && hasContentDuration) { + } else if (imaAdState == IMA_AD_STATE_NONE && !playingAd && hasContentDuration) { contentPositionMs = player.getCurrentPosition(); // Update the expected ad group index for the current content position. The update is delayed // until MAXIMUM_PRELOAD_DURATION_MS before the ad so that an ad group load error delivered // just after an ad group isn't incorrectly attributed to the next ad group. int nextAdGroupIndex = adPlaybackState.getAdGroupIndexAfterPositionUs(C.msToUs(contentPositionMs)); - if (nextAdGroupIndex != expectedAdGroupIndex - && nextAdGroupIndex != C.INDEX_UNSET - && C.usToMs(adPlaybackState.adGroupTimesUs[nextAdGroupIndex]) - contentPositionMs - < MAXIMUM_PRELOAD_DURATION_MS) { - expectedAdGroupIndex = nextAdGroupIndex; + if (nextAdGroupIndex != expectedAdGroupIndex && nextAdGroupIndex != C.INDEX_UNSET) { + long nextAdGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[nextAdGroupIndex]); + if (nextAdGroupTimeMs == C.TIME_END_OF_SOURCE) { + nextAdGroupTimeMs = contentDurationMs; + } + if (nextAdGroupTimeMs - contentPositionMs < MAXIMUM_PRELOAD_DURATION_MS) { + expectedAdGroupIndex = nextAdGroupIndex; + } } } else { return VideoProgressUpdate.VIDEO_TIME_NOT_READY; @@ -567,12 +569,12 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A public VideoProgressUpdate getAdProgress() { if (player == null) { return lastAdProgress; - } else if (imaAdState == IMA_AD_STATE_NONE) { - return VideoProgressUpdate.VIDEO_TIME_NOT_READY; - } else { + } else if (imaAdState != IMA_AD_STATE_NONE && playingAd) { long adDuration = player.getDuration(); return adDuration == C.TIME_UNSET ? VideoProgressUpdate.VIDEO_TIME_NOT_READY : new VideoProgressUpdate(player.getCurrentPosition(), adDuration); + } else { + return VideoProgressUpdate.VIDEO_TIME_NOT_READY; } } @@ -625,6 +627,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A Log.w(TAG, "Unexpected playAd without stopAd"); break; case IMA_AD_STATE_NONE: + // IMA is requesting to play the ad, so stop faking the content position. + fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET; + fakeContentProgressOffsetMs = C.TIME_UNSET; imaAdState = IMA_AD_STATE_PLAYING; for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onPlay(); @@ -923,9 +928,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A Log.d(TAG, "VideoAdPlayerCallback.onEnded in onTimelineChanged/onPositionDiscontinuity"); } } - if (!wasPlayingAd && playingAd) { + if (!wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) { int adGroupIndex = player.getCurrentAdGroupIndex(); - // IMA hasn't sent CONTENT_PAUSE_REQUESTED yet, so fake the content position. + // IMA hasn't called playAd yet, so fake the content position. fakeContentProgressElapsedRealtimeMs = SystemClock.elapsedRealtime(); fakeContentProgressOffsetMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]); if (fakeContentProgressOffsetMs == C.TIME_END_OF_SOURCE) { @@ -955,9 +960,6 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A pendingContentPositionMs = C.TIME_UNSET; sentPendingContentPositionMs = false; } - // IMA is requesting to pause content, so stop faking the content position. - fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET; - fakeContentProgressOffsetMs = C.TIME_UNSET; } private void stopAdInternal() { From db1f9d0a0f8b6846a1cf36aba4cad4e700d90819 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 19 Feb 2018 03:02:12 -0800 Subject: [PATCH 44/53] Bump to 2.7.0 and prepare release notes ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=186200840 --- RELEASENOTES.md | 76 +++++++++++-------- constants.gradle | 2 +- demos/cast/src/main/AndroidManifest.xml | 4 +- demos/ima/src/main/AndroidManifest.xml | 4 +- demos/main/src/main/AndroidManifest.xml | 4 +- .../exoplayer2/ExoPlayerLibraryInfo.java | 6 +- 6 files changed, 53 insertions(+), 43 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6dff63ecc3..b71b54575b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,14 +2,12 @@ ### dev-v2 (not yet released) ### -* SimpleExoPlayerView: Automatically apply video rotation if - `SimpleExoPlayerView` is configured to use `TextureView` - ([#91](https://github.com/google/ExoPlayer/issues/91)). +* Downloading: Add `DownloadService`, `DownloadManager` and + related classes ([#2643](https://github.com/google/ExoPlayer/issues/2643)). + +### 2.7.0 ### + * Player interface: - * Add `Player.VideoComponent`, `Player.TextComponent` and - `Player.MetadataComponent` interfaces that define optional video, text and - metadata output functionality. New `getVideoComponent`, `getTextComponent` - and `getMetadataComponent` methods provide access to this functionality. * Add optional parameter to `stop` to reset the player when stopping. * Add a reason to `EventListener.onTimelineChanged` to distinguish between initial preparation, reset and dynamic updates. @@ -21,18 +19,23 @@ more customization of the message. Now supports setting a message delivery playback position and/or a delivery handler ([#2189](https://github.com/google/ExoPlayer/issues/2189)). -* UI components: - * Generalized player and control views to allow them to bind with any - `Player`, and renamed them to `PlayerView` and `PlayerControlView` - respectively. - * Made `PlayerView`'s play button behave correctly when the player is ended - ([#3689](https://github.com/google/ExoPlayer/issues/3689)), and call a - `PlaybackPreparer` when the player is idle. + * Add `Player.VideoComponent`, `Player.TextComponent` and + `Player.MetadataComponent` interfaces that define optional video, text and + metadata output functionality. New `getVideoComponent`, `getTextComponent` + and `getMetadataComponent` methods provide access to this functionality. +* Add `ExoPlayer.setSeekParameters` for controlling how seek operations are + performed. The `SeekParameters` class contains defaults for exact seeking and + seeking to the closest sync points before, either side or after specified seek + positions. `SeekParameters` are not currently supported when playing HLS + streams. +* DefaultTrackSelector: + * Replace `DefaultTrackSelector.Parameters` copy methods with a builder. + * Support disabling of individual text track selection flags. * Buffering: * Allow a back-buffer of media to be retained behind the current playback position, for fast backward seeking. The back-buffer can be configured by custom `LoadControl` implementations. - * Add ability for `SequenceableLoader` to reevaluate its buffer and discard + * Add ability for `SequenceableLoader` to re-evaluate its buffer and discard buffered media so that it can be re-buffered in a different quality. * Allow more flexible loading strategy when playing media containing multiple sub-streams, by allowing injection of custom `CompositeSequenceableLoader` @@ -40,29 +43,33 @@ `SsMediaSource.Factory`, and `MergingMediaSource`. * Play out existing buffer before retrying for progressive live streams ([#1606](https://github.com/google/ExoPlayer/issues/1606)). -* Add `ExoPlayer.setSeekParameters` for controlling how seek operations are - performed. The `SeekParameters` class contains defaults for exact seeking and - seeking to the closest sync points before, either side or after specified seek - positions. - * Note: `SeekParameters` are not currently supported when playing HLS streams. +* UI components: + * Generalized player and control views to allow them to bind with any + `Player`, and renamed them to `PlayerView` and `PlayerControlView` + respectively. + * Made `PlayerView` automatically apply video rotation when configured to use + `TextureView` ([#91](https://github.com/google/ExoPlayer/issues/91)). + * Made `PlayerView` play button behave correctly when the player is ended + ([#3689](https://github.com/google/ExoPlayer/issues/3689)), and call a + `PlaybackPreparer` when the player is idle. * DRM: Optimistically attempt playback of DRM protected content that does not declare scheme specific init data in the manifest. If playback of clear samples without keys is allowed, delay DRM session error propagation until keys are actually needed ([#3630](https://github.com/google/ExoPlayer/issues/3630)). * DASH: - * Support in-band Emsg events targeting player with scheme id - "urn:mpeg:dash:event:2012" and scheme value of either "1", "2" or "3". - * Support DASH manifest EventStream elements. -* DefaultTrackSelector: - * Replace `DefaultTrackSelector.Parameters` copy methods with a builder. - * Support disabling of individual text track selection flags. + * Support in-band Emsg events targeting the player with scheme id + "urn:mpeg:dash:event:2012" and scheme values "1", "2" and "3". + * Support EventStream elements in DASH manifests. * HLS: * Add opt-in support for chunkless preparation in HLS. This allows an HLS source to finish preparation without downloading any chunks, which can significantly reduce initial buffering time - ([#3149](https://github.com/google/ExoPlayer/issues/3149)). - * Fail on loss of sync with Transport Stream. + ([#3149](https://github.com/google/ExoPlayer/issues/3149)). More details + can be found + [here](https://medium.com/google-exoplayer/faster-hls-preparation-f6611aa15ea6). + * Fail if unable to sync with the Transport Stream, rather than entering + stuck in an indefinite buffering state. * Fix mime type propagation ([#3653](https://github.com/google/ExoPlayer/issues/3653)). * Fix ID3 context reuse across segment format changes @@ -70,7 +77,6 @@ * Use long for media sequence numbers ([#3747](https://github.com/google/ExoPlayer/issues/3747)) * Add initial support for the EXT-X-GAP tag. -* New Cast extension: Simplifies toggling between local and Cast playbacks. * Audio: * Support TrueHD passthrough for rechunked samples in Matroska files ([#2147](https://github.com/google/ExoPlayer/issues/2147)). @@ -78,13 +84,16 @@ resolution output in `DefaultAudioSink` ([#3635](https://github.com/google/ExoPlayer/pull/3635)). * Captions: - * Initial support for PGS subtitles + * Basic support for PGS subtitles ([#3008](https://github.com/google/ExoPlayer/issues/3008)). - * Fix issue handling CEA-608 captions where multiple buffers have the same + * Fix handling of CEA-608 captions where multiple buffers have the same presentation timestamp ([#3782](https://github.com/google/ExoPlayer/issues/3782)). -* CacheDataSource: Check periodically if it's possible to read from/write to - cache after deciding to bypass cache. +* Caching: + * Fix cache corruption issue + ([#3762](https://github.com/google/ExoPlayer/issues/3762)). + * Implement periodic check in `CacheDataSource` to see whether it's possible + to switch to reading/writing the cache having initially bypassed it. * IMA extension: * Fix the player getting stuck when an ad group fails to load ([#3584](https://github.com/google/ExoPlayer/issues/3584)). @@ -102,6 +111,7 @@ * Propagate ad media preparation errors to IMA so that the ads can be skipped. * Handle exceptions in IMA callbacks so that can be logged less verbosely. +* New Cast extension. Simplifies toggling between local and Cast playbacks. * `EventLogger` moved from the demo app into the core library. * Fix ANR issue on the Huawei P8 Lite, Huawei Y6II, Moto C+, Meizu M5C, Lenovo K4 Note and Sony Xperia E5. diff --git a/constants.gradle b/constants.gradle index c18fb28d4d..e0e8bbcc69 100644 --- a/constants.gradle +++ b/constants.gradle @@ -28,7 +28,7 @@ project.ext { junitVersion = '4.12' truthVersion = '0.35' robolectricVersion = '3.4.2' - releaseVersion = '2.6.1' + releaseVersion = '2.7.0' modulePrefix = ':' if (gradle.ext.has('exoplayerModulePrefix')) { modulePrefix += gradle.ext.exoplayerModulePrefix diff --git a/demos/cast/src/main/AndroidManifest.xml b/demos/cast/src/main/AndroidManifest.xml index e12e27fa4c..d23576572a 100644 --- a/demos/cast/src/main/AndroidManifest.xml +++ b/demos/cast/src/main/AndroidManifest.xml @@ -15,8 +15,8 @@ --> + android:versionCode="2700" + android:versionName="2.7.0"> diff --git a/demos/ima/src/main/AndroidManifest.xml b/demos/ima/src/main/AndroidManifest.xml index 0efeaf6f7f..7f169b8095 100644 --- a/demos/ima/src/main/AndroidManifest.xml +++ b/demos/ima/src/main/AndroidManifest.xml @@ -15,8 +15,8 @@ --> + android:versionCode="2700" + android:versionName="2.7.0"> diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index 00326157a2..a98176d93b 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -16,8 +16,8 @@ + android:versionCode="2700" + android:versionName="2.7.0"> diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index b2200b6671..1dec506ec9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -31,13 +31,13 @@ public final class ExoPlayerLibraryInfo { * The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - public static final String VERSION = "2.6.1"; + public static final String VERSION = "2.7.0"; /** * The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.6.1"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.7.0"; /** * The version of the library expressed as an integer, for example 1002003. @@ -47,7 +47,7 @@ public final class ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final int VERSION_INT = 2006001; + public static final int VERSION_INT = 2007000; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} From d7a467c6bef6f3d2e95479533d8893176ea4a6b6 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 20 Feb 2018 13:39:48 +0000 Subject: [PATCH 45/53] Remove unnecessary strings for 2.7.0 --- library/ui/src/main/res/values-af/strings.xml | 4 ---- library/ui/src/main/res/values-am/strings.xml | 4 ---- library/ui/src/main/res/values-ar/strings.xml | 4 ---- library/ui/src/main/res/values-b+sr+Latn/strings.xml | 4 ---- library/ui/src/main/res/values-bg/strings.xml | 4 ---- library/ui/src/main/res/values-ca/strings.xml | 4 ---- library/ui/src/main/res/values-cs/strings.xml | 4 ---- library/ui/src/main/res/values-da/strings.xml | 4 ---- library/ui/src/main/res/values-de/strings.xml | 4 ---- library/ui/src/main/res/values-el/strings.xml | 4 ---- library/ui/src/main/res/values-en-rAU/strings.xml | 4 ---- library/ui/src/main/res/values-en-rGB/strings.xml | 4 ---- library/ui/src/main/res/values-en-rIN/strings.xml | 4 ---- library/ui/src/main/res/values-es-rUS/strings.xml | 4 ---- library/ui/src/main/res/values-es/strings.xml | 4 ---- library/ui/src/main/res/values-fa/strings.xml | 4 ---- library/ui/src/main/res/values-fi/strings.xml | 4 ---- library/ui/src/main/res/values-fr-rCA/strings.xml | 4 ---- library/ui/src/main/res/values-fr/strings.xml | 4 ---- library/ui/src/main/res/values-hi/strings.xml | 4 ---- library/ui/src/main/res/values-hr/strings.xml | 4 ---- library/ui/src/main/res/values-hu/strings.xml | 4 ---- library/ui/src/main/res/values-in/strings.xml | 4 ---- library/ui/src/main/res/values-it/strings.xml | 4 ---- library/ui/src/main/res/values-iw/strings.xml | 4 ---- library/ui/src/main/res/values-ja/strings.xml | 4 ---- library/ui/src/main/res/values-ko/strings.xml | 4 ---- library/ui/src/main/res/values-lt/strings.xml | 4 ---- library/ui/src/main/res/values-lv/strings.xml | 4 ---- library/ui/src/main/res/values-nb/strings.xml | 4 ---- library/ui/src/main/res/values-nl/strings.xml | 4 ---- library/ui/src/main/res/values-pl/strings.xml | 4 ---- library/ui/src/main/res/values-pt-rPT/strings.xml | 4 ---- library/ui/src/main/res/values-pt/strings.xml | 4 ---- library/ui/src/main/res/values-ro/strings.xml | 4 ---- library/ui/src/main/res/values-ru/strings.xml | 4 ---- library/ui/src/main/res/values-sk/strings.xml | 4 ---- library/ui/src/main/res/values-sl/strings.xml | 4 ---- library/ui/src/main/res/values-sr/strings.xml | 4 ---- library/ui/src/main/res/values-sv/strings.xml | 4 ---- library/ui/src/main/res/values-sw/strings.xml | 4 ---- library/ui/src/main/res/values-th/strings.xml | 4 ---- library/ui/src/main/res/values-tl/strings.xml | 4 ---- library/ui/src/main/res/values-tr/strings.xml | 4 ---- library/ui/src/main/res/values-uk/strings.xml | 4 ---- library/ui/src/main/res/values-vi/strings.xml | 4 ---- library/ui/src/main/res/values-zh-rCN/strings.xml | 4 ---- library/ui/src/main/res/values-zh-rHK/strings.xml | 4 ---- library/ui/src/main/res/values-zh-rTW/strings.xml | 4 ---- library/ui/src/main/res/values-zu/strings.xml | 4 ---- library/ui/src/main/res/values/strings.xml | 10 ---------- 51 files changed, 210 deletions(-) diff --git a/library/ui/src/main/res/values-af/strings.xml b/library/ui/src/main/res/values-af/strings.xml index 46484d96f7..9019288efb 100644 --- a/library/ui/src/main/res/values-af/strings.xml +++ b/library/ui/src/main/res/values-af/strings.xml @@ -28,8 +28,4 @@ "Herhaal alles" "Skommel" "Volskermmodus" - "Aflaai op waglys" - "Laai tans af" - "Aflaai is voltooi" - "Kon nie aflaai nie" diff --git a/library/ui/src/main/res/values-am/strings.xml b/library/ui/src/main/res/values-am/strings.xml index a69d730fa7..da1ba64b04 100644 --- a/library/ui/src/main/res/values-am/strings.xml +++ b/library/ui/src/main/res/values-am/strings.xml @@ -28,8 +28,4 @@ "ሁሉንም ድገም" "በውዝ" "የሙሉ ማያ ሁነታ" - "ማውረድ ወረፋ ይዟል" - "በማውረድ ላይ" - "ማውረድ ተጠናቋል" - "ማውረድ አልተሳካም" diff --git a/library/ui/src/main/res/values-ar/strings.xml b/library/ui/src/main/res/values-ar/strings.xml index efc65b3dc0..9e519ed5ec 100644 --- a/library/ui/src/main/res/values-ar/strings.xml +++ b/library/ui/src/main/res/values-ar/strings.xml @@ -28,8 +28,4 @@ "تكرار الكل" "ترتيب عشوائي" "وضع ملء الشاشة" - "التنزيل قيد الانتظار" - "تحميل" - "اكتمل التنزيل" - "تعذّر التنزيل" diff --git a/library/ui/src/main/res/values-b+sr+Latn/strings.xml b/library/ui/src/main/res/values-b+sr+Latn/strings.xml index 3ae9cea4b4..3647d4a724 100644 --- a/library/ui/src/main/res/values-b+sr+Latn/strings.xml +++ b/library/ui/src/main/res/values-b+sr+Latn/strings.xml @@ -28,8 +28,4 @@ "Ponovi sve" "Pusti nasumično" "Režim celog ekrana" - "Preuzimanje je na čekanju" - "Preuzimanje" - "Preuzimanje je završeno" - "Preuzimanje nije uspelo" diff --git a/library/ui/src/main/res/values-bg/strings.xml b/library/ui/src/main/res/values-bg/strings.xml index 2524ded55f..f672ffd773 100644 --- a/library/ui/src/main/res/values-bg/strings.xml +++ b/library/ui/src/main/res/values-bg/strings.xml @@ -28,8 +28,4 @@ "Повтаряне на всички" "Разбъркване" "Режим на цял екран" - "Изтеглянето е в опашката" - "Изтегля се" - "Изтеглянето завърши" - "Изтеглянето не бе успешно" diff --git a/library/ui/src/main/res/values-ca/strings.xml b/library/ui/src/main/res/values-ca/strings.xml index d62f14274a..8427872437 100644 --- a/library/ui/src/main/res/values-ca/strings.xml +++ b/library/ui/src/main/res/values-ca/strings.xml @@ -28,8 +28,4 @@ "Repeteix tot" "Reprodueix aleatòriament" "Mode de pantalla completa" - "La baixada s\'ha posat a la cua" - "S\'està baixant" - "S\'ha completat la baixada" - "No s\'ha pogut baixar" diff --git a/library/ui/src/main/res/values-cs/strings.xml b/library/ui/src/main/res/values-cs/strings.xml index fd8c7115f0..c4e78ad397 100644 --- a/library/ui/src/main/res/values-cs/strings.xml +++ b/library/ui/src/main/res/values-cs/strings.xml @@ -28,8 +28,4 @@ "Opakovat vše" "Náhodně" "Režim celé obrazovky" - "Zařazeno do fronty stahování" - "Stahování" - "Stahování bylo dokončeno" - "Stažení se nezdařilo" diff --git a/library/ui/src/main/res/values-da/strings.xml b/library/ui/src/main/res/values-da/strings.xml index a53e9d132c..b75620deb5 100644 --- a/library/ui/src/main/res/values-da/strings.xml +++ b/library/ui/src/main/res/values-da/strings.xml @@ -28,8 +28,4 @@ "Gentag alle" "Bland" "Fuld skærm" - "Downloaden er i kø" - "Download" - "Downloaden er udført" - "Download mislykkedes" diff --git a/library/ui/src/main/res/values-de/strings.xml b/library/ui/src/main/res/values-de/strings.xml index 9850520d1f..6f187a16af 100644 --- a/library/ui/src/main/res/values-de/strings.xml +++ b/library/ui/src/main/res/values-de/strings.xml @@ -28,8 +28,4 @@ "Alle wiederholen" "Zufallsmix" "Vollbildmodus" - "Download in der Warteschlange" - "Wird heruntergeladen" - "Download abgeschlossen" - "Download fehlgeschlagen" diff --git a/library/ui/src/main/res/values-el/strings.xml b/library/ui/src/main/res/values-el/strings.xml index ebfe7c0cb1..990a0aa457 100644 --- a/library/ui/src/main/res/values-el/strings.xml +++ b/library/ui/src/main/res/values-el/strings.xml @@ -28,8 +28,4 @@ "Επανάληψη όλων" "Τυχαία αναπαραγωγή" "Λειτουργία πλήρους οθόνης" - "Η λήψη προστέθηκε στην ουρά" - "Λήψη" - "Η λήψη ολοκληρώθηκε" - "Η λήψη απέτυχε" diff --git a/library/ui/src/main/res/values-en-rAU/strings.xml b/library/ui/src/main/res/values-en-rAU/strings.xml index 6c2767cacb..62f8e77e91 100644 --- a/library/ui/src/main/res/values-en-rAU/strings.xml +++ b/library/ui/src/main/res/values-en-rAU/strings.xml @@ -28,8 +28,4 @@ "Repeat all" "Shuffle" "Full-screen mode" - "Download queued" - "Downloading" - "Download completed" - "Download failed" diff --git a/library/ui/src/main/res/values-en-rGB/strings.xml b/library/ui/src/main/res/values-en-rGB/strings.xml index 6c2767cacb..62f8e77e91 100644 --- a/library/ui/src/main/res/values-en-rGB/strings.xml +++ b/library/ui/src/main/res/values-en-rGB/strings.xml @@ -28,8 +28,4 @@ "Repeat all" "Shuffle" "Full-screen mode" - "Download queued" - "Downloading" - "Download completed" - "Download failed" diff --git a/library/ui/src/main/res/values-en-rIN/strings.xml b/library/ui/src/main/res/values-en-rIN/strings.xml index 6c2767cacb..62f8e77e91 100644 --- a/library/ui/src/main/res/values-en-rIN/strings.xml +++ b/library/ui/src/main/res/values-en-rIN/strings.xml @@ -28,8 +28,4 @@ "Repeat all" "Shuffle" "Full-screen mode" - "Download queued" - "Downloading" - "Download completed" - "Download failed" diff --git a/library/ui/src/main/res/values-es-rUS/strings.xml b/library/ui/src/main/res/values-es-rUS/strings.xml index 6836cfb577..8022f3a459 100644 --- a/library/ui/src/main/res/values-es-rUS/strings.xml +++ b/library/ui/src/main/res/values-es-rUS/strings.xml @@ -28,8 +28,4 @@ "Repetir todo" "Reproducir aleatoriamente" "Modo de pantalla completa" - "Descarga en fila" - "Descargando" - "Se completó la descarga" - "No se pudo descargar" diff --git a/library/ui/src/main/res/values-es/strings.xml b/library/ui/src/main/res/values-es/strings.xml index ab71863ca2..90ce129f5b 100644 --- a/library/ui/src/main/res/values-es/strings.xml +++ b/library/ui/src/main/res/values-es/strings.xml @@ -28,8 +28,4 @@ "Repetir todo" "Reproducir aleatoriamente" "Modo de pantalla completa" - "Descarga en cola" - "Descarga de archivos" - "Descarga de archivos completado" - "No se ha podido descargar" diff --git a/library/ui/src/main/res/values-fa/strings.xml b/library/ui/src/main/res/values-fa/strings.xml index 80c7340496..f6c9291824 100644 --- a/library/ui/src/main/res/values-fa/strings.xml +++ b/library/ui/src/main/res/values-fa/strings.xml @@ -28,8 +28,4 @@ "تکرار همه" "درهم" "حالت تمام‌صفحه" - "درانتظار بارگیری" - "درحال بارگیری" - "بارگیری کامل شد" - "بارگیری نشد" diff --git a/library/ui/src/main/res/values-fi/strings.xml b/library/ui/src/main/res/values-fi/strings.xml index 4eed46ee78..c5a1eb1d25 100644 --- a/library/ui/src/main/res/values-fi/strings.xml +++ b/library/ui/src/main/res/values-fi/strings.xml @@ -28,8 +28,4 @@ "Toista kaikki uudelleen" "Satunnaistoisto" "Koko näytön tila" - "Lataus jonossa" - "Ladataan" - "Lataus valmis" - "Lataus epäonnistui" diff --git a/library/ui/src/main/res/values-fr-rCA/strings.xml b/library/ui/src/main/res/values-fr-rCA/strings.xml index 40258f6480..1e7fc40e89 100644 --- a/library/ui/src/main/res/values-fr-rCA/strings.xml +++ b/library/ui/src/main/res/values-fr-rCA/strings.xml @@ -28,8 +28,4 @@ "Tout lire en boucle" "Lecture aléatoire" "Mode Plein écran" - "File d\'attente de télécharg." - "Téléchargement en cours…" - "Téléchargement terminé" - "Échec du téléchargement" diff --git a/library/ui/src/main/res/values-fr/strings.xml b/library/ui/src/main/res/values-fr/strings.xml index 3641c218d9..ec5fadfdfb 100644 --- a/library/ui/src/main/res/values-fr/strings.xml +++ b/library/ui/src/main/res/values-fr/strings.xml @@ -28,8 +28,4 @@ "Tout lire en boucle" "Aléatoire" "Mode plein écran" - "Téléchargement en attente" - "Téléchargement…" - "Téléchargement terminé" - "Échec du téléchargement" diff --git a/library/ui/src/main/res/values-hi/strings.xml b/library/ui/src/main/res/values-hi/strings.xml index 9f7ed0d171..2fdf625381 100644 --- a/library/ui/src/main/res/values-hi/strings.xml +++ b/library/ui/src/main/res/values-hi/strings.xml @@ -28,8 +28,4 @@ "सभी को दोहराएं" "शफ़ल करें" "फ़ुलस्क्रीन मोड" - "डाउनलोड को कतार में लगाया गया" - "डाउनलोड हो रहा है" - "डाउनलोड पूरा हुआ" - "डाउनलोड नहीं हो सका" diff --git a/library/ui/src/main/res/values-hr/strings.xml b/library/ui/src/main/res/values-hr/strings.xml index aeae6d6507..f85777b9b8 100644 --- a/library/ui/src/main/res/values-hr/strings.xml +++ b/library/ui/src/main/res/values-hr/strings.xml @@ -28,8 +28,4 @@ "Ponovi sve" "Reproduciraj nasumično" "Prikaz na cijelom zaslonu" - "Preuzimanje na čekanju" - "Preuzimanje datoteka" - "Preuzimanje je dovršeno" - "Preuzimanje nije uspjelo" diff --git a/library/ui/src/main/res/values-hu/strings.xml b/library/ui/src/main/res/values-hu/strings.xml index b001182a35..51358cf68e 100644 --- a/library/ui/src/main/res/values-hu/strings.xml +++ b/library/ui/src/main/res/values-hu/strings.xml @@ -28,8 +28,4 @@ "Összes szám ismétlése" "Keverés" "Teljes képernyős mód" - "Letöltés várólistára helyezve" - "Letöltés folyamatban" - "A letöltés befejeződött" - "Nem sikerült a letöltés" diff --git a/library/ui/src/main/res/values-in/strings.xml b/library/ui/src/main/res/values-in/strings.xml index 5bac891438..e40bcc4b77 100644 --- a/library/ui/src/main/res/values-in/strings.xml +++ b/library/ui/src/main/res/values-in/strings.xml @@ -28,8 +28,4 @@ "Ulangi semua" "Acak" "Mode layar penuh" - "Download masih dalam antrean" - "Mendownload" - "Download selesai" - "Download gagal" diff --git a/library/ui/src/main/res/values-it/strings.xml b/library/ui/src/main/res/values-it/strings.xml index 7ce5dc5f45..aa5cc51f5d 100644 --- a/library/ui/src/main/res/values-it/strings.xml +++ b/library/ui/src/main/res/values-it/strings.xml @@ -28,8 +28,4 @@ "Ripeti tutto" "Riproduzione casuale" "Modalità a schermo intero" - "Download aggiunto alla coda" - "Download" - "Download completato" - "Download non riuscito" diff --git a/library/ui/src/main/res/values-iw/strings.xml b/library/ui/src/main/res/values-iw/strings.xml index 036a124adc..cadde65222 100644 --- a/library/ui/src/main/res/values-iw/strings.xml +++ b/library/ui/src/main/res/values-iw/strings.xml @@ -28,8 +28,4 @@ "חזור על הכול" "ערבוב" "מצב מסך מלא" - "ההורדה עדיין לא התחילה" - "מתבצעת הורדה" - "ההורדה הושלמה" - "ההורדה לא הושלמה" diff --git a/library/ui/src/main/res/values-ja/strings.xml b/library/ui/src/main/res/values-ja/strings.xml index d388bf6f05..ce5908c4b3 100644 --- a/library/ui/src/main/res/values-ja/strings.xml +++ b/library/ui/src/main/res/values-ja/strings.xml @@ -28,8 +28,4 @@ "全曲をリピート" "シャッフル" "全画面モード" - "ダウンロードを待機しています" - "ダウンロードしています" - "ダウンロードが完了しました" - "ダウンロードに失敗しました" diff --git a/library/ui/src/main/res/values-ko/strings.xml b/library/ui/src/main/res/values-ko/strings.xml index 3acf944f22..b130bd16e2 100644 --- a/library/ui/src/main/res/values-ko/strings.xml +++ b/library/ui/src/main/res/values-ko/strings.xml @@ -28,8 +28,4 @@ "모두 반복" "셔플" "전체화면 모드" - "다운로드 대기 중" - "다운로드하는 중" - "다운로드 완료" - "다운로드 실패" diff --git a/library/ui/src/main/res/values-lt/strings.xml b/library/ui/src/main/res/values-lt/strings.xml index f8b5366eb5..fa3d0bb9fd 100644 --- a/library/ui/src/main/res/values-lt/strings.xml +++ b/library/ui/src/main/res/values-lt/strings.xml @@ -28,8 +28,4 @@ "Kartoti viską" "Maišyti" "Viso ekrano režimas" - "Atsisiunč. elem. laukia eilėje" - "Atsisiunčiama" - "Atsisiuntimo procesas baigtas" - "Nepavyko atsisiųsti" diff --git a/library/ui/src/main/res/values-lv/strings.xml b/library/ui/src/main/res/values-lv/strings.xml index 3f5f696f86..8e6eb79c18 100644 --- a/library/ui/src/main/res/values-lv/strings.xml +++ b/library/ui/src/main/res/values-lv/strings.xml @@ -28,8 +28,4 @@ "Atkārtot visu" "Atskaņot jauktā secībā" "Pilnekrāna režīms" - "Lejupielāde gaida rindā" - "Notiek lejupielāde" - "Lejupielāde ir pabeigta" - "Lejupielāde neizdevās" diff --git a/library/ui/src/main/res/values-nb/strings.xml b/library/ui/src/main/res/values-nb/strings.xml index 7e7d580e63..cba662f87a 100644 --- a/library/ui/src/main/res/values-nb/strings.xml +++ b/library/ui/src/main/res/values-nb/strings.xml @@ -28,8 +28,4 @@ "Gjenta alle" "Tilfeldig rekkefølge" "Fullskjermmodus" - "Nedlasting står i kø" - "Laster ned" - "Nedlastingen er fullført" - "Nedlastingen mislyktes" diff --git a/library/ui/src/main/res/values-nl/strings.xml b/library/ui/src/main/res/values-nl/strings.xml index ad24b79908..b3a50ceafc 100644 --- a/library/ui/src/main/res/values-nl/strings.xml +++ b/library/ui/src/main/res/values-nl/strings.xml @@ -28,8 +28,4 @@ "Alles herhalen" "Shuffle" "Modus \'Volledig scherm\'" - "Download in de wachtrij" - "Downloaden" - "Downloaden voltooid" - "Downloaden mislukt" diff --git a/library/ui/src/main/res/values-pl/strings.xml b/library/ui/src/main/res/values-pl/strings.xml index 45f930a18c..4eede06c66 100644 --- a/library/ui/src/main/res/values-pl/strings.xml +++ b/library/ui/src/main/res/values-pl/strings.xml @@ -28,8 +28,4 @@ "Powtórz wszystkie" "Odtwarzanie losowe" "Tryb pełnoekranowy" - "W kolejce pobierania" - "Pobieranie" - "Zakończono pobieranie" - "Nie udało się pobrać" diff --git a/library/ui/src/main/res/values-pt-rPT/strings.xml b/library/ui/src/main/res/values-pt-rPT/strings.xml index d728f17347..e8ed642950 100644 --- a/library/ui/src/main/res/values-pt-rPT/strings.xml +++ b/library/ui/src/main/res/values-pt-rPT/strings.xml @@ -28,8 +28,4 @@ "Repetir tudo" "Reproduzir aleatoriamente" "Modo de ecrã inteiro" - "Transfer. em fila de espera" - "A transferir…" - "Transferência concluída" - "Falha na transferência" diff --git a/library/ui/src/main/res/values-pt/strings.xml b/library/ui/src/main/res/values-pt/strings.xml index 90595f0f81..cba63695e5 100644 --- a/library/ui/src/main/res/values-pt/strings.xml +++ b/library/ui/src/main/res/values-pt/strings.xml @@ -28,8 +28,4 @@ "Repetir tudo" "Aleatório" "Modo de tela cheia" - "Item na fila de download" - "Fazendo download" - "Download concluído" - "Falha no download" diff --git a/library/ui/src/main/res/values-ro/strings.xml b/library/ui/src/main/res/values-ro/strings.xml index 2f173f53ff..6e4fb04c4b 100644 --- a/library/ui/src/main/res/values-ro/strings.xml +++ b/library/ui/src/main/res/values-ro/strings.xml @@ -28,8 +28,4 @@ "Repetați-le pe toate" "Redați aleatoriu" "Modul Ecran complet" - "Descărcarea este în lista de așteptare" - "Se descarcă" - "Descărcarea a fost finalizată" - "Descărcarea nu a reușit" diff --git a/library/ui/src/main/res/values-ru/strings.xml b/library/ui/src/main/res/values-ru/strings.xml index 1daee396cc..090ddc574b 100644 --- a/library/ui/src/main/res/values-ru/strings.xml +++ b/library/ui/src/main/res/values-ru/strings.xml @@ -28,8 +28,4 @@ "Повторять все" "Перемешать" "Полноэкранный режим" - "В очереди на скачивание" - "Загрузка файлов" - "Скачивание завершено" - "Ошибка скачивания" diff --git a/library/ui/src/main/res/values-sk/strings.xml b/library/ui/src/main/res/values-sk/strings.xml index 15cc6ab00e..6aa70de8e3 100644 --- a/library/ui/src/main/res/values-sk/strings.xml +++ b/library/ui/src/main/res/values-sk/strings.xml @@ -28,8 +28,4 @@ "Opakovať všetko" "Náhodne prehrávať" "Režim celej obrazovky" - "Sťahovanie je v poradí" - "Sťahuje sa" - "Sťahovanie bolo dokončené" - "Nepodarilo sa stiahnuť" diff --git a/library/ui/src/main/res/values-sl/strings.xml b/library/ui/src/main/res/values-sl/strings.xml index 19ad13aa81..8842718de0 100644 --- a/library/ui/src/main/res/values-sl/strings.xml +++ b/library/ui/src/main/res/values-sl/strings.xml @@ -28,8 +28,4 @@ "Ponavljanje vseh" "Naključno predvajanje" "Celozaslonski način" - "Prenos je v čakalni vrsti" - "Prenašanje" - "Prenos je končan" - "Prenos ni uspel" diff --git a/library/ui/src/main/res/values-sr/strings.xml b/library/ui/src/main/res/values-sr/strings.xml index 71fbc7e5c2..a1c110fe60 100644 --- a/library/ui/src/main/res/values-sr/strings.xml +++ b/library/ui/src/main/res/values-sr/strings.xml @@ -28,8 +28,4 @@ "Понови све" "Пусти насумично" "Режим целог екрана" - "Преузимање је на чекању" - "Преузимање" - "Преузимање је завршено" - "Преузимање није успело" diff --git a/library/ui/src/main/res/values-sv/strings.xml b/library/ui/src/main/res/values-sv/strings.xml index 1f0793ed23..79ee9f004a 100644 --- a/library/ui/src/main/res/values-sv/strings.xml +++ b/library/ui/src/main/res/values-sv/strings.xml @@ -28,8 +28,4 @@ "Upprepa alla" "Blanda spår" "Helskärmsläge" - "Nedladdningen har köplacerats" - "Laddar ned" - "Nedladdningen är klar" - "Nedladdningen misslyckades" diff --git a/library/ui/src/main/res/values-sw/strings.xml b/library/ui/src/main/res/values-sw/strings.xml index eddaaf8264..c4ba198737 100644 --- a/library/ui/src/main/res/values-sw/strings.xml +++ b/library/ui/src/main/res/values-sw/strings.xml @@ -28,8 +28,4 @@ "Rudia zote" "Changanya" "Hali ya skrini nzima" - "Inasubiri kupakuliwa" - "Inapakua" - "Imepakuliwa" - "Imeshindwa kupakua" diff --git a/library/ui/src/main/res/values-th/strings.xml b/library/ui/src/main/res/values-th/strings.xml index d235e0349a..f1953fffb5 100644 --- a/library/ui/src/main/res/values-th/strings.xml +++ b/library/ui/src/main/res/values-th/strings.xml @@ -28,8 +28,4 @@ "เล่นซ้ำทั้งหมด" "สุ่ม" "โหมดเต็มหน้าจอ" - "การดาวน์โหลดอยู่ในคิว" - "กำลังดาวน์โหลด" - "การดาวน์โหลดเสร็จสมบูรณ์" - "การดาวน์โหลดล้มเหลว" diff --git a/library/ui/src/main/res/values-tl/strings.xml b/library/ui/src/main/res/values-tl/strings.xml index b478e9d52f..4ca10098db 100644 --- a/library/ui/src/main/res/values-tl/strings.xml +++ b/library/ui/src/main/res/values-tl/strings.xml @@ -28,8 +28,4 @@ "Ulitin lahat" "I-shuffle" "Fullscreen mode" - "Naka-queue ang download" - "Nagda-download" - "Tapos na ang pag-download" - "Hindi na-download" diff --git a/library/ui/src/main/res/values-tr/strings.xml b/library/ui/src/main/res/values-tr/strings.xml index e9251e4c79..9db6e7e1d5 100644 --- a/library/ui/src/main/res/values-tr/strings.xml +++ b/library/ui/src/main/res/values-tr/strings.xml @@ -28,8 +28,4 @@ "Tümünü tekrarla" "Karıştır" "Tam ekran modu" - "İndirme işlemi sıraya alındı" - "İndiriliyor" - "İndirme işlemi tamamlandı" - "İndirilemedi" diff --git a/library/ui/src/main/res/values-uk/strings.xml b/library/ui/src/main/res/values-uk/strings.xml index e6c2289828..da719a6206 100644 --- a/library/ui/src/main/res/values-uk/strings.xml +++ b/library/ui/src/main/res/values-uk/strings.xml @@ -28,8 +28,4 @@ "Повторити всі" "Перемішати" "Повноекранний режим" - "Завантаження розміщено в черзі" - "Завантажується" - "Завантаження завершено" - "Не вдалося завантажити" diff --git a/library/ui/src/main/res/values-vi/strings.xml b/library/ui/src/main/res/values-vi/strings.xml index 939206b7b9..e947c04c79 100644 --- a/library/ui/src/main/res/values-vi/strings.xml +++ b/library/ui/src/main/res/values-vi/strings.xml @@ -28,8 +28,4 @@ "Lặp lại tất cả" "Phát ngẫu nhiên" "Chế độ toàn màn hình" - "Đã đưa tài nguyên đã tải xuống vào hàng đợi" - "Đang tải xuống" - "Đã hoàn tất tải xuống" - "Không tải xuống được" diff --git a/library/ui/src/main/res/values-zh-rCN/strings.xml b/library/ui/src/main/res/values-zh-rCN/strings.xml index f2749db0af..f9598c8f2e 100644 --- a/library/ui/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui/src/main/res/values-zh-rCN/strings.xml @@ -28,8 +28,4 @@ "全部重复播放" "随机播放" "全屏模式" - "已加入待下载队列" - "正在下载" - "下载完毕" - "下载失败" diff --git a/library/ui/src/main/res/values-zh-rHK/strings.xml b/library/ui/src/main/res/values-zh-rHK/strings.xml index fadf8bf976..fa6b3f2c74 100644 --- a/library/ui/src/main/res/values-zh-rHK/strings.xml +++ b/library/ui/src/main/res/values-zh-rHK/strings.xml @@ -28,8 +28,4 @@ "全部重複播放" "隨機播放" "全螢幕模式" - "已加入下載列" - "正在下載" - "下載完畢" - "下載失敗" diff --git a/library/ui/src/main/res/values-zh-rTW/strings.xml b/library/ui/src/main/res/values-zh-rTW/strings.xml index 0679f360d1..df02675fdf 100644 --- a/library/ui/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui/src/main/res/values-zh-rTW/strings.xml @@ -28,8 +28,4 @@ "重複播放所有項目" "隨機播放" "全螢幕模式" - "已排入下載佇列" - "下載中" - "下載完成" - "無法下載" diff --git a/library/ui/src/main/res/values-zu/strings.xml b/library/ui/src/main/res/values-zu/strings.xml index 5eb5bf9807..45ebce44aa 100644 --- a/library/ui/src/main/res/values-zu/strings.xml +++ b/library/ui/src/main/res/values-zu/strings.xml @@ -28,8 +28,4 @@ "Phinda konke" "Shova" "Imodi yesikrini esigcwele" - "Ukulanda kukulayini" - "Iyalanda" - "Ukulanda kuqedile" - "Ukulanda kuhlulekile" diff --git a/library/ui/src/main/res/values/strings.xml b/library/ui/src/main/res/values/strings.xml index d92c615ad0..007309deda 100644 --- a/library/ui/src/main/res/values/strings.xml +++ b/library/ui/src/main/res/values/strings.xml @@ -36,14 +36,4 @@ Repeat all Shuffle - - Fullscreen mode - - Download queued - - Downloading - - Download completed - - Download failed From c851ee9388de6db80f767dfeb8c0ce9e9508b7a8 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 20 Feb 2018 05:46:26 -0800 Subject: [PATCH 46/53] Fix robolectric tests when running with gradle ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=186291489 --- constants.gradle | 4 ++-- library/core/src/test/resources/robolectric.properties | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 library/core/src/test/resources/robolectric.properties diff --git a/constants.gradle b/constants.gradle index e0e8bbcc69..b02e2d4c37 100644 --- a/constants.gradle +++ b/constants.gradle @@ -26,8 +26,8 @@ project.ext { dexmakerVersion = '1.2' mockitoVersion = '1.9.5' junitVersion = '4.12' - truthVersion = '0.35' - robolectricVersion = '3.4.2' + truthVersion = '0.39' + robolectricVersion = '3.7.1' releaseVersion = '2.7.0' modulePrefix = ':' if (gradle.ext.has('exoplayerModulePrefix')) { diff --git a/library/core/src/test/resources/robolectric.properties b/library/core/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..2f3210368e --- /dev/null +++ b/library/core/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +manifest=src/test/AndroidManifest.xml From 5e6a2e57afd83340d8abf1d48f2f2517a13d0735 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 20 Feb 2018 14:36:11 +0000 Subject: [PATCH 47/53] Fix issue template --- ISSUE_TEMPLATE | 2 -- 1 file changed, 2 deletions(-) diff --git a/ISSUE_TEMPLATE b/ISSUE_TEMPLATE index 1b912312d1..e85c0c28c7 100644 --- a/ISSUE_TEMPLATE +++ b/ISSUE_TEMPLATE @@ -1,5 +1,3 @@ -*** ISSUES THAT IGNORE THIS TEMPLATE WILL BE CLOSED WITHOUT INVESTIGATION *** - Before filing an issue: ----------------------- - Search existing issues, including issues that are closed. From 93a40d104895a4dbf49970b05ef2f19407874878 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 20 Feb 2018 08:21:19 -0800 Subject: [PATCH 48/53] Fix bad @see Javadoc in CastPlayer ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=186305613 --- .../exoplayer2/ext/cast/CastPlayer.java | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index d7756b8272..50c883c3f6 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -195,14 +195,14 @@ public final class CastPlayer implements Player { } /** - * Inserts a sequence of items into the media queue. If no media queue or period with id - * {@code periodId} exist, does nothing. + * Inserts a sequence of items into the media queue. If no media queue or period with id {@code + * periodId} exist, does nothing. * - * @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item + * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item * that will follow immediately after the inserted items. * @param items The items to insert. - * @return The Cast {@code PendingResult}, or null if no media queue or no period with id - * {@code periodId} exist. + * @return The Cast {@code PendingResult}, or null if no media queue or no period with id {@code + * periodId} exist. */ public PendingResult addItems(int periodId, MediaQueueItem... items) { if (getMediaStatus() != null && (periodId == MediaQueueItem.INVALID_ITEM_ID @@ -216,10 +216,10 @@ public final class CastPlayer implements Player { * Removes an item from the media queue. If no media queue or period with id {@code periodId} * exist, does nothing. * - * @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item + * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item * to remove. - * @return The Cast {@code PendingResult}, or null if no media queue or no period with id - * {@code periodId} exist. + * @return The Cast {@code PendingResult}, or null if no media queue or no period with id {@code + * periodId} exist. */ public PendingResult removeItem(int periodId) { if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) { @@ -229,16 +229,15 @@ public final class CastPlayer implements Player { } /** - * Moves an existing item within the media queue. If no media queue or period with id - * {@code periodId} exist, does nothing. + * Moves an existing item within the media queue. If no media queue or period with id {@code + * periodId} exist, does nothing. * - * @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item + * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item * to move. - * @param newIndex The target index of the item in the media queue. Must be in the range - * 0 <= index < {@link Timeline#getPeriodCount()}, as provided by - * {@link #getCurrentTimeline()}. - * @return The Cast {@code PendingResult}, or null if no media queue or no period with id - * {@code periodId} exist. + * @param newIndex The target index of the item in the media queue. Must be in the range 0 <= + * index < {@link Timeline#getPeriodCount()}, as provided by {@link #getCurrentTimeline()}. + * @return The Cast {@code PendingResult}, or null if no media queue or no period with id {@code + * periodId} exist. */ public PendingResult moveItem(int periodId, int newIndex) { Assertions.checkArgument(newIndex >= 0 && newIndex < currentTimeline.getPeriodCount()); @@ -252,7 +251,7 @@ public final class CastPlayer implements Player { * Returns the item that corresponds to the period with the given id, or null if no media queue or * period with id {@code periodId} exist. * - * @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item + * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item * to get. * @return The item that corresponds to the period with the given id, or null if no media queue or * period with id {@code periodId} exist. From f69cc191ab046b8f8c786033f7b77ed5e0137e4b Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 21 Feb 2018 07:14:25 -0800 Subject: [PATCH 49/53] Drop EMSG atoms before MOOV ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=186454715 --- .../exoplayer2/extractor/mp4/FragmentedMp4Extractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 9a70dfbf90..7e40f6d2ee 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -549,7 +549,7 @@ public final class FragmentedMp4Extractor implements Extractor { * Parses an emsg atom (defined in 23009-1). */ private void onEmsgLeafAtomRead(ParsableByteArray atom) { - if (emsgTrackOutputs.length == 0) { + if (emsgTrackOutputs == null || emsgTrackOutputs.length == 0) { return; } From 2cff596162fbdca51230e4e339967399ab174472 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 22 Feb 2018 10:01:04 +0000 Subject: [PATCH 50/53] Allow audio position to jump on first input buffer Allow the position to jump on receiving the first presentable input buffer, if and only if the buffer timestamp differs significantly from what was expected. This prevents a stuck buffering case for streams that are thought to start at t=0, but actually start at t=large_value. --- .../audio/MediaCodecAudioRenderer.java | 16 ++++++++++++++++ .../audio/SimpleDecoderAudioRenderer.java | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index a7063e5a7f..33a67554a5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher; +import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; @@ -70,6 +71,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media private int encoderDelay; private int encoderPadding; private long currentPositionUs; + private boolean allowFirstBufferPositionDiscontinuity; private boolean allowPositionDiscontinuity; /** @@ -366,6 +368,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media super.onPositionReset(positionUs, joining); audioSink.reset(); currentPositionUs = positionUs; + allowFirstBufferPositionDiscontinuity = true; allowPositionDiscontinuity = true; } @@ -424,6 +427,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media return audioSink.getPlaybackParameters(); } + @Override + protected void onQueueInputBuffer(DecoderInputBuffer buffer) { + if (allowFirstBufferPositionDiscontinuity && !buffer.isDecodeOnly()) { + // TODO: Remove this hack once we have a proper fix for [Internal: b/71876314]. + // Allow the position to jump if the first presentable input buffer has a timestamp that + // differs significantly from what was expected. + if (Math.abs(buffer.timeUs - currentPositionUs) > 500000) { + currentPositionUs = buffer.timeUs; + } + allowFirstBufferPositionDiscontinuity = false; + } + } + @Override protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index 2f5e7bcf97..83c33ee6d7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -105,6 +105,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements private boolean audioTrackNeedsConfigure; private long currentPositionUs; + private boolean allowFirstBufferPositionDiscontinuity; private boolean allowPositionDiscontinuity; private boolean inputStreamEnded; private boolean outputStreamEnded; @@ -416,6 +417,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements return false; } inputBuffer.flip(); + onQueueInputBuffer(inputBuffer); decoder.queueInputBuffer(inputBuffer); decoderReceivedBuffers = true; decoderCounters.inputBufferCount++; @@ -504,6 +506,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { audioSink.reset(); currentPositionUs = positionUs; + allowFirstBufferPositionDiscontinuity = true; allowPositionDiscontinuity = true; inputStreamEnded = false; outputStreamEnded = false; @@ -654,6 +657,18 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements eventDispatcher.inputFormatChanged(newFormat); } + private void onQueueInputBuffer(DecoderInputBuffer buffer) { + if (allowFirstBufferPositionDiscontinuity && !buffer.isDecodeOnly()) { + // TODO: Remove this hack once we have a proper fix for [Internal: b/71876314]. + // Allow the position to jump if the first presentable input buffer has a timestamp that + // differs significantly from what was expected. + if (Math.abs(buffer.timeUs - currentPositionUs) > 500000) { + currentPositionUs = buffer.timeUs; + } + allowFirstBufferPositionDiscontinuity = false; + } + } + private void updateCurrentPosition() { long newCurrentPositionUs = audioSink.getCurrentPositionUs(isEnded()); if (newCurrentPositionUs != AudioSink.CURRENT_POSITION_NOT_SET) { From 1a5d985e2ad37b4d5fdc18ecd20b5a914d0fdd57 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 22 Feb 2018 10:24:11 +0000 Subject: [PATCH 51/53] Remove stray translations --- library/ui/src/main/res/values-af/strings.xml | 1 - library/ui/src/main/res/values-am/strings.xml | 1 - library/ui/src/main/res/values-ar/strings.xml | 1 - library/ui/src/main/res/values-b+sr+Latn/strings.xml | 1 - library/ui/src/main/res/values-bg/strings.xml | 1 - library/ui/src/main/res/values-ca/strings.xml | 1 - library/ui/src/main/res/values-cs/strings.xml | 1 - library/ui/src/main/res/values-da/strings.xml | 1 - library/ui/src/main/res/values-de/strings.xml | 1 - library/ui/src/main/res/values-el/strings.xml | 1 - library/ui/src/main/res/values-en-rAU/strings.xml | 1 - library/ui/src/main/res/values-en-rGB/strings.xml | 1 - library/ui/src/main/res/values-en-rIN/strings.xml | 1 - library/ui/src/main/res/values-es-rUS/strings.xml | 1 - library/ui/src/main/res/values-es/strings.xml | 1 - library/ui/src/main/res/values-fa/strings.xml | 1 - library/ui/src/main/res/values-fi/strings.xml | 1 - library/ui/src/main/res/values-fr-rCA/strings.xml | 1 - library/ui/src/main/res/values-fr/strings.xml | 1 - library/ui/src/main/res/values-hi/strings.xml | 1 - library/ui/src/main/res/values-hr/strings.xml | 1 - library/ui/src/main/res/values-hu/strings.xml | 1 - library/ui/src/main/res/values-in/strings.xml | 1 - library/ui/src/main/res/values-it/strings.xml | 1 - library/ui/src/main/res/values-iw/strings.xml | 1 - library/ui/src/main/res/values-ja/strings.xml | 1 - library/ui/src/main/res/values-ko/strings.xml | 1 - library/ui/src/main/res/values-lt/strings.xml | 1 - library/ui/src/main/res/values-lv/strings.xml | 1 - library/ui/src/main/res/values-nb/strings.xml | 1 - library/ui/src/main/res/values-nl/strings.xml | 1 - library/ui/src/main/res/values-pl/strings.xml | 1 - library/ui/src/main/res/values-pt-rPT/strings.xml | 1 - library/ui/src/main/res/values-pt/strings.xml | 1 - library/ui/src/main/res/values-ro/strings.xml | 1 - library/ui/src/main/res/values-ru/strings.xml | 1 - library/ui/src/main/res/values-sk/strings.xml | 1 - library/ui/src/main/res/values-sl/strings.xml | 1 - library/ui/src/main/res/values-sr/strings.xml | 1 - library/ui/src/main/res/values-sv/strings.xml | 1 - library/ui/src/main/res/values-sw/strings.xml | 1 - library/ui/src/main/res/values-th/strings.xml | 1 - library/ui/src/main/res/values-tl/strings.xml | 1 - library/ui/src/main/res/values-tr/strings.xml | 1 - library/ui/src/main/res/values-uk/strings.xml | 1 - library/ui/src/main/res/values-vi/strings.xml | 1 - library/ui/src/main/res/values-zh-rCN/strings.xml | 1 - library/ui/src/main/res/values-zh-rHK/strings.xml | 1 - library/ui/src/main/res/values-zh-rTW/strings.xml | 1 - library/ui/src/main/res/values-zu/strings.xml | 1 - 50 files changed, 50 deletions(-) diff --git a/library/ui/src/main/res/values-af/strings.xml b/library/ui/src/main/res/values-af/strings.xml index 9019288efb..f8b249d57d 100644 --- a/library/ui/src/main/res/values-af/strings.xml +++ b/library/ui/src/main/res/values-af/strings.xml @@ -27,5 +27,4 @@ "Herhaal een" "Herhaal alles" "Skommel" - "Volskermmodus" diff --git a/library/ui/src/main/res/values-am/strings.xml b/library/ui/src/main/res/values-am/strings.xml index da1ba64b04..38e805eef6 100644 --- a/library/ui/src/main/res/values-am/strings.xml +++ b/library/ui/src/main/res/values-am/strings.xml @@ -27,5 +27,4 @@ "አንድ ድገም" "ሁሉንም ድገም" "በውዝ" - "የሙሉ ማያ ሁነታ" diff --git a/library/ui/src/main/res/values-ar/strings.xml b/library/ui/src/main/res/values-ar/strings.xml index 9e519ed5ec..98c1b555a2 100644 --- a/library/ui/src/main/res/values-ar/strings.xml +++ b/library/ui/src/main/res/values-ar/strings.xml @@ -27,5 +27,4 @@ "تكرار مقطع صوتي واحد" "تكرار الكل" "ترتيب عشوائي" - "وضع ملء الشاشة" diff --git a/library/ui/src/main/res/values-b+sr+Latn/strings.xml b/library/ui/src/main/res/values-b+sr+Latn/strings.xml index 3647d4a724..e66605b599 100644 --- a/library/ui/src/main/res/values-b+sr+Latn/strings.xml +++ b/library/ui/src/main/res/values-b+sr+Latn/strings.xml @@ -27,5 +27,4 @@ "Ponovi jednu" "Ponovi sve" "Pusti nasumično" - "Režim celog ekrana" diff --git a/library/ui/src/main/res/values-bg/strings.xml b/library/ui/src/main/res/values-bg/strings.xml index f672ffd773..6eb4e8881a 100644 --- a/library/ui/src/main/res/values-bg/strings.xml +++ b/library/ui/src/main/res/values-bg/strings.xml @@ -27,5 +27,4 @@ "Повтаряне на един елемент" "Повтаряне на всички" "Разбъркване" - "Режим на цял екран" diff --git a/library/ui/src/main/res/values-ca/strings.xml b/library/ui/src/main/res/values-ca/strings.xml index 8427872437..0e2512c061 100644 --- a/library/ui/src/main/res/values-ca/strings.xml +++ b/library/ui/src/main/res/values-ca/strings.xml @@ -27,5 +27,4 @@ "Repeteix una" "Repeteix tot" "Reprodueix aleatòriament" - "Mode de pantalla completa" diff --git a/library/ui/src/main/res/values-cs/strings.xml b/library/ui/src/main/res/values-cs/strings.xml index c4e78ad397..e397968647 100644 --- a/library/ui/src/main/res/values-cs/strings.xml +++ b/library/ui/src/main/res/values-cs/strings.xml @@ -27,5 +27,4 @@ "Opakovat jednu" "Opakovat vše" "Náhodně" - "Režim celé obrazovky" diff --git a/library/ui/src/main/res/values-da/strings.xml b/library/ui/src/main/res/values-da/strings.xml index b75620deb5..7a909daba4 100644 --- a/library/ui/src/main/res/values-da/strings.xml +++ b/library/ui/src/main/res/values-da/strings.xml @@ -27,5 +27,4 @@ "Gentag én" "Gentag alle" "Bland" - "Fuld skærm" diff --git a/library/ui/src/main/res/values-de/strings.xml b/library/ui/src/main/res/values-de/strings.xml index 6f187a16af..b40fce1fb5 100644 --- a/library/ui/src/main/res/values-de/strings.xml +++ b/library/ui/src/main/res/values-de/strings.xml @@ -27,5 +27,4 @@ "Einen wiederholen" "Alle wiederholen" "Zufallsmix" - "Vollbildmodus" diff --git a/library/ui/src/main/res/values-el/strings.xml b/library/ui/src/main/res/values-el/strings.xml index 990a0aa457..4c86019d47 100644 --- a/library/ui/src/main/res/values-el/strings.xml +++ b/library/ui/src/main/res/values-el/strings.xml @@ -27,5 +27,4 @@ "Επανάληψη ενός κομματιού" "Επανάληψη όλων" "Τυχαία αναπαραγωγή" - "Λειτουργία πλήρους οθόνης" diff --git a/library/ui/src/main/res/values-en-rAU/strings.xml b/library/ui/src/main/res/values-en-rAU/strings.xml index 62f8e77e91..2a73a3e98e 100644 --- a/library/ui/src/main/res/values-en-rAU/strings.xml +++ b/library/ui/src/main/res/values-en-rAU/strings.xml @@ -27,5 +27,4 @@ "Repeat one" "Repeat all" "Shuffle" - "Full-screen mode" diff --git a/library/ui/src/main/res/values-en-rGB/strings.xml b/library/ui/src/main/res/values-en-rGB/strings.xml index 62f8e77e91..2a73a3e98e 100644 --- a/library/ui/src/main/res/values-en-rGB/strings.xml +++ b/library/ui/src/main/res/values-en-rGB/strings.xml @@ -27,5 +27,4 @@ "Repeat one" "Repeat all" "Shuffle" - "Full-screen mode" diff --git a/library/ui/src/main/res/values-en-rIN/strings.xml b/library/ui/src/main/res/values-en-rIN/strings.xml index 62f8e77e91..2a73a3e98e 100644 --- a/library/ui/src/main/res/values-en-rIN/strings.xml +++ b/library/ui/src/main/res/values-en-rIN/strings.xml @@ -27,5 +27,4 @@ "Repeat one" "Repeat all" "Shuffle" - "Full-screen mode" diff --git a/library/ui/src/main/res/values-es-rUS/strings.xml b/library/ui/src/main/res/values-es-rUS/strings.xml index 8022f3a459..5fca31959b 100644 --- a/library/ui/src/main/res/values-es-rUS/strings.xml +++ b/library/ui/src/main/res/values-es-rUS/strings.xml @@ -27,5 +27,4 @@ "Repetir uno" "Repetir todo" "Reproducir aleatoriamente" - "Modo de pantalla completa" diff --git a/library/ui/src/main/res/values-es/strings.xml b/library/ui/src/main/res/values-es/strings.xml index 90ce129f5b..b4f76b2828 100644 --- a/library/ui/src/main/res/values-es/strings.xml +++ b/library/ui/src/main/res/values-es/strings.xml @@ -27,5 +27,4 @@ "Repetir uno" "Repetir todo" "Reproducir aleatoriamente" - "Modo de pantalla completa" diff --git a/library/ui/src/main/res/values-fa/strings.xml b/library/ui/src/main/res/values-fa/strings.xml index f6c9291824..097680ae6b 100644 --- a/library/ui/src/main/res/values-fa/strings.xml +++ b/library/ui/src/main/res/values-fa/strings.xml @@ -27,5 +27,4 @@ "یکبار تکرار" "تکرار همه" "درهم" - "حالت تمام‌صفحه" diff --git a/library/ui/src/main/res/values-fi/strings.xml b/library/ui/src/main/res/values-fi/strings.xml index c5a1eb1d25..ba93ad4d1e 100644 --- a/library/ui/src/main/res/values-fi/strings.xml +++ b/library/ui/src/main/res/values-fi/strings.xml @@ -27,5 +27,4 @@ "Toista yksi uudelleen" "Toista kaikki uudelleen" "Satunnaistoisto" - "Koko näytön tila" diff --git a/library/ui/src/main/res/values-fr-rCA/strings.xml b/library/ui/src/main/res/values-fr-rCA/strings.xml index 1e7fc40e89..f08bd3d680 100644 --- a/library/ui/src/main/res/values-fr-rCA/strings.xml +++ b/library/ui/src/main/res/values-fr-rCA/strings.xml @@ -27,5 +27,4 @@ "Lire une chanson en boucle" "Tout lire en boucle" "Lecture aléatoire" - "Mode Plein écran" diff --git a/library/ui/src/main/res/values-fr/strings.xml b/library/ui/src/main/res/values-fr/strings.xml index ec5fadfdfb..41dc7e9605 100644 --- a/library/ui/src/main/res/values-fr/strings.xml +++ b/library/ui/src/main/res/values-fr/strings.xml @@ -27,5 +27,4 @@ "Lire un titre en boucle" "Tout lire en boucle" "Aléatoire" - "Mode plein écran" diff --git a/library/ui/src/main/res/values-hi/strings.xml b/library/ui/src/main/res/values-hi/strings.xml index 2fdf625381..a3a0c76b80 100644 --- a/library/ui/src/main/res/values-hi/strings.xml +++ b/library/ui/src/main/res/values-hi/strings.xml @@ -27,5 +27,4 @@ "एक को दोहराएं" "सभी को दोहराएं" "शफ़ल करें" - "फ़ुलस्क्रीन मोड" diff --git a/library/ui/src/main/res/values-hr/strings.xml b/library/ui/src/main/res/values-hr/strings.xml index f85777b9b8..41a3c3f2e3 100644 --- a/library/ui/src/main/res/values-hr/strings.xml +++ b/library/ui/src/main/res/values-hr/strings.xml @@ -27,5 +27,4 @@ "Ponovi jedno" "Ponovi sve" "Reproduciraj nasumično" - "Prikaz na cijelom zaslonu" diff --git a/library/ui/src/main/res/values-hu/strings.xml b/library/ui/src/main/res/values-hu/strings.xml index 51358cf68e..3befaa70de 100644 --- a/library/ui/src/main/res/values-hu/strings.xml +++ b/library/ui/src/main/res/values-hu/strings.xml @@ -27,5 +27,4 @@ "Egy szám ismétlése" "Összes szám ismétlése" "Keverés" - "Teljes képernyős mód" diff --git a/library/ui/src/main/res/values-in/strings.xml b/library/ui/src/main/res/values-in/strings.xml index e40bcc4b77..636fbf0bbf 100644 --- a/library/ui/src/main/res/values-in/strings.xml +++ b/library/ui/src/main/res/values-in/strings.xml @@ -27,5 +27,4 @@ "Ulangi 1" "Ulangi semua" "Acak" - "Mode layar penuh" diff --git a/library/ui/src/main/res/values-it/strings.xml b/library/ui/src/main/res/values-it/strings.xml index aa5cc51f5d..a962f25072 100644 --- a/library/ui/src/main/res/values-it/strings.xml +++ b/library/ui/src/main/res/values-it/strings.xml @@ -27,5 +27,4 @@ "Ripeti uno" "Ripeti tutto" "Riproduzione casuale" - "Modalità a schermo intero" diff --git a/library/ui/src/main/res/values-iw/strings.xml b/library/ui/src/main/res/values-iw/strings.xml index cadde65222..56d85b90a5 100644 --- a/library/ui/src/main/res/values-iw/strings.xml +++ b/library/ui/src/main/res/values-iw/strings.xml @@ -27,5 +27,4 @@ "חזור על פריט אחד" "חזור על הכול" "ערבוב" - "מצב מסך מלא" diff --git a/library/ui/src/main/res/values-ja/strings.xml b/library/ui/src/main/res/values-ja/strings.xml index ce5908c4b3..9a6ff4f98f 100644 --- a/library/ui/src/main/res/values-ja/strings.xml +++ b/library/ui/src/main/res/values-ja/strings.xml @@ -27,5 +27,4 @@ "1 曲をリピート" "全曲をリピート" "シャッフル" - "全画面モード" diff --git a/library/ui/src/main/res/values-ko/strings.xml b/library/ui/src/main/res/values-ko/strings.xml index b130bd16e2..59c0bd6647 100644 --- a/library/ui/src/main/res/values-ko/strings.xml +++ b/library/ui/src/main/res/values-ko/strings.xml @@ -27,5 +27,4 @@ "현재 미디어 반복" "모두 반복" "셔플" - "전체화면 모드" diff --git a/library/ui/src/main/res/values-lt/strings.xml b/library/ui/src/main/res/values-lt/strings.xml index fa3d0bb9fd..4b01d4b3cd 100644 --- a/library/ui/src/main/res/values-lt/strings.xml +++ b/library/ui/src/main/res/values-lt/strings.xml @@ -27,5 +27,4 @@ "Kartoti vieną" "Kartoti viską" "Maišyti" - "Viso ekrano režimas" diff --git a/library/ui/src/main/res/values-lv/strings.xml b/library/ui/src/main/res/values-lv/strings.xml index 8e6eb79c18..5920e8b1a2 100644 --- a/library/ui/src/main/res/values-lv/strings.xml +++ b/library/ui/src/main/res/values-lv/strings.xml @@ -27,5 +27,4 @@ "Atkārtot vienu" "Atkārtot visu" "Atskaņot jauktā secībā" - "Pilnekrāna režīms" diff --git a/library/ui/src/main/res/values-nb/strings.xml b/library/ui/src/main/res/values-nb/strings.xml index cba662f87a..da7de1f1dd 100644 --- a/library/ui/src/main/res/values-nb/strings.xml +++ b/library/ui/src/main/res/values-nb/strings.xml @@ -27,5 +27,4 @@ "Gjenta én" "Gjenta alle" "Tilfeldig rekkefølge" - "Fullskjermmodus" diff --git a/library/ui/src/main/res/values-nl/strings.xml b/library/ui/src/main/res/values-nl/strings.xml index b3a50ceafc..3ad2d002ca 100644 --- a/library/ui/src/main/res/values-nl/strings.xml +++ b/library/ui/src/main/res/values-nl/strings.xml @@ -27,5 +27,4 @@ "Eén herhalen" "Alles herhalen" "Shuffle" - "Modus \'Volledig scherm\'" diff --git a/library/ui/src/main/res/values-pl/strings.xml b/library/ui/src/main/res/values-pl/strings.xml index 4eede06c66..9098e2bb9e 100644 --- a/library/ui/src/main/res/values-pl/strings.xml +++ b/library/ui/src/main/res/values-pl/strings.xml @@ -27,5 +27,4 @@ "Powtórz jeden" "Powtórz wszystkie" "Odtwarzanie losowe" - "Tryb pełnoekranowy" diff --git a/library/ui/src/main/res/values-pt-rPT/strings.xml b/library/ui/src/main/res/values-pt-rPT/strings.xml index e8ed642950..ede4093c94 100644 --- a/library/ui/src/main/res/values-pt-rPT/strings.xml +++ b/library/ui/src/main/res/values-pt-rPT/strings.xml @@ -27,5 +27,4 @@ "Repetir um" "Repetir tudo" "Reproduzir aleatoriamente" - "Modo de ecrã inteiro" diff --git a/library/ui/src/main/res/values-pt/strings.xml b/library/ui/src/main/res/values-pt/strings.xml index cba63695e5..f3c2ec2533 100644 --- a/library/ui/src/main/res/values-pt/strings.xml +++ b/library/ui/src/main/res/values-pt/strings.xml @@ -27,5 +27,4 @@ "Repetir uma" "Repetir tudo" "Aleatório" - "Modo de tela cheia" diff --git a/library/ui/src/main/res/values-ro/strings.xml b/library/ui/src/main/res/values-ro/strings.xml index 6e4fb04c4b..398352d0d3 100644 --- a/library/ui/src/main/res/values-ro/strings.xml +++ b/library/ui/src/main/res/values-ro/strings.xml @@ -27,5 +27,4 @@ "Repetați unul" "Repetați-le pe toate" "Redați aleatoriu" - "Modul Ecran complet" diff --git a/library/ui/src/main/res/values-ru/strings.xml b/library/ui/src/main/res/values-ru/strings.xml index 090ddc574b..779a2fc0f2 100644 --- a/library/ui/src/main/res/values-ru/strings.xml +++ b/library/ui/src/main/res/values-ru/strings.xml @@ -27,5 +27,4 @@ "Повторять трек" "Повторять все" "Перемешать" - "Полноэкранный режим" diff --git a/library/ui/src/main/res/values-sk/strings.xml b/library/ui/src/main/res/values-sk/strings.xml index 6aa70de8e3..51dab688c3 100644 --- a/library/ui/src/main/res/values-sk/strings.xml +++ b/library/ui/src/main/res/values-sk/strings.xml @@ -27,5 +27,4 @@ "Opakovať jednu" "Opakovať všetko" "Náhodne prehrávať" - "Režim celej obrazovky" diff --git a/library/ui/src/main/res/values-sl/strings.xml b/library/ui/src/main/res/values-sl/strings.xml index 8842718de0..832277bdc6 100644 --- a/library/ui/src/main/res/values-sl/strings.xml +++ b/library/ui/src/main/res/values-sl/strings.xml @@ -27,5 +27,4 @@ "Ponavljanje ene" "Ponavljanje vseh" "Naključno predvajanje" - "Celozaslonski način" diff --git a/library/ui/src/main/res/values-sr/strings.xml b/library/ui/src/main/res/values-sr/strings.xml index a1c110fe60..8418a51767 100644 --- a/library/ui/src/main/res/values-sr/strings.xml +++ b/library/ui/src/main/res/values-sr/strings.xml @@ -27,5 +27,4 @@ "Понови једну" "Понови све" "Пусти насумично" - "Режим целог екрана" diff --git a/library/ui/src/main/res/values-sv/strings.xml b/library/ui/src/main/res/values-sv/strings.xml index 79ee9f004a..acca62c10e 100644 --- a/library/ui/src/main/res/values-sv/strings.xml +++ b/library/ui/src/main/res/values-sv/strings.xml @@ -27,5 +27,4 @@ "Upprepa en" "Upprepa alla" "Blanda spår" - "Helskärmsläge" diff --git a/library/ui/src/main/res/values-sw/strings.xml b/library/ui/src/main/res/values-sw/strings.xml index c4ba198737..a2441b8270 100644 --- a/library/ui/src/main/res/values-sw/strings.xml +++ b/library/ui/src/main/res/values-sw/strings.xml @@ -27,5 +27,4 @@ "Rudia moja" "Rudia zote" "Changanya" - "Hali ya skrini nzima" diff --git a/library/ui/src/main/res/values-th/strings.xml b/library/ui/src/main/res/values-th/strings.xml index f1953fffb5..02d5fff60c 100644 --- a/library/ui/src/main/res/values-th/strings.xml +++ b/library/ui/src/main/res/values-th/strings.xml @@ -27,5 +27,4 @@ "เล่นซ้ำเพลงเดียว" "เล่นซ้ำทั้งหมด" "สุ่ม" - "โหมดเต็มหน้าจอ" diff --git a/library/ui/src/main/res/values-tl/strings.xml b/library/ui/src/main/res/values-tl/strings.xml index 4ca10098db..b4a3e1136c 100644 --- a/library/ui/src/main/res/values-tl/strings.xml +++ b/library/ui/src/main/res/values-tl/strings.xml @@ -27,5 +27,4 @@ "Mag-ulit ng isa" "Ulitin lahat" "I-shuffle" - "Fullscreen mode" diff --git a/library/ui/src/main/res/values-tr/strings.xml b/library/ui/src/main/res/values-tr/strings.xml index 9db6e7e1d5..e7f6c3c89b 100644 --- a/library/ui/src/main/res/values-tr/strings.xml +++ b/library/ui/src/main/res/values-tr/strings.xml @@ -27,5 +27,4 @@ "Birini tekrarla" "Tümünü tekrarla" "Karıştır" - "Tam ekran modu" diff --git a/library/ui/src/main/res/values-uk/strings.xml b/library/ui/src/main/res/values-uk/strings.xml index da719a6206..114c7d9298 100644 --- a/library/ui/src/main/res/values-uk/strings.xml +++ b/library/ui/src/main/res/values-uk/strings.xml @@ -27,5 +27,4 @@ "Повторити 1" "Повторити всі" "Перемішати" - "Повноекранний режим" diff --git a/library/ui/src/main/res/values-vi/strings.xml b/library/ui/src/main/res/values-vi/strings.xml index e947c04c79..25604f52fa 100644 --- a/library/ui/src/main/res/values-vi/strings.xml +++ b/library/ui/src/main/res/values-vi/strings.xml @@ -27,5 +27,4 @@ "Lặp lại một" "Lặp lại tất cả" "Phát ngẫu nhiên" - "Chế độ toàn màn hình" diff --git a/library/ui/src/main/res/values-zh-rCN/strings.xml b/library/ui/src/main/res/values-zh-rCN/strings.xml index f9598c8f2e..4624a9551e 100644 --- a/library/ui/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui/src/main/res/values-zh-rCN/strings.xml @@ -27,5 +27,4 @@ "重复播放一项" "全部重复播放" "随机播放" - "全屏模式" diff --git a/library/ui/src/main/res/values-zh-rHK/strings.xml b/library/ui/src/main/res/values-zh-rHK/strings.xml index fa6b3f2c74..5957c595a5 100644 --- a/library/ui/src/main/res/values-zh-rHK/strings.xml +++ b/library/ui/src/main/res/values-zh-rHK/strings.xml @@ -27,5 +27,4 @@ "重複播放單一項目" "全部重複播放" "隨機播放" - "全螢幕模式" diff --git a/library/ui/src/main/res/values-zh-rTW/strings.xml b/library/ui/src/main/res/values-zh-rTW/strings.xml index df02675fdf..44275f85ff 100644 --- a/library/ui/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui/src/main/res/values-zh-rTW/strings.xml @@ -27,5 +27,4 @@ "重複播放單一項目" "重複播放所有項目" "隨機播放" - "全螢幕模式" diff --git a/library/ui/src/main/res/values-zu/strings.xml b/library/ui/src/main/res/values-zu/strings.xml index 45ebce44aa..6d8e67154f 100644 --- a/library/ui/src/main/res/values-zu/strings.xml +++ b/library/ui/src/main/res/values-zu/strings.xml @@ -27,5 +27,4 @@ "Phinda okukodwa" "Phinda konke" "Shova" - "Imodi yesikrini esigcwele" From 6054ba418f0e4cebba8944a0c10c9616efa3ac99 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 22 Feb 2018 10:32:17 +0000 Subject: [PATCH 52/53] Fix bad Javadoc --- .../android/exoplayer2/drm/DrmInitData.java | 12 +++++----- .../google/android/exoplayer2/util/Clock.java | 2 +- .../exoplayer2/util/HandlerWrapper.java | 22 +++++++++---------- .../google/android/exoplayer2/util/Util.java | 2 +- .../dash/manifest/DashManifestParser.java | 9 ++++---- .../source/dash/offline/DashDownloader.java | 2 +- .../smoothstreaming/offline/SsDownloader.java | 2 +- 7 files changed, 25 insertions(+), 26 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java index 5662730650..0c7cb0ef01 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java @@ -39,18 +39,18 @@ public final class DrmInitData implements Comparator, Parcelable { *

The result is generated as follows. * *

    - *
      + *
    1. * Include all {@link SchemeData}s from {@code manifestData} where {@link * SchemeData#hasData()} is true. - *
    - *
      + * + *
    1. * Include all {@link SchemeData}s in {@code mediaData} where {@link SchemeData#hasData()} is * true and for which we did not include an entry from the manifest targeting the same UUID. - *
    - *
      + * + *
    1. * If available, the scheme type from the manifest is used. If not, the scheme type from the * media is used. - *
    + * *
* * @param manifestData DRM session acquisition data obtained from the manifest. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Clock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Clock.java index dced6752eb..36fc3b1bf8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Clock.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Clock.java @@ -43,7 +43,7 @@ public interface Clock { * Creates a {@link HandlerWrapper} using a specified looper and a specified callback for handling * messages. * - * @see Handler#Handler(Looper, Handler.Callback). + * @see Handler#Handler(Looper, Handler.Callback) */ HandlerWrapper createHandler(Looper looper, @Nullable Handler.Callback callback); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java b/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java index 3ce93f9370..8f1a6544ca 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java @@ -25,36 +25,36 @@ import android.os.Message; */ public interface HandlerWrapper { - /** @see Handler#getLooper(). */ + /** @see Handler#getLooper() */ Looper getLooper(); - /** @see Handler#obtainMessage(int). */ + /** @see Handler#obtainMessage(int) */ Message obtainMessage(int what); - /** @see Handler#obtainMessage(int, Object). */ + /** @see Handler#obtainMessage(int, Object) */ Message obtainMessage(int what, Object obj); - /** @see Handler#obtainMessage(int, int, int). */ + /** @see Handler#obtainMessage(int, int, int) */ Message obtainMessage(int what, int arg1, int arg2); - /** @see Handler#obtainMessage(int, int, int, Object). */ + /** @see Handler#obtainMessage(int, int, int, Object) */ Message obtainMessage(int what, int arg1, int arg2, Object obj); - /** @see Handler#sendEmptyMessage(int). */ + /** @see Handler#sendEmptyMessage(int) */ boolean sendEmptyMessage(int what); - /** @see Handler#sendEmptyMessageAtTime(int, long). */ + /** @see Handler#sendEmptyMessageAtTime(int, long) */ boolean sendEmptyMessageAtTime(int what, long uptimeMs); - /** @see Handler#removeMessages(int). */ + /** @see Handler#removeMessages(int) */ void removeMessages(int what); - /** @see Handler#removeCallbacksAndMessages(Object). */ + /** @see Handler#removeCallbacksAndMessages(Object) */ void removeCallbacksAndMessages(Object token); - /** @see Handler#post(Runnable). */ + /** @see Handler#post(Runnable) */ boolean post(Runnable runnable); - /** @see Handler#postDelayed(Runnable, long). */ + /** @see Handler#postDelayed(Runnable, long) */ boolean postDelayed(Runnable runnable, long delayMs); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index cd643f2df4..2761cc3ce6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -942,7 +942,7 @@ public final class Util { } /** - * Returns whether {@code encoding} is high resolution (> 16-bit) integer PCM. + * Returns whether {@code encoding} is high resolution (> 16-bit) integer PCM. * * @param encoding The encoding of the audio data. * @return Whether the encoding is high resolution integer PCM. diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 1416e9beeb..aeae720517 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -737,8 +737,8 @@ public class DashManifestParser extends DefaultHandler * @param schemeIdUri The schemeIdUri of the parent EventStream. * @param value The schemeIdUri of the parent EventStream. * @param timescale The timescale of the parent EventStream. - * @param scratchOutputStream A {@link ByteArrayOutputStream} that is used to write serialize data - * in between and tags into. + * @param scratchOutputStream A {@link ByteArrayOutputStream} that's used when parsing event + * objects. * @return The {@link EventMessage} parsed from this EventStream node. * @throws XmlPullParserException If there is any error parsing this node. * @throws IOException If there is any error reading from the underlying input stream. @@ -757,11 +757,10 @@ public class DashManifestParser extends DefaultHandler } /** - * Parses everything between as a byte array string. + * Parses an event object. * * @param xpp The current xml parser. - * @param scratchOutputStream A {@link ByteArrayOutputStream} that is used to write serialize byte - * array data into. + * @param scratchOutputStream A {@link ByteArrayOutputStream} that's used when parsing the object. * @return The serialized byte array. * @throws XmlPullParserException If there is any error parsing this node. * @throws IOException If there is any error reading from the underlying input stream. diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java index 2b2c37be3d..abae7a5f1e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java @@ -54,7 +54,7 @@ import java.util.List; * // Select the first representation of the first adaptation set of the first period * dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)}); * dashDownloader.download(new ProgressListener() { - * @Override + * {@literal @}Override * public void onDownloadProgress(Downloader downloader, float downloadPercentage, * long downloadedBytes) { * // Invoked periodically during the download. diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java index 12cfe2ee36..7988523bed 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java @@ -48,7 +48,7 @@ import java.util.List; * // Select the first track of the first stream element * ssDownloader.selectRepresentations(new TrackKey[] {new TrackKey(0, 0)}); * ssDownloader.download(new ProgressListener() { - * @Override + * {@literal @}Override * public void onDownloadProgress(Downloader downloader, float downloadPercentage, * long downloadedBytes) { * // Invoked periodically during the download. From 166c4ba795ff3b5f2019c400d11bb641a4d9caa9 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 22 Feb 2018 11:43:02 +0000 Subject: [PATCH 53/53] Remove dev release notes from 2.7.0 --- RELEASENOTES.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b71b54575b..f9a03390ef 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,10 +1,5 @@ # Release notes # -### dev-v2 (not yet released) ### - -* Downloading: Add `DownloadService`, `DownloadManager` and - related classes ([#2643](https://github.com/google/ExoPlayer/issues/2643)). - ### 2.7.0 ### * Player interface: