From 75cc5990b03eb11c0492eb8c5c1fef12077aca46 Mon Sep 17 00:00:00 2001 From: kimvde Date: Fri, 12 Feb 2021 11:45:08 +0000 Subject: [PATCH 01/19] Add support for MP4/QuickTime non-full meta atoms PiperOrigin-RevId: 357160215 --- RELEASENOTES.md | 5 +++ .../exoplayer2/extractor/mp4/AtomParsers.java | 31 ++++++++++++++++--- .../extractor/mp4/Mp4Extractor.java | 29 ++++------------- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b2ac9f0197..f0729ab173 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,10 @@ # Release notes +### dev-v2 ### + +* Extractors: + * Add support for MP4 and QuickTime meta atoms that are not full atoms. + ### 2.13.1 (2021-02-12) * Live streaming: diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 162294f1a0..b2db7a6df5 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -145,12 +145,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; * Parses a udta atom. * * @param udtaAtom The udta (user data) atom to decode. - * @param isQuickTime True for QuickTime media. False otherwise. * @return A {@link Pair} containing the metadata from the meta child atom as first value (if * any), and the metadata from the smta child atom as second value (if any). */ public static Pair<@NullableType Metadata, @NullableType Metadata> parseUdta( - Atom.LeafAtom udtaAtom, boolean isQuickTime) { + Atom.LeafAtom udtaAtom) { ParsableByteArray udtaData = udtaAtom.data; udtaData.setPosition(Atom.HEADER_SIZE); @Nullable Metadata metaMetadata = null; @@ -159,8 +158,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; int atomPosition = udtaData.getPosition(); int atomSize = udtaData.readInt(); int atomType = udtaData.readInt(); - // Meta boxes are regular boxes rather than full boxes in QuickTime. Ignore them for now. - if (atomType == Atom.TYPE_meta && !isQuickTime) { + if (atomType == Atom.TYPE_meta) { udtaData.setPosition(atomPosition); metaMetadata = parseUdtaMeta(udtaData, atomPosition + atomSize); } else if (atomType == Atom.TYPE_smta) { @@ -227,6 +225,28 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; return entries.isEmpty() ? null : new Metadata(entries); } + /** + * Possibly skips the version and flags fields (1+3 byte) of a full meta atom. + * + *

Atoms of type {@link Atom#TYPE_meta} are defined to be full atoms which have four additional + * bytes for a version and a flags field (see 4.2 'Object Structure' in ISO/IEC 14496-12:2005). + * QuickTime do not have such a full box structure. Since some of these files are encoded wrongly, + * we can't rely on the file type though. Instead we must check the 4 bytes after the common + * header bytes ourselves. + * + * @param meta The 4 or more bytes following the meta atom size and type. + */ + public static void maybeSkipRemainingMetaAtomHeaderBytes(ParsableByteArray meta) { + int startPosition = meta.getPosition(); + // The next 4 bytes can be either: + // (iso) 4 zero bytes (version + flags) + // (qt) 4 byte size of next atom + // In case of (iso) we need to skip the next 4 bytes. + if (meta.readInt() != 0) { + meta.setPosition(startPosition); + } + } + /** * Parses a trak atom (defined in ISO/IEC 14496-12). * @@ -677,7 +697,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; @Nullable private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) { - meta.skipBytes(Atom.FULL_HEADER_SIZE); + meta.skipBytes(Atom.HEADER_SIZE); + maybeSkipRemainingMetaAtomHeaderBytes(meta); while (meta.getPosition() < limit) { int atomPosition = meta.getPosition(); int atomSize = meta.readInt(); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 823fd77fe0..f284eaff4f 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -470,7 +470,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { @Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); if (udta != null) { Pair<@NullableType Metadata, @NullableType Metadata> udtaMetadata = - AtomParsers.parseUdta(udta, isQuickTime); + AtomParsers.parseUdta(udta); udtaMetaMetadata = udtaMetadata.first; smtaMetadata = udtaMetadata.second; if (udtaMetaMetadata != null) { @@ -727,29 +727,12 @@ public final class Mp4Extractor implements Extractor, SeekMap { } } - /** - * Possibly skips the version and flags fields (1+3 byte) of a full meta atom of the {@code - * input}. - * - *

Atoms of type {@link Atom#TYPE_meta} are defined to be full atoms which have four additional - * bytes for a version and a flags field (see 4.2 'Object Structure' in ISO/IEC 14496-12:2005). - * QuickTime do not have such a full box structure. Since some of these files are encoded wrongly, - * we can't rely on the file type though. Instead we must check the 8 bytes after the common - * header bytes ourselves. - */ private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input) throws IOException { - scratch.reset(8); - // Peek the next 8 bytes which can be either - // (iso) [1 byte version + 3 bytes flags][4 byte size of next atom] - // (qt) [4 byte size of next atom ][4 byte hdlr atom type ] - // In case of (iso) we need to skip the next 4 bytes. - input.peekFully(scratch.getData(), 0, 8); - scratch.skipBytes(4); - if (scratch.readInt() == Atom.TYPE_hdlr) { - input.resetPeekPosition(); - } else { - input.skipFully(4); - } + scratch.reset(4); + input.peekFully(scratch.getData(), 0, 4); + AtomParsers.maybeSkipRemainingMetaAtomHeaderBytes(scratch); + input.skipFully(scratch.getPosition()); + input.resetPeekPosition(); } /** Processes an atom whose payload does not need to be parsed. */ From 84d7433936c93626752fa2843f311eb2283c0e0a Mon Sep 17 00:00:00 2001 From: kimvde Date: Fri, 12 Feb 2021 16:58:14 +0000 Subject: [PATCH 02/19] Revert logic to decide whether meta atom is full The previous logic was changed under the assumption that the first box inside a meta box was not always an hdlr box, but this is not true. #minor-release PiperOrigin-RevId: 357200713 --- .../exoplayer2/extractor/mp4/AtomParsers.java | 18 ++++++++++-------- .../exoplayer2/extractor/mp4/Mp4Extractor.java | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index b2db7a6df5..dc098994df 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -231,20 +231,22 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; *

Atoms of type {@link Atom#TYPE_meta} are defined to be full atoms which have four additional * bytes for a version and a flags field (see 4.2 'Object Structure' in ISO/IEC 14496-12:2005). * QuickTime do not have such a full box structure. Since some of these files are encoded wrongly, - * we can't rely on the file type though. Instead we must check the 4 bytes after the common + * we can't rely on the file type though. Instead we must check the 8 bytes after the common * header bytes ourselves. * - * @param meta The 4 or more bytes following the meta atom size and type. + * @param meta The 8 or more bytes following the meta atom size and type. */ public static void maybeSkipRemainingMetaAtomHeaderBytes(ParsableByteArray meta) { - int startPosition = meta.getPosition(); - // The next 4 bytes can be either: - // (iso) 4 zero bytes (version + flags) - // (qt) 4 byte size of next atom + int endPosition = meta.getPosition(); + // The next 8 bytes can be either: + // (iso) [1 byte version + 3 bytes flags][4 byte size of next atom] + // (qt) [4 byte size of next atom ][4 byte hdlr atom type ] // In case of (iso) we need to skip the next 4 bytes. - if (meta.readInt() != 0) { - meta.setPosition(startPosition); + meta.skipBytes(4); + if (meta.readInt() != Atom.TYPE_hdlr) { + endPosition += 4; } + meta.setPosition(endPosition); } /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index f284eaff4f..08c399baff 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -728,8 +728,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { } private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input) throws IOException { - scratch.reset(4); - input.peekFully(scratch.getData(), 0, 4); + scratch.reset(8); + input.peekFully(scratch.getData(), 0, 8); AtomParsers.maybeSkipRemainingMetaAtomHeaderBytes(scratch); input.skipFully(scratch.getPosition()); input.resetPeekPosition(); From 0ab9a219f7b78db6bd7f8cd283f6c72ac0cffceb Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 15 Feb 2021 11:32:46 +0000 Subject: [PATCH 03/19] Clarify/correct restrictions of AdsMediaSource. The source can be used in compositions (in fact, every source is automatically used in an internal composition when constructing the playlist), and there is not really a concept of top-level media source any more since the Player supports playlists. The actual restriction is that the content media source needs to have exactly one period to be able to create a SinglePeriodAdTimeline. #minor-release PiperOrigin-RevId: 357544191 --- .../android/exoplayer2/source/ads/AdsMediaSource.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 be1ab81cd3..dbe54168e8 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 @@ -51,9 +51,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** - * A {@link MediaSource} that inserts ads linearly with a provided content media source. This source - * cannot be used as a child source in a composition. It must be the top-level source used to - * prepare the player. + * A {@link MediaSource} that inserts ads linearly into a provided content media source. + * + *

The wrapped content media source must contain a single {@link Timeline.Period}. */ public final class AdsMediaSource extends CompositeMediaSource { From 5be7d4da9eebe325848291dc31dbf808f283463b Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Feb 2021 12:37:15 +0000 Subject: [PATCH 04/19] Don't clear audioSessionId when audio disabled Issue: #8585 PiperOrigin-RevId: 357553237 --- RELEASENOTES.md | 3 +++ .../java/com/google/android/exoplayer2/SimpleExoPlayer.java | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f0729ab173..3127ef60c9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,6 +4,9 @@ * Extractors: * Add support for MP4 and QuickTime meta atoms that are not full atoms. +* Audio: + * Fix `SimpleExoPlayer` reporting audio session ID as 0 in some cases + ([#8585](https://github.com/google/ExoPlayer/issues/8585)). ### 2.13.1 (2021-02-12) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index e89e05eb64..a98e3475af 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -2140,7 +2140,6 @@ public class SimpleExoPlayer extends BasePlayer analyticsCollector.onAudioDisabled(counters); audioFormat = null; audioDecoderCounters = null; - audioSessionId = C.AUDIO_SESSION_ID_UNSET; } @Override From c6ed561d5714d42119e1744b4955fe4909650a31 Mon Sep 17 00:00:00 2001 From: kimvde Date: Mon, 15 Feb 2021 14:12:43 +0000 Subject: [PATCH 05/19] Upgrade extensions NDK version Issue:#8581 PiperOrigin-RevId: 357563419 --- RELEASENOTES.md | 8 ++++++++ extensions/ffmpeg/README.md | 2 +- extensions/flac/README.md | 2 +- extensions/opus/README.md | 2 +- extensions/vp9/README.md | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3127ef60c9..967f28bca9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,14 @@ * Audio: * Fix `SimpleExoPlayer` reporting audio session ID as 0 in some cases ([#8585](https://github.com/google/ExoPlayer/issues/8585)). +* VP9 extension: Update to use NDK r22 + ([#8581](https://github.com/google/ExoPlayer/issues/8581)). +* FLAC extension: Update to use NDK r22 + ([#8581](https://github.com/google/ExoPlayer/issues/8581)). +* Opus extension: Update to use NDK r22 + ([#8581](https://github.com/google/ExoPlayer/issues/8581)). +* FFmpeg extension: Update to use NDK r22 + ([#8581](https://github.com/google/ExoPlayer/issues/8581)). ### 2.13.1 (2021-02-12) diff --git a/extensions/ffmpeg/README.md b/extensions/ffmpeg/README.md index 639d1f6d6c..68eafd2926 100644 --- a/extensions/ffmpeg/README.md +++ b/extensions/ffmpeg/README.md @@ -30,7 +30,7 @@ FFMPEG_EXT_PATH="${EXOPLAYER_ROOT}/extensions/ffmpeg/src/main" ``` * Download the [Android NDK][] and set its location in a shell variable. - This build configuration has been tested on NDK r20. + This build configuration has been tested on NDK r22. ``` NDK_PATH="" diff --git a/extensions/flac/README.md b/extensions/flac/README.md index 47c74d1148..5b98e33364 100644 --- a/extensions/flac/README.md +++ b/extensions/flac/README.md @@ -29,7 +29,7 @@ FLAC_EXT_PATH="${EXOPLAYER_ROOT}/extensions/flac/src/main" ``` * Download the [Android NDK][] and set its location in an environment variable. - This build configuration has been tested on NDK r20. + This build configuration has been tested on NDK r22. ``` NDK_PATH="" diff --git a/extensions/opus/README.md b/extensions/opus/README.md index b683dae0bf..6a68a1946b 100644 --- a/extensions/opus/README.md +++ b/extensions/opus/README.md @@ -29,7 +29,7 @@ OPUS_EXT_PATH="${EXOPLAYER_ROOT}/extensions/opus/src/main" ``` * Download the [Android NDK][] and set its location in an environment variable. - This build configuration has been tested on NDK r20. + This build configuration has been tested on NDK r22. ``` NDK_PATH="" diff --git a/extensions/vp9/README.md b/extensions/vp9/README.md index 1c1d91eb03..3abf72758d 100644 --- a/extensions/vp9/README.md +++ b/extensions/vp9/README.md @@ -29,7 +29,7 @@ VP9_EXT_PATH="${EXOPLAYER_ROOT}/extensions/vp9/src/main" ``` * Download the [Android NDK][] and set its location in an environment variable. - This build configuration has been tested on NDK r20. + This build configuration has been tested on NDK r22. ``` NDK_PATH="" From 326ec967af0ac5782b0b979b7012ca2ae6faea70 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 15 Feb 2021 16:25:14 +0000 Subject: [PATCH 06/19] Fix ad tag loader period index Previously it was safe to query the first period in the timeline, but now we support using the ads loader in concatenations we need to use the current period index instead. PiperOrigin-RevId: 357578003 --- RELEASENOTES.md | 4 ++++ .../com/google/android/exoplayer2/ext/ima/AdTagLoader.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 967f28bca9..498953995d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,10 @@ * Audio: * Fix `SimpleExoPlayer` reporting audio session ID as 0 in some cases ([#8585](https://github.com/google/ExoPlayer/issues/8585)). +* IMA extension: + * Fix a bug with playback of ads in playlists, where the incorrect period + index was used when deciding whether to trigger playback of an ad after + a seek. * VP9 extension: Update to use NDK r22 ([#8581](https://github.com/google/ExoPlayer/issues/8581)). * FLAC extension: Update to use NDK r22 diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java index 9908e4940c..3984d586df 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java @@ -826,7 +826,7 @@ import java.util.Map; ensureSentContentCompleteIfAtEndOfStream(); if (!sentContentComplete && !timeline.isEmpty()) { long positionMs = getContentPeriodPositionMs(player, timeline, period); - timeline.getPeriod(/* periodIndex= */ 0, period); + timeline.getPeriod(player.getCurrentPeriodIndex(), period); int newAdGroupIndex = period.getAdGroupIndexForPositionUs(C.msToUs(positionMs)); if (newAdGroupIndex != C.INDEX_UNSET) { sentPendingContentPositionMs = false; From 625c830b3d5174ccee3ac6ea074eb20324cdecd1 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 16 Feb 2021 11:25:48 +0000 Subject: [PATCH 07/19] Fix seeking to a non-zero position in a preloaded ad item `ImaAdsLoader` will preload the first ad of a subsequent media item, but the preloaded ad might not actually play because the user could seek to a non-zero position in that media item (which could trigger playback of a midroll, not the preroll). In this case, playback would get stuck because the midroll ad expected to play after the seek would never load, because the IMA SDK expected the preroll to play first. Fix this behavior by discarding the preloaded ad break. If there isn't a seek, the transition to the next media item is still seamless. #minor-release PiperOrigin-RevId: 357682510 --- RELEASENOTES.md | 3 +++ .../exoplayer2/ext/ima/AdTagLoader.java | 24 +++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 498953995d..6143f48a5e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,6 +8,9 @@ * Fix `SimpleExoPlayer` reporting audio session ID as 0 in some cases ([#8585](https://github.com/google/ExoPlayer/issues/8585)). * IMA extension: + * Fix a bug where playback could get stuck when seeking into a playlist + item with ads, if the preroll ad had preloaded but the window position + of the seek should instead trigger playback of a midroll. * Fix a bug with playback of ads in playlists, where the incorrect period index was used when deciding whether to trigger playback of an ad after a seek. diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java index 3984d586df..39f9f36fd4 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java @@ -341,11 +341,25 @@ import java.util.Map; boolean playWhenReady = player.getPlayWhenReady(); onTimelineChanged(player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); - if (!AdPlaybackState.NONE.equals(adPlaybackState) - && adsManager != null - && imaPausedContent - && playWhenReady) { - adsManager.resume(); + @Nullable AdsManager adsManager = this.adsManager; + if (!AdPlaybackState.NONE.equals(adPlaybackState) && adsManager != null && imaPausedContent) { + // Check whether the current ad break matches the expected ad break based on the current + // position. If not, discard the current ad break so that the correct ad break can load. + long contentPositionMs = getContentPeriodPositionMs(player, timeline, period); + int adGroupForPositionIndex = + adPlaybackState.getAdGroupIndexForPositionUs( + C.msToUs(contentPositionMs), C.msToUs(contentDurationMs)); + if (adGroupForPositionIndex != C.INDEX_UNSET + && imaAdInfo != null + && imaAdInfo.adGroupIndex != adGroupForPositionIndex) { + if (configuration.debugModeEnabled) { + Log.d(TAG, "Discarding preloaded ad " + imaAdInfo); + } + adsManager.discardAdBreak(); + } + if (playWhenReady) { + adsManager.resume(); + } } } From 18a94aa717afdb1d91539599d48e9728edaeeb31 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 17 Feb 2021 01:38:19 +0000 Subject: [PATCH 08/19] SilenceSampleStream.readData: Handle flags-only buffers The SampleStream.readData contract is that when reading a sample with a flags-only buffer, the buffer timestamp and flags should be set and the read position should not be advanced. #minor-release PiperOrigin-RevId: 357842130 --- .../android/exoplayer2/source/SilenceMediaSource.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java index b802717ee2..447a3e3ad5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java @@ -305,11 +305,15 @@ public final class SilenceMediaSource extends BaseMediaSource { return C.RESULT_BUFFER_READ; } + buffer.timeUs = getAudioPositionUs(positionBytes); + buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME); + if (buffer.isFlagsOnly()) { + return C.RESULT_BUFFER_READ; + } + int bytesToWrite = (int) min(SILENCE_SAMPLE.length, bytesRemaining); buffer.ensureSpaceForWrite(bytesToWrite); buffer.data.put(SILENCE_SAMPLE, /* offset= */ 0, bytesToWrite); - buffer.timeUs = getAudioPositionUs(positionBytes); - buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME); positionBytes += bytesToWrite; return C.RESULT_BUFFER_READ; } From 5e517f895340844b5a4c0706e5c01c4627fac830 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 22 Feb 2021 18:21:44 +0000 Subject: [PATCH 09/19] Read to end-of-stream for HTTP contract tests #minor-release PiperOrigin-RevId: 358847933 --- .../testutil/HttpDataSourceTestEnv.java | 1 - .../testutil/WebServerDispatcher.java | 43 +++++++++++++------ .../testutil/WebServerDispatcherTest.java | 4 +- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/HttpDataSourceTestEnv.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/HttpDataSourceTestEnv.java index fe15120260..9fbbdd3049 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/HttpDataSourceTestEnv.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/HttpDataSourceTestEnv.java @@ -147,7 +147,6 @@ public class HttpDataSourceTestEnv extends ExternalResource { .setName(name) .setUri(Uri.parse(server.url(resource.getPath()).toString())) .setExpectedBytes(resource.getData()) - .setEndOfInputExpected(!resource.resolvesToUnknownLength()) .build(); } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/WebServerDispatcher.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/WebServerDispatcher.java index 0ba0835f6c..64b7be407b 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/WebServerDispatcher.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/WebServerDispatcher.java @@ -33,6 +33,7 @@ import com.google.common.collect.Maps; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -277,21 +278,19 @@ public class WebServerDispatcher extends Dispatcher { if (!resource.supportsRangeRequests() || rangeHeader == null) { switch (preferredContentCoding) { case "gzip": + setResponseBody( + response, Util.gzip(resourceData), /* chunked= */ resource.resolvesToUnknownLength); response - .setBody(new Buffer().write(Util.gzip(resourceData))) .setHeader("Content-Encoding", "gzip"); break; case "identity": + setResponseBody(response, resourceData, /* chunked= */ resource.resolvesToUnknownLength); response - .setBody(new Buffer().write(resourceData)) .setHeader("Content-Encoding", "identity"); break; default: throw new IllegalStateException("Unexpected content coding: " + preferredContentCoding); } - if (resource.resolvesToUnknownLength()) { - response.setHeader("Content-Length", ""); - } return response; } @@ -328,11 +327,11 @@ public class WebServerDispatcher extends Dispatcher { + "-" + (resourceData.length - 1) + "/" - + (resource.resolvesToUnknownLength() ? "*" : resourceData.length)) - .setBody(new Buffer().write(resourceData, start, resourceData.length - start)); - if (resource.resolvesToUnknownLength()) { - response.setHeader("Content-Length", ""); - } + + (resource.resolvesToUnknownLength() ? "*" : resourceData.length)); + setResponseBody( + response, + Arrays.copyOfRange(resourceData, start, resourceData.length), + /* chunked= */ resource.resolvesToUnknownLength); return response; } @@ -345,7 +344,7 @@ public class WebServerDispatcher extends Dispatcher { } int end = min(range.second + 1, resourceData.length); - return response + response .setResponseCode(206) .setHeader( "Content-Range", @@ -354,8 +353,26 @@ public class WebServerDispatcher extends Dispatcher { + "-" + (end - 1) + "/" - + (resource.resolvesToUnknownLength() ? "*" : resourceData.length)) - .setBody(new Buffer().write(resourceData, range.first, end - range.first)); + + (resource.resolvesToUnknownLength() ? "*" : resourceData.length)); + setResponseBody( + response, Arrays.copyOfRange(resourceData, range.first, end), /* chunked= */ false); + return response; + } + + /** + * Populates a response with the specified body. + * + * @param response The response whose body should be populated. + * @param body The body data. + * @param chunked Whether to use chunked transfer encoding. Note that if set to {@code true}, the + * "Content-Length" header will not be set. + */ + private static void setResponseBody(MockResponse response, byte[] body, boolean chunked) { + if (chunked) { + response.setChunkedBody(new Buffer().write(body), /* maxChunkSize= */ Integer.MAX_VALUE); + } else { + response.setBody(new Buffer().write(body)); + } } /** diff --git a/testutils/src/test/java/com/google/android/exoplayer2/testutil/WebServerDispatcherTest.java b/testutils/src/test/java/com/google/android/exoplayer2/testutil/WebServerDispatcherTest.java index 78418565b6..2def3514f8 100644 --- a/testutils/src/test/java/com/google/android/exoplayer2/testutil/WebServerDispatcherTest.java +++ b/testutils/src/test/java/com/google/android/exoplayer2/testutil/WebServerDispatcherTest.java @@ -257,7 +257,7 @@ public class WebServerDispatcherTest { try (Response response = client.newCall(request).execute()) { assertThat(response.code()).isEqualTo(200); assertThat(response.header("Accept-Ranges")).isEqualTo("bytes"); - assertThat(response.header("Content-Length")).isEmpty(); + assertThat(response.header("Content-Length")).isNull(); assertThat(response.header("Content-Range")).isNull(); assertThat(response.body().contentLength()).isEqualTo(-1); @@ -298,7 +298,7 @@ public class WebServerDispatcherTest { try (Response response = client.newCall(request).execute()) { assertThat(response.code()).isEqualTo(206); assertThat(response.header("Accept-Ranges")).isEqualTo("bytes"); - assertThat(response.header("Content-Length")).isEmpty(); + assertThat(response.header("Content-Length")).isNull(); assertThat(response.header("Content-Range")).isEqualTo("bytes 5-19/*"); assertThat(response.body().contentLength()).isEqualTo(-1); From 46995b3c24c5132b0427930a4f595d9dbad180b7 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 23 Feb 2021 09:41:28 +0000 Subject: [PATCH 10/19] Remove unused mocking in CacheWriterTest #minor-release PiperOrigin-RevId: 358998449 --- .../upstream/cache/CacheWriterTest.java | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java index 6064783e08..4c2fd3f765 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheWriterTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.upstream.cache; import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData; import static com.google.common.truth.Truth.assertThat; -import static java.lang.Math.min; import static org.junit.Assert.assertThrows; import android.net.Uri; @@ -36,65 +35,18 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** Unit tests for {@link CacheWriter}. */ @RunWith(AndroidJUnit4.class) public final class CacheWriterTest { - /** - * Abstract fake Cache implementation used by the test. This class must be public so Mockito can - * create a proxy for it. - */ - public abstract static class AbstractFakeCache implements Cache { - - // This array is set to alternating length of cached and not cached regions in tests: - // spansAndGaps = {, , - // , , ... } - // Ideally it should end with a cached region but it shouldn't matter for any code. - private int[] spansAndGaps; - private long contentLength; - - private void init() { - spansAndGaps = new int[] {}; - contentLength = C.LENGTH_UNSET; - } - - @Override - public long getCachedLength(String key, long position, long length) { - if (length == C.LENGTH_UNSET) { - length = Long.MAX_VALUE; - } - for (int i = 0; i < spansAndGaps.length; i++) { - int spanOrGap = spansAndGaps[i]; - if (position < spanOrGap) { - long left = min(spanOrGap - position, length); - return (i & 1) == 1 ? -left : left; - } - position -= spanOrGap; - } - return -length; - } - - @Override - public ContentMetadata getContentMetadata(String key) { - DefaultContentMetadata metadata = new DefaultContentMetadata(); - ContentMetadataMutations mutations = new ContentMetadataMutations(); - ContentMetadataMutations.setContentLength(mutations, contentLength); - return metadata.copyWithMutationsApplied(mutations); - } - } - - @Mock(answer = Answers.CALLS_REAL_METHODS) private AbstractFakeCache mockCache; private File tempFolder; private SimpleCache cache; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mockCache.init(); tempFolder = Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest"); cache = From e890204757151950ba75a171224be6d0c0ceb09a Mon Sep 17 00:00:00 2001 From: marcbaechinger Date: Tue, 23 Feb 2021 12:30:31 +0000 Subject: [PATCH 11/19] Merge pull request #8539 from larryng:patch-1 PiperOrigin-RevId: 359000734 --- .../exo_styled_player_control_ffwd_button.xml | 2 +- ...xo_styled_player_control_rewind_button.xml | 2 +- .../layout/exo_styled_player_control_view.xml | 47 +++++++++---------- .../layout/exo_styled_settings_list_item.xml | 1 + .../exo_styled_sub_settings_list_item.xml | 1 + library/ui/src/main/res/values/ids.xml | 15 ++++++ 6 files changed, 42 insertions(+), 26 deletions(-) diff --git a/library/ui/src/main/res/layout/exo_styled_player_control_ffwd_button.xml b/library/ui/src/main/res/layout/exo_styled_player_control_ffwd_button.xml index f4f7dd6e91..bf4a0f94f0 100644 --- a/library/ui/src/main/res/layout/exo_styled_player_control_ffwd_button.xml +++ b/library/ui/src/main/res/layout/exo_styled_player_control_ffwd_button.xml @@ -22,7 +22,7 @@ android:addStatesFromChildren="true" style="@style/ExoStyledControls.Button.Center"> -