From 6134f96127385dc0685aa394a87cefee6d5ee905 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 23 Oct 2020 13:44:51 +0100 Subject: [PATCH 01/88] Add missing release note --- RELEASENOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 90040fc231..c88c52d9de 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,9 @@ argument ([#8024](https://github.com/google/ExoPlayer/issues/8024)). * Fix bug where streams with highly uneven track durations may get stuck in a buffering state + * Switch Guava dependency from `implementation` to `api` + ([#7905](https://github.com/google/ExoPlayer/issues/7905), + ([#7993](https://github.com/google/ExoPlayer/issues/7993)). * Add 403, 500 and 503 to the list of HTTP status codes that can trigger failover to another quality variant during adaptive playbacks. * Data sources: From 59b34ede6165d029d86a677fea73a5dd0dd55c74 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 23 Oct 2020 13:47:53 +0100 Subject: [PATCH 02/88] Fix release notes --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c88c52d9de..5a5aa12655 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,7 +9,7 @@ in a buffering state * Switch Guava dependency from `implementation` to `api` ([#7905](https://github.com/google/ExoPlayer/issues/7905), - ([#7993](https://github.com/google/ExoPlayer/issues/7993)). + [#7993](https://github.com/google/ExoPlayer/issues/7993)). * Add 403, 500 and 503 to the list of HTTP status codes that can trigger failover to another quality variant during adaptive playbacks. * Data sources: From 3d8c9b0bf87f271a1d5cc69966159bbb15b024c4 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 22 Oct 2020 20:47:19 +0100 Subject: [PATCH 03/88] Allow multiple codecs with same type in DefaultHlsExtractorFactory When disabling a TsExtractor track type because of a missing codec in the master playlist, look through the entire codecs string instead of checking the first codec with matching type. Issue: #7877 PiperOrigin-RevId: 338530046 --- .../android/exoplayer2/util/MimeTypes.java | 23 +++++++++++++ .../exoplayer2/util/MimeTypesTest.java | 32 +++++++++++++++++++ .../hls/DefaultHlsExtractorFactory.java | 4 +-- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index 6d5f167047..64afcd393a 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -239,6 +239,29 @@ public final class MimeTypes { return null; } + /** + * Returns whether the given {@code codecs} string contains a codec which corresponds to the given + * {@code mimeType}. + * + * @param codecs An RFC 6381 codecs string. + * @param mimeType A MIME type to look for. + * @return Whether the given {@code codecs} string contains a codec which corresponds to the given + * {@code mimeType}. + */ + public static boolean containsCodecsCorrespondingToMimeType( + @Nullable String codecs, String mimeType) { + if (codecs == null) { + return false; + } + String[] codecList = Util.splitCodecs(codecs); + for (String codec : codecList) { + if (mimeType.equals(getMediaMimeType(codec))) { + return true; + } + } + return false; + } + /** * Returns the first audio MIME type derived from an RFC 6381 codecs string. * diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java index 46202a5991..6f68328dc7 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java @@ -28,6 +28,38 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public final class MimeTypesTest { + @Test + public void containsCodecsCorrespondingToMimeType_returnsCorrectResult() { + assertThat( + MimeTypes.containsCodecsCorrespondingToMimeType( + /* codecs= */ "ac-3,mp4a.40.2,avc1.4D4015", MimeTypes.AUDIO_AAC)) + .isTrue(); + assertThat( + MimeTypes.containsCodecsCorrespondingToMimeType( + /* codecs= */ "ac-3,mp4a.40.2,avc1.4D4015", MimeTypes.AUDIO_AC3)) + .isTrue(); + assertThat( + MimeTypes.containsCodecsCorrespondingToMimeType( + /* codecs= */ "ac-3,mp4a.40.2,avc1.4D4015", MimeTypes.VIDEO_H264)) + .isTrue(); + assertThat( + MimeTypes.containsCodecsCorrespondingToMimeType( + /* codecs= */ "unknown-codec,mp4a.40.2,avc1.4D4015", MimeTypes.AUDIO_AAC)) + .isTrue(); + + assertThat( + MimeTypes.containsCodecsCorrespondingToMimeType( + /* codecs= */ "unknown-codec,mp4a.40.2,avc1.4D4015", MimeTypes.AUDIO_AC3)) + .isFalse(); + assertThat( + MimeTypes.containsCodecsCorrespondingToMimeType( + /* codecs= */ null, MimeTypes.AUDIO_AC3)) + .isFalse(); + assertThat( + MimeTypes.containsCodecsCorrespondingToMimeType(/* codecs= */ "", MimeTypes.AUDIO_AC3)) + .isFalse(); + } + @Test public void isText_returnsCorrectResult() { assertThat(MimeTypes.isText(MimeTypes.TEXT_VTT)).isTrue(); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java index 0a9ead7c48..c8ef90742b 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java @@ -199,10 +199,10 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really // exist. If we know from the codec attribute that they don't exist, then we can // explicitly ignore them even if they're declared. - if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { + if (!MimeTypes.containsCodecsCorrespondingToMimeType(codecs, MimeTypes.AUDIO_AAC)) { payloadReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; } - if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { + if (!MimeTypes.containsCodecsCorrespondingToMimeType(codecs, MimeTypes.VIDEO_H264)) { payloadReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; } } From 806681dd16838d4b833cd80e376f0f72ec2f04bc Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 22 Oct 2020 21:33:03 +0100 Subject: [PATCH 04/88] Map HLS sample formats to the correct codec string This change fixes format creation for traditional preparation of streams where the master playlist contains more than one codec string per track type. Issue: #7877 PiperOrigin-RevId: 338538693 --- .../android/exoplayer2/util/MimeTypes.java | 29 ++++++++++++++--- .../exoplayer2/util/MimeTypesTest.java | 31 +++++++++++++++++++ .../source/hls/HlsSampleStreamWrapper.java | 6 ++-- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index 64afcd393a..d6dd67ee7d 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -250,16 +250,37 @@ public final class MimeTypes { */ public static boolean containsCodecsCorrespondingToMimeType( @Nullable String codecs, String mimeType) { - if (codecs == null) { - return false; + return getCodecsCorrespondingToMimeType(codecs, mimeType) != null; + } + + /** + * Returns a subsequence of {@code codecs} containing the codec strings that correspond to the + * given {@code mimeType}. Returns null if {@code mimeType} is null, {@code codecs} is null, or + * {@code codecs} does not contain a codec that corresponds to {@code mimeType}. + * + * @param codecs An RFC 6381 codecs string. + * @param mimeType A MIME type to look for. + * @return A subsequence of {@code codecs} containing the codec strings that correspond to the + * given {@code mimeType}. Returns null if {@code mimeType} is null, {@code codecs} is null, + * or {@code codecs} does not contain a codec that corresponds to {@code mimeType}. + */ + @Nullable + public static String getCodecsCorrespondingToMimeType( + @Nullable String codecs, @Nullable String mimeType) { + if (codecs == null || mimeType == null) { + return null; } String[] codecList = Util.splitCodecs(codecs); + StringBuilder builder = new StringBuilder(); for (String codec : codecList) { if (mimeType.equals(getMediaMimeType(codec))) { - return true; + if (builder.length() > 0) { + builder.append(","); + } + builder.append(codec); } } - return false; + return builder.length() > 0 ? builder.toString() : null; } /** diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java index 6f68328dc7..2baac87e85 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java @@ -60,6 +60,37 @@ public final class MimeTypesTest { .isFalse(); } + @Test + public void getCodecsCorrespondingToMimeType_returnsCorrectResult() { + assertThat( + MimeTypes.getCodecsCorrespondingToMimeType( + /* codecs= */ "avc1.4D5015,ac-3,mp4a.40.2,avc1.4D4015", MimeTypes.AUDIO_AAC)) + .isEqualTo("mp4a.40.2"); + assertThat( + MimeTypes.getCodecsCorrespondingToMimeType( + /* codecs= */ "avc1.4D5015,ac-3,mp4a.40.2,avc1.4D4015", MimeTypes.VIDEO_H264)) + .isEqualTo("avc1.4D5015,avc1.4D4015"); + assertThat( + MimeTypes.getCodecsCorrespondingToMimeType( + /* codecs= */ "avc1.4D5015,ac-3,mp4a.40.2,avc1.4D4015", MimeTypes.AUDIO_AC3)) + .isEqualTo("ac-3"); + assertThat( + MimeTypes.getCodecsCorrespondingToMimeType( + /* codecs= */ "unknown-codec,ac-3,mp4a.40.2,avc1.4D4015", MimeTypes.AUDIO_AC3)) + .isEqualTo("ac-3"); + + assertThat( + MimeTypes.getCodecsCorrespondingToMimeType( + /* codecs= */ "avc1.4D5015,ac-3,mp4a.40.2,avc1.4D4015", MimeTypes.VIDEO_H265)) + .isNull(); + assertThat( + MimeTypes.getCodecsCorrespondingToMimeType( + /* codecs= */ "avc1.4D5015,ac-3,mp4a.40.2,avc1.4D4015", null)) + .isNull(); + assertThat(MimeTypes.getCodecsCorrespondingToMimeType(/* codecs= */ null, MimeTypes.AUDIO_AAC)) + .isNull(); + } + @Test public void isText_returnsCorrectResult() { assertThat(MimeTypes.isText(MimeTypes.TEXT_VTT)).isTrue(); 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 89e7687a21..7f1af4496f 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 @@ -1404,8 +1404,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return sampleFormat; } - int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType); - @Nullable String codecs = Util.getCodecsOfType(playlistFormat.codecs, sampleTrackType); + @Nullable + String codecs = + MimeTypes.getCodecsCorrespondingToMimeType( + playlistFormat.codecs, sampleFormat.sampleMimeType); @Nullable String sampleMimeType = MimeTypes.getMediaMimeType(codecs); Format.Builder formatBuilder = From da663aa0812f346d68b172ba6f7c59caa00b3523 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 23 Oct 2020 14:11:49 +0100 Subject: [PATCH 05/88] Avoid chunkless preparation if the codec mapping is ambiguous Issue: #7877 PiperOrigin-RevId: 338659937 --- .../google/android/exoplayer2/util/Util.java | 12 +++++++++ .../exoplayer2/source/hls/HlsMediaPeriod.java | 26 ++++++++++--------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index 745c44395f..441ce84f8d 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -1485,6 +1485,18 @@ public final class Util { + ") " + ExoPlayerLibraryInfo.VERSION_SLASHY; } + /** Returns the number of codec strings in {@code codecs} whose type matches {@code trackType}. */ + public static int getCodecCountOfType(@Nullable String codecs, int trackType) { + String[] codecArray = splitCodecs(codecs); + int count = 0; + for (String codec : codecArray) { + if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) { + count++; + } + } + return count; + } + /** * Returns a copy of {@code codecs} without the codecs whose track type doesn't match {@code * trackType}. diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index 5e0709228d..0089f68bf4 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -603,6 +603,12 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper } } String codecs = selectedPlaylistFormats[0].codecs; + int numberOfVideoCodecs = Util.getCodecCountOfType(codecs, C.TRACK_TYPE_VIDEO); + int numberOfAudioCodecs = Util.getCodecCountOfType(codecs, C.TRACK_TYPE_AUDIO); + boolean codecsStringAllowsChunklessPreparation = + numberOfAudioCodecs <= 1 + && numberOfVideoCodecs <= 1 + && numberOfAudioCodecs + numberOfVideoCodecs > 0; HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper( C.TRACK_TYPE_DEFAULT, @@ -614,18 +620,16 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper positionUs); sampleStreamWrappers.add(sampleStreamWrapper); manifestUrlIndicesPerWrapper.add(selectedVariantIndices); - if (allowChunklessPreparation && codecs != null) { - boolean variantsContainVideoCodecs = Util.getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO) != null; - boolean variantsContainAudioCodecs = Util.getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO) != null; + if (allowChunklessPreparation && codecsStringAllowsChunklessPreparation) { List muxedTrackGroups = new ArrayList<>(); - if (variantsContainVideoCodecs) { + if (numberOfVideoCodecs > 0) { Format[] videoFormats = new Format[selectedVariantsCount]; for (int i = 0; i < videoFormats.length; i++) { videoFormats[i] = deriveVideoFormat(selectedPlaylistFormats[i]); } muxedTrackGroups.add(new TrackGroup(videoFormats)); - if (variantsContainAudioCodecs + if (numberOfAudioCodecs > 0 && (masterPlaylist.muxedAudioFormat != null || masterPlaylist.audios.isEmpty())) { muxedTrackGroups.add( new TrackGroup( @@ -640,7 +644,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper muxedTrackGroups.add(new TrackGroup(ccFormats.get(i))); } } - } else if (variantsContainAudioCodecs) { + } else /* numberOfAudioCodecs > 0 */ { // Variants only contain audio. Format[] audioFormats = new Format[selectedVariantsCount]; for (int i = 0; i < audioFormats.length; i++) { @@ -651,9 +655,6 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper /* isPrimaryTrackInVariant= */ true); } muxedTrackGroups.add(new TrackGroup(audioFormats)); - } else { - // Variants contain codecs but no video or audio entries could be identified. - throw new IllegalArgumentException("Unexpected codecs attribute: " + codecs); } TrackGroup id3TrackGroup = @@ -693,7 +694,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper continue; } - boolean renditionsHaveCodecs = true; + boolean codecStringsAllowChunklessPreparation = true; scratchPlaylistUrls.clear(); scratchPlaylistFormats.clear(); scratchIndicesList.clear(); @@ -704,7 +705,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper scratchIndicesList.add(renditionIndex); scratchPlaylistUrls.add(rendition.url); scratchPlaylistFormats.add(rendition.format); - renditionsHaveCodecs &= rendition.format.codecs != null; + codecStringsAllowChunklessPreparation &= + Util.getCodecCountOfType(rendition.format.codecs, C.TRACK_TYPE_AUDIO) == 1; } } @@ -720,7 +722,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper manifestUrlsIndicesPerWrapper.add(Ints.toArray(scratchIndicesList)); sampleStreamWrappers.add(sampleStreamWrapper); - if (allowChunklessPreparation && renditionsHaveCodecs) { + if (allowChunklessPreparation && codecStringsAllowChunklessPreparation) { Format[] renditionFormats = scratchPlaylistFormats.toArray(new Format[0]); sampleStreamWrapper.prepareWithMasterPlaylistInfo( new TrackGroup[] {new TrackGroup(renditionFormats)}, /* primaryTrackGroupIndex= */ 0); From 1264dbcd36935629f289594b89443abd7b0c57fc Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 23 Oct 2020 14:50:03 +0100 Subject: [PATCH 06/88] Handle stream volume register/unregister errors Issue: #8106 Issue: #8087 PiperOrigin-RevId: 338664455 --- RELEASENOTES.md | 16 ++++++++--- .../exoplayer2/StreamVolumeManager.java | 27 +++++++++++++------ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5a5aa12655..e8daab543f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,12 @@ # Release notes +### 2.12.2 (2020-11-??) ### + +* Core library: + * Suppress exceptions from registering/unregistering the stream volume + receiver ([#8087](https://github.com/google/ExoPlayer/issues/8087)), + ([#8106](https://github.com/google/ExoPlayer/issues/8106)). + ### 2.12.1 (2020-10-23) ### * Core library: @@ -7,6 +14,7 @@ argument ([#8024](https://github.com/google/ExoPlayer/issues/8024)). * Fix bug where streams with highly uneven track durations may get stuck in a buffering state + ([#7943](https://github.com/google/ExoPlayer/issues/7943)). * Switch Guava dependency from `implementation` to `api` ([#7905](https://github.com/google/ExoPlayer/issues/7905), [#7993](https://github.com/google/ExoPlayer/issues/7993)). @@ -63,9 +71,9 @@ * Allow apps to specify a `VideoAdPlayerCallback` ([#7944](https://github.com/google/ExoPlayer/issues/7944)). * Accept ad tags via the `AdsMediaSource` constructor and deprecate - passing them via the `ImaAdsLoader` constructor/builders. Passing the - ad tag via media item playback properties continues to be supported. - This is in preparation for supporting ads in playlists + passing them via the `ImaAdsLoader` constructor/builders. Passing the ad + tag via media item playback properties continues to be supported. This + is in preparation for supporting ads in playlists ([#3750](https://github.com/google/ExoPlayer/issues/3750)). * Add a way to override ad media MIME types ([#7961)(https://github.com/google/ExoPlayer/issues/7961)). @@ -74,6 +82,8 @@ * Upgrade IMA SDK dependency to 3.20.1. This brings in a fix for companion ads rendering when targeting API 29 ([#6432](https://github.com/google/ExoPlayer/issues/6432)). +* Metadata retriever: + * Parse Google Photos HEIC motion photos metadata. ### 2.12.0 (2020-09-11) ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java b/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java index 66216de861..fa5d316b60 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java @@ -21,7 +21,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.os.Handler; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; /** A manager that wraps {@link AudioManager} to control/listen audio stream volume. */ @@ -37,6 +39,8 @@ import com.google.android.exoplayer2.util.Util; void onStreamVolumeChanged(int streamVolume, boolean streamMuted); } + private static final String TAG = "StreamVolumeManager"; + // TODO(b/151280453): Replace the hidden intent action with an official one. // Copied from AudioManager#VOLUME_CHANGED_ACTION private static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION"; @@ -48,12 +52,11 @@ import com.google.android.exoplayer2.util.Util; private final Handler eventHandler; private final Listener listener; private final AudioManager audioManager; - private final VolumeChangeReceiver receiver; + @Nullable private VolumeChangeReceiver receiver; @C.StreamType private int streamType; private int volume; private boolean muted; - private boolean released; /** Creates a manager. */ public StreamVolumeManager(Context context, Handler eventHandler, Listener listener) { @@ -68,9 +71,14 @@ import com.google.android.exoplayer2.util.Util; volume = getVolumeFromManager(audioManager, streamType); muted = getMutedFromManager(audioManager, streamType); - receiver = new VolumeChangeReceiver(); + VolumeChangeReceiver receiver = new VolumeChangeReceiver(); IntentFilter filter = new IntentFilter(VOLUME_CHANGED_ACTION); - applicationContext.registerReceiver(receiver, filter); + try { + applicationContext.registerReceiver(receiver, filter); + this.receiver = receiver; + } catch (RuntimeException e) { + Log.w(TAG, "Error registering stream volume receiver", e); + } } /** Sets the audio stream type. */ @@ -159,11 +167,14 @@ import com.google.android.exoplayer2.util.Util; /** Releases the manager. It must be called when the manager is no longer required. */ public void release() { - if (released) { - return; + if (receiver != null) { + try { + applicationContext.unregisterReceiver(receiver); + } catch (RuntimeException e) { + Log.w(TAG, "Error unregistering stream volume receiver", e); + } + receiver = null; } - applicationContext.unregisterReceiver(receiver); - released = true; } private void updateVolumeAndNotifyIfChanged() { From 9ad70246e6c35b361f7c27d96d76e524f7117ca0 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 26 Oct 2020 12:05:09 +0000 Subject: [PATCH 07/88] Treat -1000 duration as unknown duration for live streams in Cast Issue: #7983 #minor-release PiperOrigin-RevId: 339016928 --- .../com/google/android/exoplayer2/ext/cast/CastUtils.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java index 182afb0468..4f3f52a5f9 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastUtils.java @@ -27,6 +27,10 @@ import com.google.android.gms.cast.MediaTrack; */ /* package */ final class CastUtils { + /** The duration returned by {@link MediaInfo#getStreamDuration()} for live streams. */ + // TODO: Remove once [Internal ref: b/171657375] is fixed. + private static final long LIVE_STREAM_DURATION = -1000; + /** * Returns the duration in microseconds advertised by a media info, or {@link C#TIME_UNSET} if * unknown or not applicable. @@ -39,7 +43,9 @@ import com.google.android.gms.cast.MediaTrack; return C.TIME_UNSET; } long durationMs = mediaInfo.getStreamDuration(); - return durationMs != MediaInfo.UNKNOWN_DURATION ? C.msToUs(durationMs) : C.TIME_UNSET; + return durationMs != MediaInfo.UNKNOWN_DURATION && durationMs != LIVE_STREAM_DURATION + ? C.msToUs(durationMs) + : C.TIME_UNSET; } /** From 7cc129d7b7f90ea6b8f7564bd6bc3cbd9f73e28c Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 26 Oct 2020 12:59:26 +0000 Subject: [PATCH 08/88] Upgrade IMA SDK dependency to 3.21.0 Call the new method AdsLoader.release() to allow the IMA SDK to dispose of its WebView. Issue: #7344 PiperOrigin-RevId: 339022162 --- RELEASENOTES.md | 3 +++ extensions/ima/build.gradle | 2 +- .../com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e8daab543f..b8b950cbe3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,6 +6,9 @@ * Suppress exceptions from registering/unregistering the stream volume receiver ([#8087](https://github.com/google/ExoPlayer/issues/8087)), ([#8106](https://github.com/google/ExoPlayer/issues/8106)). +* IMA extension: + * Upgrade IMA SDK dependency to 3.21.0, and release the `AdsLoader` + ([#7344](https://github.com/google/ExoPlayer/issues/7344)). ### 2.12.1 (2020-10-23) ### diff --git a/extensions/ima/build.gradle b/extensions/ima/build.gradle index ed20dedb10..8cdfb0dffc 100644 --- a/extensions/ima/build.gradle +++ b/extensions/ima/build.gradle @@ -25,7 +25,7 @@ android { } dependencies { - api 'com.google.ads.interactivemedia.v3:interactivemedia:3.20.1' + api 'com.google.ads.interactivemedia.v3:interactivemedia:3.21.0' implementation project(modulePrefix + 'library-core') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0' 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 4185a158f7..c72650cf5c 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 @@ -871,6 +871,7 @@ public final class ImaAdsLoader if (configuration.applicationAdErrorListener != null) { adsLoader.removeAdErrorListener(configuration.applicationAdErrorListener); } + adsLoader.release(); } imaPausedContent = false; imaAdState = IMA_AD_STATE_NONE; From 44c2ddb07674cee5338b3423a32664a82af90e24 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 26 Oct 2020 16:10:20 +0000 Subject: [PATCH 09/88] Suppress ProGuard warnings related to Guava's compile-only deps Without these lines, ProGuard fails on the demo app (R8 works). Also include some more `-dontwarn` lines from https://github.com/google/guava/wiki/UsingProGuardWithGuava Issue: #8103 PiperOrigin-RevId: 339050634 --- RELEASENOTES.md | 2 ++ library/common/proguard-rules.txt | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b8b950cbe3..de07aecfb6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,6 +6,8 @@ * Suppress exceptions from registering/unregistering the stream volume receiver ([#8087](https://github.com/google/ExoPlayer/issues/8087)), ([#8106](https://github.com/google/ExoPlayer/issues/8106)). + * Suppress ProGuard warnings caused by Guava's compile-only dependencies + ([#8103](https://github.com/google/ExoPlayer/issues/8103)). * IMA extension: * Upgrade IMA SDK dependency to 3.21.0, and release the `AdsLoader` ([#7344](https://github.com/google/ExoPlayer/issues/7344)). diff --git a/library/common/proguard-rules.txt b/library/common/proguard-rules.txt index 18e5264c20..8de310a867 100644 --- a/library/common/proguard-rules.txt +++ b/library/common/proguard-rules.txt @@ -7,3 +7,12 @@ # From https://github.com/google/guava/wiki/UsingProGuardWithGuava -dontwarn java.lang.ClassValue +-dontwarn java.lang.SafeVarargs +-dontwarn javax.lang.model.element.Modifier +-dontwarn sun.misc.Unsafe + +# Don't warn about Guava's compile-only dependencies. +# These lines are needed for ProGuard but not R8. +-dontwarn com.google.errorprone.annotations.** +-dontwarn com.google.j2objc.annotations.** +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement From 875987492401c144d0006e81da0f3b1cf0b4d6e8 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 26 Oct 2020 19:01:21 +0000 Subject: [PATCH 10/88] Improve handling of VPAID ads Issue: #7832 PiperOrigin-RevId: 339087555 --- RELEASENOTES.md | 2 ++ .../exoplayer2/ext/ima/ImaAdsLoader.java | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index de07aecfb6..31f21e4c57 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -11,6 +11,8 @@ * IMA extension: * Upgrade IMA SDK dependency to 3.21.0, and release the `AdsLoader` ([#7344](https://github.com/google/ExoPlayer/issues/7344)). + * Improve handling of ad tags with unsupported VPAID ads + ([#7832](https://github.com/google/ExoPlayer/issues/7832)). ### 2.12.1 (2020-10-23) ### 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 c72650cf5c..e7a8253451 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 @@ -1212,11 +1212,24 @@ public final class ImaAdsLoader if (imaAdInfo != null) { adPlaybackState = adPlaybackState.withSkippedAdGroup(imaAdInfo.adGroupIndex); updateAdPlaybackState(); - } else if (adPlaybackState.adGroupCount == 1 && adPlaybackState.adGroupTimesUs[0] == 0) { - // For incompatible VPAID ads with one preroll, content is resumed immediately. In this case - // we haven't received ad info (the ad never loaded), but there is only one ad group to skip. - adPlaybackState = adPlaybackState.withSkippedAdGroup(/* adGroupIndex= */ 0); - updateAdPlaybackState(); + } else { + // Mark any ads for the current/reported player position that haven't loaded as being in the + // error state, to force resuming content. This includes VPAID ads that never load. + long playerPositionUs; + if (player != null) { + playerPositionUs = C.msToUs(getContentPeriodPositionMs(player, timeline, period)); + } else if (!VideoProgressUpdate.VIDEO_TIME_NOT_READY.equals(lastContentProgress)) { + // Playback is backgrounded so use the last reported content position. + playerPositionUs = C.msToUs(lastContentProgress.getCurrentTimeMs()); + } else { + return; + } + int adGroupIndex = + adPlaybackState.getAdGroupIndexForPositionUs( + playerPositionUs, C.msToUs(contentDurationMs)); + if (adGroupIndex != C.INDEX_UNSET) { + markAdGroupInErrorStateAndClearPendingContentPosition(adGroupIndex); + } } } From f1126ce5145069ee4d4e6f858cca386f88da4fc3 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 27 Oct 2020 12:01:10 +0000 Subject: [PATCH 11/88] Improve progress update logs Add logging for ad progress and switch from deprecated getters to new millisecond getters. PiperOrigin-RevId: 339226534 --- .../android/exoplayer2/ext/ima/ImaAdsLoader.java | 16 +++++++--------- .../android/exoplayer2/ext/ima/ImaUtil.java | 12 ++++++++++++ 2 files changed, 19 insertions(+), 9 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 e7a8253451..d619c1fe84 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 @@ -1119,6 +1119,10 @@ public final class ImaAdsLoader private void updateAdProgress() { VideoProgressUpdate videoProgressUpdate = getAdVideoProgressUpdate(); + if (configuration.debugModeEnabled) { + Log.d(TAG, "Ad progress: " + ImaUtil.getStringForVideoProgressUpdate(videoProgressUpdate)); + } + AdMediaInfo adMediaInfo = checkNotNull(imaAdMediaInfo); for (int i = 0; i < adCallbacks.size(); i++) { adCallbacks.get(i).onAdProgress(adMediaInfo, videoProgressUpdate); @@ -1730,15 +1734,9 @@ public final class ImaAdsLoader public VideoProgressUpdate getContentProgress() { VideoProgressUpdate videoProgressUpdate = getContentVideoProgressUpdate(); if (configuration.debugModeEnabled) { - if (VideoProgressUpdate.VIDEO_TIME_NOT_READY.equals(videoProgressUpdate)) { - Log.d(TAG, "Content progress: not ready"); - } else { - Log.d( - TAG, - Util.formatInvariant( - "Content progress: %.1f of %.1f s", - videoProgressUpdate.getCurrentTime(), videoProgressUpdate.getDuration())); - } + Log.d( + TAG, + "Content progress: " + ImaUtil.getStringForVideoProgressUpdate(videoProgressUpdate)); } if (waitingForPreloadElapsedRealtimeMs != C.TIME_UNSET) { diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java index a4f1ec92cc..6d69547278 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java @@ -33,6 +33,7 @@ import com.google.ads.interactivemedia.v3.api.FriendlyObstructionPurpose; import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; import com.google.ads.interactivemedia.v3.api.UiElement; import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; +import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.AdsLoader.OverlayInfo; @@ -202,5 +203,16 @@ import java.util.Set; || adError.getErrorCode() == AdError.AdErrorCode.UNKNOWN_ERROR; } + /** Returns a human-readable representation of a video progress update. */ + public static String getStringForVideoProgressUpdate(VideoProgressUpdate videoProgressUpdate) { + if (VideoProgressUpdate.VIDEO_TIME_NOT_READY.equals(videoProgressUpdate)) { + return "not ready"; + } else { + return Util.formatInvariant( + "%d ms of %d ms", + videoProgressUpdate.getCurrentTimeMs(), videoProgressUpdate.getDurationMs()); + } + } + private ImaUtil() {} } From 0c9e92136aecd3f2568420c0cc1df23e4b007678 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 28 Oct 2020 10:04:20 +0000 Subject: [PATCH 12/88] Fix skipping behavior in ad pods ImaAdsLoader notified onEnded whenever an ad finished playing, but when an ad is skipped in an ad pod we'd receive a playAd call before the player discontinuity for skipping to the next ad. Fix this behavior by checking that IMA's playing ad matches the player's playing ad before notifying onEnded. #minor-release PiperOrigin-RevId: 339424910 --- RELEASENOTES.md | 2 ++ .../android/exoplayer2/ext/ima/ImaAdsLoader.java | 15 ++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 31f21e4c57..387fb247a8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -13,6 +13,8 @@ ([#7344](https://github.com/google/ExoPlayer/issues/7344)). * Improve handling of ad tags with unsupported VPAID ads ([#7832](https://github.com/google/ExoPlayer/issues/7832)). + * Fix a bug that caused multiple ads in an ad pod to be skipped when one + ad in the ad pod was skipped. ### 2.12.1 (2020-10-23) ### 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 d619c1fe84..265ffe585b 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 @@ -1300,13 +1300,18 @@ public final class ImaAdsLoader if (adMediaInfo == null) { Log.w(TAG, "onEnded without ad media info"); } else { - for (int i = 0; i < adCallbacks.size(); i++) { - adCallbacks.get(i).onEnded(adMediaInfo); + @Nullable AdInfo adInfo = adInfoByAdMediaInfo.get(adMediaInfo); + if (playingAdIndexInAdGroup == C.INDEX_UNSET + || (adInfo != null && adInfo.adIndexInAdGroup < playingAdIndexInAdGroup)) { + for (int i = 0; i < adCallbacks.size(); i++) { + adCallbacks.get(i).onEnded(adMediaInfo); + } + if (configuration.debugModeEnabled) { + Log.d( + TAG, "VideoAdPlayerCallback.onEnded in onTimelineChanged/onPositionDiscontinuity"); + } } } - if (configuration.debugModeEnabled) { - Log.d(TAG, "VideoAdPlayerCallback.onEnded in onTimelineChanged/onPositionDiscontinuity"); - } } if (!sentContentComplete && !wasPlayingAd && playingAd && imaAdState == IMA_AD_STATE_NONE) { int adGroupIndex = player.getCurrentAdGroupIndex(); From e35f7d828d9deb4eaeb538a5d17ddca5328e89c0 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 28 Oct 2020 13:32:28 +0000 Subject: [PATCH 13/88] Add sample for testing skippable ads in midrolls #minor-release PiperOrigin-RevId: 339447845 --- demos/main/src/main/assets/media.exolist.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index 24213918f5..4fdfaddea6 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -497,6 +497,11 @@ "name": "VMAP midroll at 1765 s", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/frame-counter-one-hour.mp4", "ad_tag_uri": "https://vastsynthesizer.appspot.com/midroll-large" + }, + { + "name": "VMAP midroll ad pod at 5 s with 10 skippable ads", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/frame-counter-one-hour.mp4", + "ad_tag_uri": "https://vastsynthesizer.appspot.com/midroll-10-skippable-ads" } ] }, From 379cd8a04f0bf44a9422a08440223581b2657d74 Mon Sep 17 00:00:00 2001 From: olly Date: Sun, 1 Nov 2020 19:51:56 +0000 Subject: [PATCH 14/88] Make defaultLicenseUrl optional Some content types always provide the license URL in the media. The PlayReady example in the demo app doesn't provide a default license URL for this reason, as an example. #minor-release PiperOrigin-RevId: 340125784 --- .../google/android/exoplayer2/MediaItem.java | 17 +++++----- .../exoplayer2/drm/HttpMediaDrmCallback.java | 32 ++++++++++++++----- .../source/MediaSourceDrmHelper.java | 5 ++- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java index 556b04b8ca..0c25f2d43e 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java @@ -216,7 +216,7 @@ public final class MediaItem { } /** - * Sets the optional DRM license server URI. If this URI is set, the {@link + * Sets the optional default DRM license server URI. If this URI is set, the {@link * DrmConfiguration#uuid} needs to be specified as well. * *

If {@link #setUri} is passed a non-null {@code uri}, the DRM license server URI is used to @@ -228,7 +228,7 @@ public final class MediaItem { } /** - * Sets the optional DRM license server URI. If this URI is set, the {@link + * Sets the optional default DRM license server URI. If this URI is set, the {@link * DrmConfiguration#uuid} needs to be specified as well. * *

If {@link #setUri} is passed a non-null {@code uri}, the DRM license server URI is used to @@ -279,8 +279,8 @@ public final class MediaItem { } /** - * Sets whether to use the DRM license server URI of the media item for key requests that - * include their own DRM license server URI. + * Sets whether to force use the default DRM license server URI even if the media specifies its + * own DRM license server URI. * *

If {@link #setUri} is passed a non-null {@code uri}, the DRM force default license flag is * used to create a {@link PlaybackProperties} object. Otherwise it will be ignored. @@ -482,8 +482,8 @@ public final class MediaItem { public final UUID uuid; /** - * Optional DRM license server {@link Uri}. If {@code null} then the DRM license server must be - * specified by the media. + * Optional default DRM license server {@link Uri}. If {@code null} then the DRM license server + * must be specified by the media. */ @Nullable public final Uri licenseUri; @@ -500,8 +500,8 @@ public final class MediaItem { public final boolean playClearContentWithoutKey; /** - * Sets whether to use the DRM license server URI of the media item for key requests that - * include their own DRM license server URI. + * Whether to force use of {@link #licenseUri} even if the media specifies its own DRM license + * server URI. */ public final boolean forceDefaultLicenseUri; @@ -519,6 +519,7 @@ public final class MediaItem { boolean playClearContentWithoutKey, List drmSessionForClearTypes, @Nullable byte[] keySetId) { + Assertions.checkArgument(!(forceDefaultLicenseUri && licenseUri == null)); this.uuid = uuid; this.licenseUri = licenseUri; this.requestHeaders = requestHeaders; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java index 7ab90b023e..6a20cf7bda 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.drm; +import android.net.Uri; import android.text.TextUtils; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; @@ -27,6 +28,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCode import com.google.android.exoplayer2.upstream.StatsDataSource; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -39,29 +41,35 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { private static final int MAX_MANUAL_REDIRECTS = 5; private final HttpDataSource.Factory dataSourceFactory; - private final String defaultLicenseUrl; + @Nullable private final String defaultLicenseUrl; private final boolean forceDefaultLicenseUrl; private final Map keyRequestProperties; /** * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify - * their own license URL. + * their own license URL. May be {@code null} if it's known that all key requests will specify + * their own URLs. * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. */ - public HttpMediaDrmCallback(String defaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) { + public HttpMediaDrmCallback( + @Nullable String defaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) { this(defaultLicenseUrl, /* forceDefaultLicenseUrl= */ false, dataSourceFactory); } /** * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify - * their own license URL, or for all key requests if {@code forceDefaultLicenseUrl} is - * set to true. - * @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that - * include their own license URL. + * their own license URL, or for all key requests if {@code forceDefaultLicenseUrl} is set to + * true. May be {@code null} if {@code forceDefaultLicenseUrl} is {@code false} and if it's + * known that all key requests will specify their own URLs. + * @param forceDefaultLicenseUrl Whether to force use of {@code defaultLicenseUrl} for key + * requests that include their own license URL. * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. */ - public HttpMediaDrmCallback(String defaultLicenseUrl, boolean forceDefaultLicenseUrl, + public HttpMediaDrmCallback( + @Nullable String defaultLicenseUrl, + boolean forceDefaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) { + Assertions.checkArgument(!(forceDefaultLicenseUrl && TextUtils.isEmpty(defaultLicenseUrl))); this.dataSourceFactory = dataSourceFactory; this.defaultLicenseUrl = defaultLicenseUrl; this.forceDefaultLicenseUrl = forceDefaultLicenseUrl; @@ -121,6 +129,14 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { if (forceDefaultLicenseUrl || TextUtils.isEmpty(url)) { url = defaultLicenseUrl; } + if (TextUtils.isEmpty(url)) { + throw new MediaDrmCallbackException( + new DataSpec.Builder().setUri(Uri.EMPTY).build(), + Uri.EMPTY, + /* responseHeaders= */ ImmutableMap.of(), + /* bytesLoaded= */ 0, + /* cause= */ new IllegalStateException("No license URL")); + } Map requestProperties = new HashMap<>(); // Add standard request properties for supported schemes. String contentType = C.PLAYREADY_UUID.equals(uuid) ? "text/xml" diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceDrmHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceDrmHelper.java index 7859254401..f4a7b89fc7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceDrmHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSourceDrmHelper.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.source; import static com.google.android.exoplayer2.ExoPlayerLibraryInfo.DEFAULT_USER_AGENT; import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK; -import static com.google.android.exoplayer2.util.Util.castNonNull; import androidx.annotation.Nullable; import com.google.android.exoplayer2.MediaItem; @@ -68,7 +67,7 @@ public final class MediaSourceDrmHelper { Assertions.checkNotNull(mediaItem.playbackProperties); @Nullable MediaItem.DrmConfiguration drmConfiguration = mediaItem.playbackProperties.drmConfiguration; - if (drmConfiguration == null || drmConfiguration.licenseUri == null || Util.SDK_INT < 18) { + if (drmConfiguration == null || Util.SDK_INT < 18) { return DrmSessionManager.getDummyDrmSessionManager(); } HttpDataSource.Factory dataSourceFactory = @@ -77,7 +76,7 @@ public final class MediaSourceDrmHelper { : new DefaultHttpDataSourceFactory(userAgent != null ? userAgent : DEFAULT_USER_AGENT); HttpMediaDrmCallback httpDrmCallback = new HttpMediaDrmCallback( - castNonNull(drmConfiguration.licenseUri).toString(), + drmConfiguration.licenseUri == null ? null : drmConfiguration.licenseUri.toString(), drmConfiguration.forceDefaultLicenseUri, dataSourceFactory); for (Map.Entry entry : drmConfiguration.requestHeaders.entrySet()) { From 2d022a21b367848bdfe0d324c0722f5652bef8d7 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 2 Nov 2020 11:06:17 +0000 Subject: [PATCH 15/88] Fix buildForAdsResponse PiperOrigin-RevId: 340198099 --- RELEASENOTES.md | 1 + .../com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 387fb247a8..b1115d5c39 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,6 +15,7 @@ ([#7832](https://github.com/google/ExoPlayer/issues/7832)). * Fix a bug that caused multiple ads in an ad pod to be skipped when one ad in the ad pod was skipped. + * Fix passing an ads response to the `ImaAdsLoader` builder. ### 2.12.1 (2020-10-23) ### 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 265ffe585b..65c8920971 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 @@ -705,7 +705,9 @@ public final class ImaAdsLoader if (adTagUri != null) { adTagDataSpec = new DataSpec(adTagUri); } else if (adsResponse != null) { - adTagDataSpec = new DataSpec(Util.getDataUriForString(adsResponse, "text/xml")); + adTagDataSpec = + new DataSpec( + Util.getDataUriForString(/* mimeType= */ "text/xml", /* data= */ adsResponse)); } else { throw new IllegalStateException(); } From 07455da2f5b5857fa9cab1316f9543efd1ae680c Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 2 Nov 2020 17:17:17 +0000 Subject: [PATCH 16/88] Migrate Tx3gDecoderTest to Guava and SpannedSubject #minor-release PiperOrigin-RevId: 340249019 --- .../exoplayer2/text/tx3g/Tx3gDecoderTest.java | 159 ++++++++---------- 1 file changed, 70 insertions(+), 89 deletions(-) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java index 58b9a853e7..ca84f901d8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java @@ -15,24 +15,18 @@ */ package com.google.android.exoplayer2.text.tx3g; +import static com.google.android.exoplayer2.testutil.truth.SpannedSubject.assertThat; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; import android.graphics.Color; -import android.graphics.Typeface; import android.text.SpannedString; -import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; -import android.text.style.TypefaceSpan; -import android.text.style.UnderlineSpan; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Subtitle; -import com.google.android.exoplayer2.text.SubtitleDecoderException; -import java.io.IOException; +import com.google.common.collect.ImmutableList; import java.util.Collections; import org.junit.Test; import org.junit.runner.RunWith; @@ -57,197 +51,184 @@ public final class Tx3gDecoderTest { "media/tx3g/initialization_all_defaults"; @Test - public void decodeNoSubtitle() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); + public void decodeNoSubtitle() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NO_SUBTITLE); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + assertThat(subtitle.getCues(0)).isEmpty(); } @Test - public void decodeJustText() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); + public void decodeJustText() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), 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"); - assertThat(text.getSpans(0, text.length(), Object.class)).hasLength(0); + assertThat(text).hasNoSpans(); assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } @Test - public void decodeWithStyl() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); + public void decodeWithStyl() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), 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"); - assertThat(text.getSpans(0, text.length(), Object.class)).hasLength(3); - StyleSpan styleSpan = findSpan(text, 0, 6, StyleSpan.class); - assertThat(styleSpan.getStyle()).isEqualTo(Typeface.BOLD_ITALIC); - findSpan(text, 0, 6, UnderlineSpan.class); - ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class); - assertThat(colorSpan.getForegroundColor()).isEqualTo(Color.GREEN); + assertThat(text).hasBoldItalicSpanBetween(0, 6); + assertThat(text).hasUnderlineSpanBetween(0, 6); + assertThat(text).hasForegroundColorSpanBetween(0, 6).withColor(Color.GREEN); assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } @Test - public void decodeWithStylAllDefaults() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); + public void decodeWithStylAllDefaults() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); byte[] bytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), 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"); - assertThat(text.getSpans(0, text.length(), Object.class)).hasLength(0); + assertThat(text).hasNoSpans(); assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } @Test - public void decodeUtf16BeNoStyl() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); + public void decodeUtf16BeNoStyl() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), 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("你好"); - assertThat(text.getSpans(0, text.length(), Object.class)).hasLength(0); + assertThat(text).hasNoSpans(); assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } @Test - public void decodeUtf16LeNoStyl() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); + public void decodeUtf16LeNoStyl() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), 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("你好"); - assertThat(text.getSpans(0, text.length(), Object.class)).hasLength(0); + assertThat(text).hasNoSpans(); assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } @Test - public void decodeWithMultipleStyl() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); + public void decodeWithMultipleStyl() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); byte[] bytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), 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"); - assertThat(text.getSpans(0, text.length(), Object.class)).hasLength(4); - StyleSpan styleSpan = findSpan(text, 0, 5, StyleSpan.class); - assertThat(styleSpan.getStyle()).isEqualTo(Typeface.ITALIC); - findSpan(text, 7, 12, UnderlineSpan.class); - ForegroundColorSpan colorSpan = findSpan(text, 0, 5, ForegroundColorSpan.class); - assertThat(colorSpan.getForegroundColor()).isEqualTo(Color.GREEN); - colorSpan = findSpan(text, 7, 12, ForegroundColorSpan.class); - assertThat(colorSpan.getForegroundColor()).isEqualTo(Color.GREEN); + assertThat(text).hasItalicSpanBetween(0, 5); + assertThat(text).hasUnderlineSpanBetween(7, 12); + assertThat(text).hasForegroundColorSpanBetween(0, 5).withColor(Color.GREEN); + assertThat(text).hasForegroundColorSpanBetween(7, 12).withColor(Color.GREEN); assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } @Test - public void decodeWithOtherExtension() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); + public void decodeWithOtherExtension() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); byte[] bytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), 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"); - assertThat(text.getSpans(0, text.length(), Object.class)).hasLength(2); - StyleSpan styleSpan = findSpan(text, 0, 6, StyleSpan.class); - assertThat(styleSpan.getStyle()).isEqualTo(Typeface.BOLD); - ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class); - assertThat(colorSpan.getForegroundColor()).isEqualTo(Color.GREEN); + assertThat(text).hasBoldSpanBetween(0, 6); + assertThat(text).hasForegroundColorSpanBetween(0, 6).withColor(Color.GREEN); assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } @Test - public void initializationDecodeWithStyl() throws IOException, SubtitleDecoderException { + public void initializationDecodeWithStyl() throws Exception { byte[] initBytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), INITIALIZATION); Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes)); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), 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"); - assertThat(text.getSpans(0, text.length(), Object.class)).hasLength(5); - StyleSpan styleSpan = findSpan(text, 0, text.length(), StyleSpan.class); - assertThat(styleSpan.getStyle()).isEqualTo(Typeface.BOLD_ITALIC); - findSpan(text, 0, text.length(), UnderlineSpan.class); - TypefaceSpan typefaceSpan = findSpan(text, 0, text.length(), TypefaceSpan.class); - assertThat(typefaceSpan.getFamily()).isEqualTo(C.SERIF_NAME); - ForegroundColorSpan colorSpan = findSpan(text, 0, text.length(), ForegroundColorSpan.class); - assertThat(colorSpan.getForegroundColor()).isEqualTo(Color.RED); - colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class); - assertThat(colorSpan.getForegroundColor()).isEqualTo(Color.GREEN); + assertThat(text).hasBoldItalicSpanBetween(0, 7); + assertThat(text).hasUnderlineSpanBetween(0, 7); + assertThat(text).hasTypefaceSpanBetween(0, 7).withFamily(C.SERIF_NAME); + // TODO(internal b/171984212): Fix Tx3gDecoder to avoid overlapping spans of the same type. + assertThat(text).hasForegroundColorSpanBetween(0, 7).withColor(Color.RED); + assertThat(text).hasForegroundColorSpanBetween(0, 6).withColor(Color.GREEN); assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.1f); } @Test - public void initializationDecodeWithTbox() throws IOException, SubtitleDecoderException { + public void initializationDecodeWithTbox() throws Exception { byte[] initBytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), INITIALIZATION); Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes)); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), 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"); - assertThat(text.getSpans(0, text.length(), Object.class)).hasLength(4); - StyleSpan styleSpan = findSpan(text, 0, text.length(), StyleSpan.class); - assertThat(styleSpan.getStyle()).isEqualTo(Typeface.BOLD_ITALIC); - findSpan(text, 0, text.length(), UnderlineSpan.class); - TypefaceSpan typefaceSpan = findSpan(text, 0, text.length(), TypefaceSpan.class); - assertThat(typefaceSpan.getFamily()).isEqualTo(C.SERIF_NAME); - ForegroundColorSpan colorSpan = findSpan(text, 0, text.length(), ForegroundColorSpan.class); - assertThat(colorSpan.getForegroundColor()).isEqualTo(Color.RED); + assertThat(text).hasBoldItalicSpanBetween(0, 7); + assertThat(text).hasUnderlineSpanBetween(0, 7); + assertThat(text).hasTypefaceSpanBetween(0, 7).withFamily(C.SERIF_NAME); + assertThat(text).hasForegroundColorSpanBetween(0, 7).withColor(Color.RED); assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.1875f); } @Test - public void initializationAllDefaultsDecodeWithStyl() - throws IOException, SubtitleDecoderException { + public void initializationAllDefaultsDecodeWithStyl() throws Exception { byte[] initBytes = TestUtil.getByteArray( ApplicationProvider.getApplicationContext(), INITIALIZATION_ALL_DEFAULTS); Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes)); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), 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"); - assertThat(text.getSpans(0, text.length(), Object.class)).hasLength(3); - StyleSpan styleSpan = findSpan(text, 0, 6, StyleSpan.class); - assertThat(styleSpan.getStyle()).isEqualTo(Typeface.BOLD_ITALIC); - findSpan(text, 0, 6, UnderlineSpan.class); - ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class); - assertThat(colorSpan.getForegroundColor()).isEqualTo(Color.GREEN); + assertThat(text).hasBoldItalicSpanBetween(0, 6); + assertThat(text).hasUnderlineSpanBetween(0, 6); + assertThat(text).hasForegroundColorSpanBetween(0, 6).withColor(Color.GREEN); assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } - 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 - && testObject.getSpanEnd(span) == expectedEnd) { - return span; - } - } - fail("Span not found."); - return null; - } - private static void assertFractionalLinePosition(Cue cue, float expectedFraction) { assertThat(cue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); assertThat(cue.lineAnchor).isEqualTo(Cue.ANCHOR_TYPE_START); - assertThat(Math.abs(expectedFraction - cue.line) < 1e-6).isTrue(); + assertThat(cue.line).isWithin(1e-6f).of(expectedFraction); } } From ac1ffa4fc2453391cc7d20b235502e75a81786fd Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 2 Nov 2020 23:01:45 +0000 Subject: [PATCH 17/88] Merge pull request #8133 from xufuji456:dev-v2 PiperOrigin-RevId: 340254878 --- RELEASENOTES.md | 3 ++ .../exoplayer2/text/tx3g/Tx3gDecoder.java | 13 +++++ .../exoplayer2/text/tx3g/Tx3gDecoderTest.java | 50 ++++++++++++++++++ .../media/tx3g/sample_with_styl_end_too_large | Bin 0 -> 31 bytes .../tx3g/sample_with_styl_start_too_large | Bin 0 -> 31 bytes 5 files changed, 66 insertions(+) create mode 100644 testdata/src/test/assets/media/tx3g/sample_with_styl_end_too_large create mode 100644 testdata/src/test/assets/media/tx3g/sample_with_styl_start_too_large diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b1115d5c39..44a0ecedd7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -16,6 +16,9 @@ * Fix a bug that caused multiple ads in an ad pod to be skipped when one ad in the ad pod was skipped. * Fix passing an ads response to the `ImaAdsLoader` builder. +* Text + * Allow tx3g subtitles with `styl` boxes with start and/or end offsets + that lie outside the length of the cue text. ### 2.12.1 (2020-10-23) ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java index 4ce0ea8df5..907607f859 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.SubtitleDecoderException; +import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import com.google.common.base.Charsets; @@ -43,6 +44,8 @@ import java.util.List; */ public final class Tx3gDecoder extends SimpleSubtitleDecoder { + private static final String TAG = "Tx3gDecoder"; + private static final char BOM_UTF16_BE = '\uFEFF'; private static final char BOM_UTF16_LE = '\uFFFE'; @@ -185,6 +188,16 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { int fontFace = parsableByteArray.readUnsignedByte(); parsableByteArray.skipBytes(1); // font size int colorRgba = parsableByteArray.readInt(); + + if (end > cueText.length()) { + Log.w( + TAG, "Truncating styl end (" + end + ") to cueText.length() (" + cueText.length() + ")."); + end = cueText.length(); + } + if (start >= end) { + Log.w(TAG, "Ignoring styl with start (" + start + ") >= end (" + end + ")."); + return; + } attachFontFace(cueText, fontFace, defaultFontFace, start, end, SPAN_PRIORITY_HIGH); attachColor(cueText, colorRgba, defaultColorRgba, start, end, SPAN_PRIORITY_HIGH); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java index ca84f901d8..b64466cc00 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java @@ -38,6 +38,10 @@ public final class Tx3gDecoderTest { private static final String NO_SUBTITLE = "media/tx3g/no_subtitle"; private static final String SAMPLE_JUST_TEXT = "media/tx3g/sample_just_text"; private static final String SAMPLE_WITH_STYL = "media/tx3g/sample_with_styl"; + private static final String SAMPLE_WITH_STYL_START_TOO_LARGE = + "media/tx3g/sample_with_styl_start_too_large"; + private static final String SAMPLE_WITH_STYL_END_TOO_LARGE = + "media/tx3g/sample_with_styl_end_too_large"; private static final String SAMPLE_WITH_STYL_ALL_DEFAULTS = "media/tx3g/sample_with_styl_all_defaults"; private static final String SAMPLE_UTF16_BE_NO_STYL = "media/tx3g/sample_utf16_be_no_styl"; @@ -90,6 +94,52 @@ public final class Tx3gDecoderTest { assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } + /** + * The 7-byte sample contains a 4-byte emoji. The start index (6) and end index (7) are valid as + * byte offsets, but not a UTF-16 code-unit offset, so they're both truncated to 5 (the length of + * the resulting the string in Java) and the spans end up empty (so we don't add them). + * + *

https://github.com/google/ExoPlayer/pull/8133 + */ + @Test + public void decodeWithStyl_startTooLarge_noSpanAdded() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); + byte[] bytes = + TestUtil.getByteArray( + ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL_START_TOO_LARGE); + + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); + + assertThat(text.toString()).isEqualTo("CC 🙂"); + assertThat(text).hasNoSpans(); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); + } + + /** + * The 7-byte sample contains a 4-byte emoji. The end index (6) is valid as a byte offset, but not + * a UTF-16 code-unit offset, so it's truncated to 5 (the length of the resulting the string in + * Java). + * + *

https://github.com/google/ExoPlayer/pull/8133 + */ + @Test + public void decodeWithStyl_endTooLarge_clippedToEndOfText() throws Exception { + Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); + byte[] bytes = + TestUtil.getByteArray( + ApplicationProvider.getApplicationContext(), SAMPLE_WITH_STYL_END_TOO_LARGE); + + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); + + assertThat(text.toString()).isEqualTo("CC 🙂"); + assertThat(text).hasBoldItalicSpanBetween(0, 5); + assertThat(text).hasUnderlineSpanBetween(0, 5); + assertThat(text).hasForegroundColorSpanBetween(0, 5).withColor(Color.GREEN); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); + } + @Test public void decodeWithStylAllDefaults() throws Exception { Tx3gDecoder decoder = new Tx3gDecoder(ImmutableList.of()); diff --git a/testdata/src/test/assets/media/tx3g/sample_with_styl_end_too_large b/testdata/src/test/assets/media/tx3g/sample_with_styl_end_too_large new file mode 100644 index 0000000000000000000000000000000000000000..35c60e0607e0baeae1f51e92bc5e066fb6587ca6 GIT binary patch literal 31 lcmZQzcXn3zFn?wf0|SFtaY Date: Mon, 2 Nov 2020 18:26:17 +0000 Subject: [PATCH 18/88] Matroska: Support additional PCM codec modes - Support 32-bit A_PCM/FLOAT/IEEE PCM - Support 8-bit and 16-bit A_PCM/INT/BIG PCM Issue: #8142 PiperOrigin-RevId: 340264679 --- RELEASENOTES.md | 4 + .../extractor/mkv/MatroskaExtractor.java | 109 +++++++++++++----- 2 files changed, 81 insertions(+), 32 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 44a0ecedd7..55087d5267 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,6 +8,10 @@ ([#8106](https://github.com/google/ExoPlayer/issues/8106)). * Suppress ProGuard warnings caused by Guava's compile-only dependencies ([#8103](https://github.com/google/ExoPlayer/issues/8103)). +* Extractors: + * Matroska: Add support for 32-bit floating point PCM, and 8-bit and + 16-bit big endian integer PCM + ([#8142](https://github.com/google/ExoPlayer/issues/8142)). * IMA extension: * Upgrade IMA SDK dependency to 3.21.0, and release the `AdsLoader` ([#7344](https://github.com/google/ExoPlayer/issues/7344)). diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 660605ebe5..c8f4cadcb1 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -128,6 +128,8 @@ public class MatroskaExtractor implements Extractor { private static final String CODEC_ID_FLAC = "A_FLAC"; private static final String CODEC_ID_ACM = "A_MS/ACM"; private static final String CODEC_ID_PCM_INT_LIT = "A_PCM/INT/LIT"; + private static final String CODEC_ID_PCM_INT_BIG = "A_PCM/INT/BIG"; + private static final String CODEC_ID_PCM_FLOAT = "A_PCM/FLOAT/IEEE"; private static final String CODEC_ID_SUBRIP = "S_TEXT/UTF8"; private static final String CODEC_ID_ASS = "S_TEXT/ASS"; private static final String CODEC_ID_VOBSUB = "S_VOBSUB"; @@ -1743,36 +1745,43 @@ public class MatroskaExtractor implements Extractor { } private static boolean isCodecSupported(String codecId) { - return CODEC_ID_VP8.equals(codecId) - || CODEC_ID_VP9.equals(codecId) - || CODEC_ID_AV1.equals(codecId) - || CODEC_ID_MPEG2.equals(codecId) - || CODEC_ID_MPEG4_SP.equals(codecId) - || CODEC_ID_MPEG4_ASP.equals(codecId) - || CODEC_ID_MPEG4_AP.equals(codecId) - || CODEC_ID_H264.equals(codecId) - || CODEC_ID_H265.equals(codecId) - || CODEC_ID_FOURCC.equals(codecId) - || CODEC_ID_THEORA.equals(codecId) - || CODEC_ID_OPUS.equals(codecId) - || CODEC_ID_VORBIS.equals(codecId) - || CODEC_ID_AAC.equals(codecId) - || CODEC_ID_MP2.equals(codecId) - || CODEC_ID_MP3.equals(codecId) - || CODEC_ID_AC3.equals(codecId) - || CODEC_ID_E_AC3.equals(codecId) - || CODEC_ID_TRUEHD.equals(codecId) - || CODEC_ID_DTS.equals(codecId) - || CODEC_ID_DTS_EXPRESS.equals(codecId) - || CODEC_ID_DTS_LOSSLESS.equals(codecId) - || CODEC_ID_FLAC.equals(codecId) - || CODEC_ID_ACM.equals(codecId) - || CODEC_ID_PCM_INT_LIT.equals(codecId) - || CODEC_ID_SUBRIP.equals(codecId) - || CODEC_ID_ASS.equals(codecId) - || CODEC_ID_VOBSUB.equals(codecId) - || CODEC_ID_PGS.equals(codecId) - || CODEC_ID_DVBSUB.equals(codecId); + switch (codecId) { + case CODEC_ID_VP8: + case CODEC_ID_VP9: + case CODEC_ID_AV1: + case CODEC_ID_MPEG2: + case CODEC_ID_MPEG4_SP: + case CODEC_ID_MPEG4_ASP: + case CODEC_ID_MPEG4_AP: + case CODEC_ID_H264: + case CODEC_ID_H265: + case CODEC_ID_FOURCC: + case CODEC_ID_THEORA: + case CODEC_ID_OPUS: + case CODEC_ID_VORBIS: + case CODEC_ID_AAC: + case CODEC_ID_MP2: + case CODEC_ID_MP3: + case CODEC_ID_AC3: + case CODEC_ID_E_AC3: + case CODEC_ID_TRUEHD: + case CODEC_ID_DTS: + case CODEC_ID_DTS_EXPRESS: + case CODEC_ID_DTS_LOSSLESS: + case CODEC_ID_FLAC: + case CODEC_ID_ACM: + case CODEC_ID_PCM_INT_LIT: + case CODEC_ID_PCM_INT_BIG: + case CODEC_ID_PCM_FLOAT: + case CODEC_ID_SUBRIP: + case CODEC_ID_ASS: + case CODEC_ID_VOBSUB: + case CODEC_ID_PGS: + case CODEC_ID_DVBSUB: + return true; + default: + return false; + } } /** @@ -2102,8 +2111,44 @@ public class MatroskaExtractor implements Extractor { if (pcmEncoding == C.ENCODING_INVALID) { pcmEncoding = Format.NO_VALUE; mimeType = MimeTypes.AUDIO_UNKNOWN; - Log.w(TAG, "Unsupported PCM bit depth: " + audioBitDepth + ". Setting mimeType to " - + mimeType); + Log.w( + TAG, + "Unsupported little endian PCM bit depth: " + + audioBitDepth + + ". Setting mimeType to " + + mimeType); + } + break; + case CODEC_ID_PCM_INT_BIG: + mimeType = MimeTypes.AUDIO_RAW; + if (audioBitDepth == 8) { + pcmEncoding = C.ENCODING_PCM_8BIT; + } else if (audioBitDepth == 16) { + pcmEncoding = C.ENCODING_PCM_16BIT_BIG_ENDIAN; + } else { + pcmEncoding = Format.NO_VALUE; + mimeType = MimeTypes.AUDIO_UNKNOWN; + Log.w( + TAG, + "Unsupported big endian PCM bit depth: " + + audioBitDepth + + ". Setting mimeType to " + + mimeType); + } + break; + case CODEC_ID_PCM_FLOAT: + mimeType = MimeTypes.AUDIO_RAW; + if (audioBitDepth == 32) { + pcmEncoding = C.ENCODING_PCM_FLOAT; + } else { + pcmEncoding = Format.NO_VALUE; + mimeType = MimeTypes.AUDIO_UNKNOWN; + Log.w( + TAG, + "Unsupported floating point PCM bit depth: " + + audioBitDepth + + ". Setting mimeType to " + + mimeType); } break; case CODEC_ID_SUBRIP: From 3f9488b7cd41d037ec03d7061079974e5610ef8e Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 3 Nov 2020 10:27:12 +0000 Subject: [PATCH 19/88] Fix ImaPlaybackTest This test is not run in presubmit as it was too flaky, and is currently broken due to assets moving. Also migrate off ImaPlaybackTest off deprecated APIs. #minor-release PiperOrigin-RevId: 340405666 --- .../exoplayer2/ext/ima/ImaPlaybackTest.java | 24 ++++++++++++------- .../ad-responses/midroll10s_midroll20s.xml | 4 ++-- .../ad-responses/midroll1s_midroll7s.xml | 4 ++-- .../assets/media/ad-responses/preroll.xml | 2 +- .../preroll_midroll6s_postroll.xml | 6 ++--- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java b/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java index 88bc4e14c5..9527d35cef 100644 --- a/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java +++ b/extensions/ima/src/androidTest/java/com/google/android/exoplayer2/ext/ima/ImaPlaybackTest.java @@ -47,8 +47,10 @@ import com.google.android.exoplayer2.testutil.HostActivity; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Arrays; @@ -78,7 +80,7 @@ public final class ImaPlaybackTest { @Test public void playbackWithPrerollAdTag_playsAdAndContent() throws Exception { String adsResponse = - TestUtil.getString(/* context= */ testRule.getActivity(), "ad-responses/preroll.xml"); + TestUtil.getString(/* context= */ testRule.getActivity(), "media/ad-responses/preroll.xml"); AdId[] expectedAdIds = new AdId[] {ad(0), CONTENT}; ImaHostedTest hostedTest = new ImaHostedTest(Uri.parse(CONTENT_URI_SHORT), adsResponse, expectedAdIds); @@ -90,7 +92,8 @@ public final class ImaPlaybackTest { public void playbackWithMidrolls_playsAdAndContent() throws Exception { String adsResponse = TestUtil.getString( - /* context= */ testRule.getActivity(), "ad-responses/preroll_midroll6s_postroll.xml"); + /* context= */ testRule.getActivity(), + "media/ad-responses/preroll_midroll6s_postroll.xml"); AdId[] expectedAdIds = new AdId[] {ad(0), CONTENT, ad(1), CONTENT, ad(2), CONTENT}; ImaHostedTest hostedTest = new ImaHostedTest(Uri.parse(CONTENT_URI_SHORT), adsResponse, expectedAdIds); @@ -102,7 +105,7 @@ public final class ImaPlaybackTest { public void playbackWithMidrolls1And7_playsAdsAndContent() throws Exception { String adsResponse = TestUtil.getString( - /* context= */ testRule.getActivity(), "ad-responses/midroll1s_midroll7s.xml"); + /* context= */ testRule.getActivity(), "media/ad-responses/midroll1s_midroll7s.xml"); AdId[] expectedAdIds = new AdId[] {CONTENT, ad(0), CONTENT, ad(1), CONTENT}; ImaHostedTest hostedTest = new ImaHostedTest(Uri.parse(CONTENT_URI_SHORT), adsResponse, expectedAdIds); @@ -114,7 +117,7 @@ public final class ImaPlaybackTest { public void playbackWithMidrolls10And20WithSeekTo12_playsAdsAndContent() throws Exception { String adsResponse = TestUtil.getString( - /* context= */ testRule.getActivity(), "ad-responses/midroll10s_midroll20s.xml"); + /* context= */ testRule.getActivity(), "media/ad-responses/midroll10s_midroll20s.xml"); AdId[] expectedAdIds = new AdId[] {CONTENT, ad(0), CONTENT, ad(1), CONTENT}; ImaHostedTest hostedTest = new ImaHostedTest(Uri.parse(CONTENT_URI_LONG), adsResponse, expectedAdIds); @@ -131,7 +134,7 @@ public final class ImaPlaybackTest { public void playbackWithMidrolls10And20WithSeekTo18_playsAdsAndContent() throws Exception { String adsResponse = TestUtil.getString( - /* context= */ testRule.getActivity(), "ad-responses/midroll10s_midroll20s.xml"); + /* context= */ testRule.getActivity(), "media/ad-responses/midroll10s_midroll20s.xml"); AdId[] expectedAdIds = new AdId[] {CONTENT, ad(0), CONTENT, ad(1), CONTENT}; ImaHostedTest hostedTest = new ImaHostedTest(Uri.parse(CONTENT_URI_LONG), adsResponse, expectedAdIds); @@ -190,7 +193,7 @@ public final class ImaPlaybackTest { private static final class ImaHostedTest extends ExoHostedTest implements EventListener { private final Uri contentUri; - private final String adsResponse; + private final DataSpec adTagDataSpec; private final List expectedAdIds; private final List seenAdIds; private @MonotonicNonNull ImaAdsLoader imaAdsLoader; @@ -201,7 +204,9 @@ public final class ImaPlaybackTest { // duration due to ad playback, so the hosted test shouldn't assert the playing duration. super(ImaPlaybackTest.class.getSimpleName(), /* fullPlaybackNoSeeking= */ false); this.contentUri = contentUri; - this.adsResponse = adsResponse; + this.adTagDataSpec = + new DataSpec( + Util.getDataUriForString(/* mimeType= */ "text/xml", /* data= */ adsResponse)); this.expectedAdIds = Arrays.asList(expectedAdIds); seenAdIds = new ArrayList<>(); } @@ -226,7 +231,7 @@ public final class ImaPlaybackTest { } }); Context context = host.getApplicationContext(); - imaAdsLoader = new ImaAdsLoader.Builder(context).buildForAdsResponse(adsResponse); + imaAdsLoader = new ImaAdsLoader.Builder(context).build(); imaAdsLoader.setPlayer(player); return player; } @@ -242,7 +247,8 @@ public final class ImaPlaybackTest { new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(contentUri)); return new AdsMediaSource( contentMediaSource, - dataSourceFactory, + adTagDataSpec, + new DefaultMediaSourceFactory(dataSourceFactory), Assertions.checkNotNull(imaAdsLoader), new AdViewProvider() { diff --git a/testdata/src/test/assets/media/ad-responses/midroll10s_midroll20s.xml b/testdata/src/test/assets/media/ad-responses/midroll10s_midroll20s.xml index 1543e11d26..98c59ec45d 100644 --- a/testdata/src/test/assets/media/ad-responses/midroll10s_midroll20s.xml +++ b/testdata/src/test/assets/media/ad-responses/midroll10s_midroll20s.xml @@ -17,7 +17,7 @@ @@ -48,7 +48,7 @@ file:///android_asset/mp4/midroll-5s.mp4 diff --git a/testdata/src/test/assets/media/ad-responses/midroll1s_midroll7s.xml b/testdata/src/test/assets/media/ad-responses/midroll1s_midroll7s.xml index 7b693747fc..58c1834df3 100644 --- a/testdata/src/test/assets/media/ad-responses/midroll1s_midroll7s.xml +++ b/testdata/src/test/assets/media/ad-responses/midroll1s_midroll7s.xml @@ -17,7 +17,7 @@ @@ -48,7 +48,7 @@ file:///android_asset/mp4/midroll-5s.mp4 diff --git a/testdata/src/test/assets/media/ad-responses/preroll.xml b/testdata/src/test/assets/media/ad-responses/preroll.xml index 3456649b29..d55f960381 100644 --- a/testdata/src/test/assets/media/ad-responses/preroll.xml +++ b/testdata/src/test/assets/media/ad-responses/preroll.xml @@ -14,7 +14,7 @@ diff --git a/testdata/src/test/assets/media/ad-responses/preroll_midroll6s_postroll.xml b/testdata/src/test/assets/media/ad-responses/preroll_midroll6s_postroll.xml index bbf216bf12..01d62c3a82 100644 --- a/testdata/src/test/assets/media/ad-responses/preroll_midroll6s_postroll.xml +++ b/testdata/src/test/assets/media/ad-responses/preroll_midroll6s_postroll.xml @@ -17,7 +17,7 @@ @@ -48,7 +48,7 @@ file:///android_asset/mp4/preroll-5s.mp4 @@ -79,7 +79,7 @@ file:///android_asset/mp4/midroll-5s.mp4 From 81d68317ac3c85f7a6f62a278ea7296eb5372a34 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 5 Nov 2020 12:29:31 +0000 Subject: [PATCH 20/88] Fix or suppress nullness warnings introduced by checkerframework 3.7.0 PiperOrigin-RevId: 340826532 --- .../exoplayer2/ui/PlayerNotificationManager.java | 10 ++++++---- .../android/exoplayer2/ui/StyledPlayerControlView.java | 10 ++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index b52a3e6f82..7c899c1ea2 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java @@ -610,10 +610,12 @@ public class PlayerNotificationManager { controlDispatcher = new DefaultControlDispatcher(); window = new Timeline.Window(); instanceId = instanceIdCounter++; - //noinspection Convert2MethodRef - mainHandler = - Util.createHandler( - Looper.getMainLooper(), msg -> PlayerNotificationManager.this.handleMessage(msg)); + // This fails the nullness checker because handleMessage() is 'called' while `this` is still + // @UnderInitialization. No tasks are scheduled on mainHandler before the constructor completes, + // so this is safe and we can suppress the warning. + @SuppressWarnings("nullness:methodref.receiver.bound.invalid") + Handler mainHandler = Util.createHandler(Looper.getMainLooper(), this::handleMessage); + this.mainHandler = mainHandler; notificationManager = NotificationManagerCompat.from(context); playerListener = new PlayerListener(); notificationBroadcastReceiver = new NotificationBroadcastReceiver(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index ed2bad6eeb..f17404b7a2 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -1920,7 +1920,7 @@ public class StyledPlayerControlView extends FrameLayout { } } - private class SettingViewHolder extends RecyclerView.ViewHolder { + private final class SettingViewHolder extends RecyclerView.ViewHolder { private final TextView mainTextView; private final TextView subTextView; private final ImageView iconView; @@ -1930,8 +1930,7 @@ public class StyledPlayerControlView extends FrameLayout { mainTextView = itemView.findViewById(R.id.exo_main_text); subTextView = itemView.findViewById(R.id.exo_sub_text); iconView = itemView.findViewById(R.id.exo_icon); - itemView.setOnClickListener( - v -> onSettingViewClicked(SettingViewHolder.this.getAdapterPosition())); + itemView.setOnClickListener(v -> onSettingViewClicked(getAdapterPosition())); } } @@ -1969,7 +1968,7 @@ public class StyledPlayerControlView extends FrameLayout { } } - private class SubSettingViewHolder extends RecyclerView.ViewHolder { + private final class SubSettingViewHolder extends RecyclerView.ViewHolder { private final TextView textView; private final View checkView; @@ -1977,8 +1976,7 @@ public class StyledPlayerControlView extends FrameLayout { super(itemView); textView = itemView.findViewById(R.id.exo_text); checkView = itemView.findViewById(R.id.exo_check); - itemView.setOnClickListener( - v -> onSubSettingViewClicked(SubSettingViewHolder.this.getAdapterPosition())); + itemView.setOnClickListener(v -> onSubSettingViewClicked(getAdapterPosition())); } } From 11b09dd58d222ed451a200d03690be8c7ac4f914 Mon Sep 17 00:00:00 2001 From: bachinger Date: Mon, 9 Nov 2020 14:35:16 +0000 Subject: [PATCH 21/88] Add dispatchPrepare(player) to ControlDispatcher Issue: #7882 PiperOrigin-RevId: 341394254 --- RELEASENOTES.md | 8 ++++++++ .../exoplayer2/demo/PlayerActivity.java | 11 +---------- .../ext/leanback/LeanbackPlayerAdapter.java | 15 ++++++++++++--- .../mediasession/MediaSessionConnector.java | 2 ++ .../android/exoplayer2/ControlDispatcher.java | 8 ++++++++ .../exoplayer2/DefaultControlDispatcher.java | 6 ++++++ .../android/exoplayer2/PlaybackPreparer.java | 6 ++++-- .../exoplayer2/ui/PlayerControlView.java | 15 +++++++++++---- .../ui/PlayerNotificationManager.java | 18 +++++++++++++----- .../android/exoplayer2/ui/PlayerView.java | 12 ++++++++---- .../exoplayer2/ui/StyledPlayerControlView.java | 15 +++++++++++---- .../exoplayer2/ui/StyledPlayerView.java | 13 +++++++++---- 12 files changed, 93 insertions(+), 36 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 55087d5267..5950cbfaa6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,6 +8,14 @@ ([#8106](https://github.com/google/ExoPlayer/issues/8106)). * Suppress ProGuard warnings caused by Guava's compile-only dependencies ([#8103](https://github.com/google/ExoPlayer/issues/8103)). +* UI: + * Add `dispatchPrepare(Player)` to `ControlDispatcher` and implement it in + `DefaultControlDispatcher`. Deprecate `PlaybackPreparer` and + `setPlaybackPreparer` in `StyledPlayerView`, `StyledPlayerControlView`, + `PlayerView`, `PlayerControlView`, `PlayerNotificationManager` and + `LeanbackPlayerAdapter` and use `ControlDispatcher` for dispatching + prepare instead + ([#7882](https://github.com/google/ExoPlayer/issues/7882)). * Extractors: * Matroska: Add support for 32-bit floating point PCM, and 8-bit and 16-bit big endian integer PCM diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index c35080c47f..60bdcf1986 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -35,7 +35,6 @@ import androidx.appcompat.app.AppCompatActivity; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.PlaybackPreparer; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; @@ -69,7 +68,7 @@ import java.util.List; /** An activity that plays media using {@link SimpleExoPlayer}. */ public class PlayerActivity extends AppCompatActivity - implements OnClickListener, PlaybackPreparer, StyledPlayerControlView.VisibilityListener { + implements OnClickListener, StyledPlayerControlView.VisibilityListener { // Saved instance state keys. @@ -252,13 +251,6 @@ public class PlayerActivity extends AppCompatActivity } } - // PlaybackPreparer implementation - - @Override - public void preparePlayback() { - player.prepare(); - } - // PlayerControlView.VisibilityListener implementation @Override @@ -304,7 +296,6 @@ public class PlayerActivity extends AppCompatActivity player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true); player.setPlayWhenReady(startAutoPlay); playerView.setPlayer(player); - playerView.setPlaybackPreparer(this); debugViewHelper = new DebugTextViewHelper(player, debugTextView); debugViewHelper.start(); } diff --git a/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java b/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java index 6538160b8b..6da02bb324 100644 --- a/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java +++ b/extensions/leanback/src/main/java/com/google/android/exoplayer2/ext/leanback/LeanbackPlayerAdapter.java @@ -78,10 +78,15 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab } /** - * Sets the {@link PlaybackPreparer}. - * - * @param playbackPreparer The {@link PlaybackPreparer}. + * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} instead. The adapter calls + * {@link ControlDispatcher#dispatchPrepare(Player)} instead of {@link + * PlaybackPreparer#preparePlayback()}. The {@link DefaultControlDispatcher} that the adapter + * uses by default, calls {@link Player#prepare()}. If you wish to customize this behaviour, + * you can provide a custom implementation of {@link + * ControlDispatcher#dispatchPrepare(Player)}. */ + @SuppressWarnings("deprecation") + @Deprecated public void setPlaybackPreparer(@Nullable PlaybackPreparer playbackPreparer) { this.playbackPreparer = playbackPreparer; } @@ -167,11 +172,15 @@ public final class LeanbackPlayerAdapter extends PlayerAdapter implements Runnab return player.getPlaybackState() == Player.STATE_IDLE ? -1 : player.getCurrentPosition(); } + // Calls deprecated method to provide backwards compatibility. + @SuppressWarnings("deprecation") @Override public void play() { if (player.getPlaybackState() == Player.STATE_IDLE) { if (playbackPreparer != null) { playbackPreparer.preparePlayback(); + } else { + controlDispatcher.dispatchPrepare(player); } } else if (player.getPlaybackState() == Player.STATE_ENDED) { controlDispatcher.dispatchSeekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java index 85d0155bd7..e78c55b2af 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -1147,6 +1147,8 @@ public final class MediaSessionConnector { if (player.getPlaybackState() == Player.STATE_IDLE) { if (playbackPreparer != null) { playbackPreparer.onPrepare(/* playWhenReady= */ true); + } else { + controlDispatcher.dispatchPrepare(player); } } else if (player.getPlaybackState() == Player.STATE_ENDED) { seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java b/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java index 7b78147e12..0d5e55fc83 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ControlDispatcher.java @@ -26,6 +26,14 @@ import com.google.android.exoplayer2.Player.RepeatMode; */ public interface ControlDispatcher { + /** + * Dispatches a {@link Player#prepare()} operation. + * + * @param player The {@link Player} to which the operation should be dispatched. + * @return True if the operation was dispatched. False if suppressed. + */ + boolean dispatchPrepare(Player player); + /** * Dispatches a {@link Player#setPlayWhenReady(boolean)} operation. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java index d46b939c1f..25c468330c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultControlDispatcher.java @@ -52,6 +52,12 @@ public class DefaultControlDispatcher implements ControlDispatcher { window = new Timeline.Window(); } + @Override + public boolean dispatchPrepare(Player player) { + player.prepare(); + return true; + } + @Override public boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady) { player.setPlayWhenReady(playWhenReady); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackPreparer.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackPreparer.java index 8ff7f50402..3ef38c8520 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackPreparer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackPreparer.java @@ -15,9 +15,11 @@ */ package com.google.android.exoplayer2; -/** Called to prepare a playback. */ +/** @deprecated Use {@link ControlDispatcher} instead. */ +@Deprecated public interface PlaybackPreparer { - /** Called to prepare a playback. */ + /** @deprecated Use {@link ControlDispatcher#dispatchPrepare(Player)} instead. */ + @Deprecated void preparePlayback(); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java index 65a9a5ed8f..1ae7812bd4 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java @@ -611,11 +611,15 @@ public class PlayerControlView extends FrameLayout { } /** - * Sets the {@link PlaybackPreparer}. - * - * @param playbackPreparer The {@link PlaybackPreparer}, or null to remove the current playback - * preparer. + * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} instead. The view calls {@link + * ControlDispatcher#dispatchPrepare(Player)} instead of {@link + * PlaybackPreparer#preparePlayback()}. The {@link DefaultControlDispatcher} that the view + * uses by default, calls {@link Player#prepare()}. If you wish to customize this behaviour, + * you can provide a custom implementation of {@link + * ControlDispatcher#dispatchPrepare(Player)}. */ + @SuppressWarnings("deprecation") + @Deprecated public void setPlaybackPreparer(@Nullable PlaybackPreparer playbackPreparer) { this.playbackPreparer = playbackPreparer; } @@ -1254,11 +1258,14 @@ public class PlayerControlView extends FrameLayout { } } + @SuppressWarnings("deprecation") private void dispatchPlay(Player player) { @State int state = player.getPlaybackState(); if (state == Player.STATE_IDLE) { if (playbackPreparer != null) { playbackPreparer.preparePlayback(); + } else { + controlDispatcher.dispatchPrepare(player); } } else if (state == Player.STATE_ENDED) { seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index 7c899c1ea2..b183fddbb6 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java @@ -682,10 +682,16 @@ public class PlayerNotificationManager { } /** - * Sets the {@link PlaybackPreparer}. - * - * @param playbackPreparer The {@link PlaybackPreparer}. + * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} instead. The manager calls + * {@link ControlDispatcher#dispatchPrepare(Player)} instead of {@link + * PlaybackPreparer#preparePlayback()}. The {@link DefaultControlDispatcher} that this manager + * uses by default, calls {@link Player#prepare()}. If you wish to intercept or customize this + * behaviour, you can provide a custom implementation of {@link + * ControlDispatcher#dispatchPrepare(Player)} and pass it to {@link + * #setControlDispatcher(ControlDispatcher)}. */ + @SuppressWarnings("deprecation") + @Deprecated public void setPlaybackPreparer(@Nullable PlaybackPreparer playbackPreparer) { this.playbackPreparer = playbackPreparer; } @@ -1039,8 +1045,7 @@ public class PlayerNotificationManager { @Nullable NotificationCompat.Builder builder, boolean ongoing, @Nullable Bitmap largeIcon) { - if (player.getPlaybackState() == Player.STATE_IDLE - && (player.getCurrentTimeline().isEmpty() || playbackPreparer == null)) { + if (player.getPlaybackState() == Player.STATE_IDLE && player.getCurrentTimeline().isEmpty()) { builderActions = null; return null; } @@ -1369,6 +1374,7 @@ public class PlayerNotificationManager { private class NotificationBroadcastReceiver extends BroadcastReceiver { + @SuppressWarnings("deprecation") @Override public void onReceive(Context context, Intent intent) { Player player = PlayerNotificationManager.this.player; @@ -1382,6 +1388,8 @@ public class PlayerNotificationManager { if (player.getPlaybackState() == Player.STATE_IDLE) { if (playbackPreparer != null) { playbackPreparer.preparePlayback(); + } else { + controlDispatcher.dispatchPrepare(player); } } else if (player.getPlaybackState() == Player.STATE_ENDED) { controlDispatcher.dispatchSeekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java index 049af9b64a..c1587d8cdf 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java @@ -983,11 +983,15 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider } /** - * Sets the {@link PlaybackPreparer}. - * - * @param playbackPreparer The {@link PlaybackPreparer}, or null to remove the current playback - * preparer. + * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} instead. The view calls {@link + * ControlDispatcher#dispatchPrepare(Player)} instead of {@link + * PlaybackPreparer#preparePlayback()}. The {@link DefaultControlDispatcher} that the view + * uses by default, calls {@link Player#prepare()}. If you wish to customize this behaviour, + * you can provide a custom implementation of {@link + * ControlDispatcher#dispatchPrepare(Player)}. */ + @SuppressWarnings("deprecation") + @Deprecated public void setPlaybackPreparer(@Nullable PlaybackPreparer playbackPreparer) { Assertions.checkStateNotNull(controller); controller.setPlaybackPreparer(playbackPreparer); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java index f17404b7a2..3cff0ef3cb 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerControlView.java @@ -834,11 +834,15 @@ public class StyledPlayerControlView extends FrameLayout { } /** - * Sets the {@link PlaybackPreparer}. - * - * @param playbackPreparer The {@link PlaybackPreparer}, or null to remove the current playback - * preparer. + * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} instead. The view calls {@link + * ControlDispatcher#dispatchPrepare(Player)} instead of {@link + * PlaybackPreparer#preparePlayback()}. The {@link DefaultControlDispatcher} that the view + * uses by default, calls {@link Player#prepare()}. If you wish to customize this behaviour, + * you can provide a custom implementation of {@link + * ControlDispatcher#dispatchPrepare(Player)}. */ + @SuppressWarnings("deprecation") + @Deprecated public void setPlaybackPreparer(@Nullable PlaybackPreparer playbackPreparer) { this.playbackPreparer = playbackPreparer; } @@ -1698,11 +1702,14 @@ public class StyledPlayerControlView extends FrameLayout { } } + @SuppressWarnings("deprecation") private void dispatchPlay(Player player) { @State int state = player.getPlaybackState(); if (state == Player.STATE_IDLE) { if (playbackPreparer != null) { playbackPreparer.preparePlayback(); + } else { + controlDispatcher.dispatchPrepare(player); } } else if (state == Player.STATE_ENDED) { seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java index 38d8bc9710..4ae1b32215 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java @@ -45,6 +45,7 @@ import androidx.annotation.RequiresApi; import androidx.core.content.ContextCompat; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ControlDispatcher; +import com.google.android.exoplayer2.DefaultControlDispatcher; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.PlaybackPreparer; import com.google.android.exoplayer2.Player; @@ -978,11 +979,15 @@ public class StyledPlayerView extends FrameLayout implements AdsLoader.AdViewPro } /** - * Sets the {@link PlaybackPreparer}. - * - * @param playbackPreparer The {@link PlaybackPreparer}, or null to remove the current playback - * preparer. + * @deprecated Use {@link #setControlDispatcher(ControlDispatcher)} instead. The view calls {@link + * ControlDispatcher#dispatchPrepare(Player)} instead of {@link + * PlaybackPreparer#preparePlayback()}. The {@link DefaultControlDispatcher} that the view + * uses by default, calls {@link Player#prepare()}. If you wish to customize this behaviour, + * you can provide a custom implementation of {@link + * ControlDispatcher#dispatchPrepare(Player)}. */ + @SuppressWarnings("deprecation") + @Deprecated public void setPlaybackPreparer(@Nullable PlaybackPreparer playbackPreparer) { Assertions.checkStateNotNull(controller); controller.setPlaybackPreparer(playbackPreparer); From 8e638ec75397536c63573315909563cf9d0ad346 Mon Sep 17 00:00:00 2001 From: kimvde Date: Tue, 10 Nov 2020 17:18:02 +0000 Subject: [PATCH 22/88] Work around AudioManager#getStreamVolume crashes Issue:#8191 PiperOrigin-RevId: 341632732 --- RELEASENOTES.md | 4 ++++ .../android/exoplayer2/StreamVolumeManager.java | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5950cbfaa6..f4b485aaee 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -16,6 +16,10 @@ `LeanbackPlayerAdapter` and use `ControlDispatcher` for dispatching prepare instead ([#7882](https://github.com/google/ExoPlayer/issues/7882)). +* Audio: + * Retry playback after some types of `AudioTrack` error. + * Work around `AudioManager` crashes when calling `getStreamVolume` + ([#8191](https://github.com/google/ExoPlayer/issues/8191)). * Extractors: * Matroska: Add support for 32-bit floating point PCM, and 8-bit and 16-bit big endian integer PCM diff --git a/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java b/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java index fa5d316b60..fe7f8c0f40 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java @@ -188,7 +188,14 @@ import com.google.android.exoplayer2.util.Util; } private static int getVolumeFromManager(AudioManager audioManager, @C.StreamType int streamType) { - return audioManager.getStreamVolume(streamType); + // AudioManager#getStreamVolume(int) throws an exception on some devices. See + // https://github.com/google/ExoPlayer/issues/8191. + try { + return audioManager.getStreamVolume(streamType); + } catch (RuntimeException e) { + Log.w(TAG, "Could not retrieve stream volume for stream type " + streamType, e); + return audioManager.getStreamMaxVolume(streamType); + } } private static boolean getMutedFromManager( @@ -196,7 +203,7 @@ import com.google.android.exoplayer2.util.Util; if (Util.SDK_INT >= 23) { return audioManager.isStreamMute(streamType); } else { - return audioManager.getStreamVolume(streamType) == 0; + return getVolumeFromManager(audioManager, streamType) == 0; } } From 12e428ec87dd9f76839a9a5c037e72ae01969b25 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 10 Nov 2020 20:01:35 +0000 Subject: [PATCH 23/88] Fix incorrect IntDef usage #minor-release PiperOrigin-RevId: 341668326 --- .../google/android/exoplayer2/ui/spherical/SceneRenderer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java index 5080e86345..674826e387 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SceneRenderer.java @@ -54,8 +54,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private @MonotonicNonNull SurfaceTexture surfaceTexture; // Used by other threads only - private volatile @C.StreamType int defaultStereoMode; - private @C.StreamType int lastStereoMode; + @C.StereoMode private volatile int defaultStereoMode; + @C.StereoMode private int lastStereoMode; @Nullable private byte[] lastProjectionData; // Methods called on any thread. From 0520c7d337c8112f8d3c4383e66af46241c70230 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 11 Nov 2020 16:14:12 +0000 Subject: [PATCH 24/88] Remove C.StreamType constant that's not a real stream type #minor-release PiperOrigin-RevId: 341833274 --- .../main/java/com/google/android/exoplayer2/C.java | 14 +++----------- .../com/google/android/exoplayer2/util/Util.java | 2 -- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/C.java b/library/common/src/main/java/com/google/android/exoplayer2/C.java index c4f4a2bbb5..b4208c5282 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/C.java @@ -253,8 +253,7 @@ public final class C { /** * Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link * #STREAM_TYPE_DTMF}, {@link #STREAM_TYPE_MUSIC}, {@link #STREAM_TYPE_NOTIFICATION}, {@link - * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link - * #STREAM_TYPE_USE_DEFAULT}. + * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM} or {@link #STREAM_TYPE_VOICE_CALL}. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -265,8 +264,7 @@ public final class C { STREAM_TYPE_NOTIFICATION, STREAM_TYPE_RING, STREAM_TYPE_SYSTEM, - STREAM_TYPE_VOICE_CALL, - STREAM_TYPE_USE_DEFAULT + STREAM_TYPE_VOICE_CALL }) public @interface StreamType {} /** @@ -297,13 +295,7 @@ public final class C { * @see AudioManager#STREAM_VOICE_CALL */ public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL; - /** - * @see AudioManager#USE_DEFAULT_STREAM_TYPE - */ - public static final int STREAM_TYPE_USE_DEFAULT = AudioManager.USE_DEFAULT_STREAM_TYPE; - /** - * The default stream type used by audio renderers. - */ + /** The default stream type used by audio renderers. Equal to {@link #STREAM_TYPE_MUSIC}. */ public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC; /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index 441ce84f8d..61c762615c 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -1689,7 +1689,6 @@ public final class Util { return C.USAGE_ASSISTANCE_SONIFICATION; case C.STREAM_TYPE_VOICE_CALL: return C.USAGE_VOICE_COMMUNICATION; - case C.STREAM_TYPE_USE_DEFAULT: case C.STREAM_TYPE_MUSIC: default: return C.USAGE_MEDIA; @@ -1710,7 +1709,6 @@ public final class Util { return C.CONTENT_TYPE_SONIFICATION; case C.STREAM_TYPE_VOICE_CALL: return C.CONTENT_TYPE_SPEECH; - case C.STREAM_TYPE_USE_DEFAULT: case C.STREAM_TYPE_MUSIC: default: return C.CONTENT_TYPE_MUSIC; From d5f5d311d2f0f99726c7b38b26686c0e5297340c Mon Sep 17 00:00:00 2001 From: christosts Date: Fri, 13 Nov 2020 13:18:54 +0000 Subject: [PATCH 25/88] Pass drm_key_request_properties in offline DRM downloads Pass the drm_key_request_properties specified in the json list when donwloading thee offline Widevide license in the demo app. PiperOrigin-RevId: 342243441 --- .../android/exoplayer2/demo/DownloadTracker.java | 12 +++++++----- .../android/exoplayer2/demo/PlayerActivity.java | 11 ++++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java index 07f4dd2f6e..cbae6fdc06 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java @@ -223,7 +223,7 @@ public class DownloadTracker { widevineOfflineLicenseFetchTask = new WidevineOfflineLicenseFetchTask( format, - mediaItem.playbackProperties.drmConfiguration.licenseUri, + mediaItem.playbackProperties.drmConfiguration, httpDataSourceFactory, /* dialogHelper= */ this, helper); @@ -373,7 +373,7 @@ public class DownloadTracker { private static final class WidevineOfflineLicenseFetchTask extends AsyncTask { private final Format format; - private final Uri licenseUri; + private final MediaItem.DrmConfiguration drmConfiguration; private final HttpDataSource.Factory httpDataSourceFactory; private final StartDownloadDialogHelper dialogHelper; private final DownloadHelper downloadHelper; @@ -383,12 +383,12 @@ public class DownloadTracker { public WidevineOfflineLicenseFetchTask( Format format, - Uri licenseUri, + MediaItem.DrmConfiguration drmConfiguration, HttpDataSource.Factory httpDataSourceFactory, StartDownloadDialogHelper dialogHelper, DownloadHelper downloadHelper) { this.format = format; - this.licenseUri = licenseUri; + this.drmConfiguration = drmConfiguration; this.httpDataSourceFactory = httpDataSourceFactory; this.dialogHelper = dialogHelper; this.downloadHelper = downloadHelper; @@ -398,8 +398,10 @@ public class DownloadTracker { protected Void doInBackground(Void... voids) { OfflineLicenseHelper offlineLicenseHelper = OfflineLicenseHelper.newWidevineInstance( - licenseUri.toString(), + drmConfiguration.licenseUri.toString(), + drmConfiguration.forceDefaultLicenseUri, httpDataSourceFactory, + drmConfiguration.requestHeaders, new DrmSessionEventListener.EventDispatcher()); try { keySetId = offlineLicenseHelper.downloadLicense(format); diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 60bdcf1986..c79f213d39 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -65,6 +65,7 @@ import java.net.CookiePolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; /** An activity that plays media using {@link SimpleExoPlayer}. */ public class PlayerActivity extends AppCompatActivity @@ -542,7 +543,9 @@ public class PlayerActivity extends AppCompatActivity .setCustomCacheKey(downloadRequest.customCacheKey) .setMimeType(downloadRequest.mimeType) .setStreamKeys(downloadRequest.streamKeys) - .setDrmKeySetId(downloadRequest.keySetId); + .setDrmKeySetId(downloadRequest.keySetId) + .setDrmLicenseRequestHeaders(getDrmRequestHeaders(item)); + mediaItems.add(builder.build()); } else { mediaItems.add(item); @@ -550,4 +553,10 @@ public class PlayerActivity extends AppCompatActivity } return mediaItems; } + + @Nullable + private static Map getDrmRequestHeaders(MediaItem item) { + MediaItem.DrmConfiguration drmConfiguration = item.playbackProperties.drmConfiguration; + return drmConfiguration != null ? drmConfiguration.requestHeaders : null; + } } From bf131ec0d249d9a64459c4ba125539628b8727da Mon Sep 17 00:00:00 2001 From: olly Date: Sun, 15 Nov 2020 16:04:08 +0000 Subject: [PATCH 26/88] Fix 2.12.1 release note PiperOrigin-RevId: 342512836 --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f4b485aaee..79cb8ab805 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -105,7 +105,7 @@ is in preparation for supporting ads in playlists ([#3750](https://github.com/google/ExoPlayer/issues/3750)). * Add a way to override ad media MIME types - ([#7961)(https://github.com/google/ExoPlayer/issues/7961)). + ([#7961](https://github.com/google/ExoPlayer/issues/7961)). * Fix incorrect truncation of large cue point positions ([#8067](https://github.com/google/ExoPlayer/issues/8067)). * Upgrade IMA SDK dependency to 3.20.1. This brings in a fix for From 82969c823cde614917235ff1329e88b011fbb514 Mon Sep 17 00:00:00 2001 From: insun Date: Mon, 16 Nov 2020 07:11:23 +0000 Subject: [PATCH 27/88] Increase touch target height of timebar in StyledPlayerControlView This change also introduces gravity attribute to DefaultTimeBar. PiperOrigin-RevId: 342573167 --- RELEASENOTES.md | 2 ++ .../android/exoplayer2/ui/DefaultTimeBar.java | 21 ++++++++++++++++++- library/ui/src/main/res/values/attrs.xml | 9 ++++++++ library/ui/src/main/res/values/dimens.xml | 4 ++-- library/ui/src/main/res/values/styles.xml | 1 + 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 79cb8ab805..e37f2fe1f2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -16,6 +16,8 @@ `LeanbackPlayerAdapter` and use `ControlDispatcher` for dispatching prepare instead ([#7882](https://github.com/google/ExoPlayer/issues/7882)). + * Add `bar_gravity` attribute into `DefaultTimeBar`. + * Increase seekbar's touch target height in `StyledPlayerControlView`. * Audio: * Retry playback after some types of `AudioTrack` error. * Work around `AudioManager` crashes when calling `getStreamVolume` diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java index f7a99a50dc..4e96d39e7c 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java @@ -152,6 +152,15 @@ public class DefaultTimeBar extends View implements TimeBar { /** Default color for played ad markers. */ public static final int DEFAULT_PLAYED_AD_MARKER_COLOR = 0x33FFFF00; + // LINT.IfChange + /** Vertical gravity for progress bar to be located at the center in the view. */ + public static final int BAR_GRAVITY_CENTER = 0; + /** Vertical gravity for progress bar to be located at the bottom in the view. */ + public static final int BAR_GRAVITY_BOTTOM = 1; + /** Vertical gravity for progress bar to be located at the top in the view. */ + public static final int BAR_GRAVITY_TOP = 2; + // LINT.ThenChange(../../../../../../../../../ui/src/main/res/values/attrs.xml) + /** The threshold in dps above the bar at which touch events trigger fine scrub mode. */ private static final int FINE_SCRUB_Y_THRESHOLD_DP = -50; /** The ratio by which times are reduced in fine scrub mode. */ @@ -186,6 +195,7 @@ public class DefaultTimeBar extends View implements TimeBar { @Nullable private final Drawable scrubberDrawable; private final int barHeight; private final int touchTargetHeight; + private final int barGravity; private final int adMarkerWidth; private final int scrubberEnabledSize; private final int scrubberDisabledSize; @@ -286,6 +296,7 @@ public class DefaultTimeBar extends View implements TimeBar { defaultBarHeight); touchTargetHeight = a.getDimensionPixelSize(R.styleable.DefaultTimeBar_touch_target_height, defaultTouchTargetHeight); + barGravity = a.getInt(R.styleable.DefaultTimeBar_bar_gravity, BAR_GRAVITY_CENTER); adMarkerWidth = a.getDimensionPixelSize(R.styleable.DefaultTimeBar_ad_marker_width, defaultAdMarkerWidth); scrubberEnabledSize = a.getDimensionPixelSize( @@ -318,6 +329,7 @@ public class DefaultTimeBar extends View implements TimeBar { } else { barHeight = defaultBarHeight; touchTargetHeight = defaultTouchTargetHeight; + barGravity = BAR_GRAVITY_CENTER; adMarkerWidth = defaultAdMarkerWidth; scrubberEnabledSize = defaultScrubberEnabledSize; scrubberDisabledSize = defaultScrubberDisabledSize; @@ -659,7 +671,14 @@ public class DefaultTimeBar extends View implements TimeBar { int barY = (height - touchTargetHeight) / 2; int seekLeft = getPaddingLeft(); int seekRight = width - getPaddingRight(); - int progressY = barY + (touchTargetHeight - barHeight) / 2; + int progressY; + if (barGravity == BAR_GRAVITY_BOTTOM) { + progressY = barY + touchTargetHeight - (getPaddingBottom() + scrubberPadding + barHeight / 2); + } else if (barGravity == BAR_GRAVITY_TOP) { + progressY = barY + getPaddingTop() + scrubberPadding - barHeight / 2; + } else { + progressY = barY + (touchTargetHeight - barHeight) / 2; + } seekBounds.set(seekLeft, barY, seekRight, barY + touchTargetHeight); progressBar.set(seekBounds.left + scrubberPadding, progressY, seekBounds.right - scrubberPadding, progressY + barHeight); diff --git a/library/ui/src/main/res/values/attrs.xml b/library/ui/src/main/res/values/attrs.xml index 439afb19c2..00456222c4 100644 --- a/library/ui/src/main/res/values/attrs.xml +++ b/library/ui/src/main/res/values/attrs.xml @@ -74,6 +74,11 @@ + + + + + @@ -154,6 +159,7 @@ + @@ -186,6 +192,7 @@ + @@ -217,6 +224,7 @@ + @@ -233,6 +241,7 @@ + diff --git a/library/ui/src/main/res/values/dimens.xml b/library/ui/src/main/res/values/dimens.xml index 93bfd8828d..3c4e998852 100644 --- a/library/ui/src/main/res/values/dimens.xml +++ b/library/ui/src/main/res/values/dimens.xml @@ -38,8 +38,8 @@ 2dp 10dp 14dp - 14dp - 14dp + 48dp + 48dp 52dp 60dp diff --git a/library/ui/src/main/res/values/styles.xml b/library/ui/src/main/res/values/styles.xml index 38daccb377..cbba94210a 100644 --- a/library/ui/src/main/res/values/styles.xml +++ b/library/ui/src/main/res/values/styles.xml @@ -187,6 +187,7 @@ +