From 3d8c9b0bf87f271a1d5cc69966159bbb15b024c4 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 22 Oct 2020 20:47:19 +0100 Subject: [PATCH 01/51] 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 6d5f167047d..64afcd393a2 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 static String getVideoMediaMimeType(@Nullable String codecs) { 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 46202a59912..6f68328dc73 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 @@ @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 0a9ead7c480..c8ef90742b3 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 @@ private static TsExtractor createTsExtractor( // 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 02/51] 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 64afcd393a2..d6dd67ee7d8 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 static String getVideoMediaMimeType(@Nullable String codecs) { */ 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 6f68328dc73..2baac87e858 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 void containsCodecsCorrespondingToMimeType_returnsCorrectResult() { .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 89e7687a21a..7f1af4496f9 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 @@ private static Format deriveFormat( 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 03/51] 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 745c44395f0..441ce84f8d7 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 static String getUserAgent(Context context, String applicationName) { + ") " + 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 5e0709228db..0089f68bf45 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 @@ private void buildAndPrepareMainSampleStreamWrapper( } } 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 @@ private void buildAndPrepareMainSampleStreamWrapper( 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 @@ private void buildAndPrepareMainSampleStreamWrapper( 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 @@ private void buildAndPrepareMainSampleStreamWrapper( /* 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 @@ private void buildAndPrepareAudioSampleStreamWrappers( continue; } - boolean renditionsHaveCodecs = true; + boolean codecStringsAllowChunklessPreparation = true; scratchPlaylistUrls.clear(); scratchPlaylistFormats.clear(); scratchIndicesList.clear(); @@ -704,7 +705,8 @@ private void buildAndPrepareAudioSampleStreamWrappers( 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 @@ private void buildAndPrepareAudioSampleStreamWrappers( 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 04/51] 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 5a5aa126558..e8daab543f0 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 66216de8617..fa5d316b60b 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.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 @@ public interface Listener { 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 @@ public interface Listener { 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 @@ public StreamVolumeManager(Context context, Handler eventHandler, Listener liste 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 @@ public void setMuted(boolean muted) { /** 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 05/51] 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 182afb04685..4f3f52a5f9d 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 @@ */ /* 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 @@ public static long getStreamDurationUs(@Nullable MediaInfo mediaInfo) { 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 06/51] 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 e8daab543f0..b8b950cbe36 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 ed20dedb108..8cdfb0dffc7 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 4185a158f75..c72650cf5ce 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 void release() { 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 07/51] 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 b8b950cbe36..de07aecfb67 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 18e5264c203..8de310a867f 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 08/51] 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 de07aecfb67..31f21e4c576 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 c72650cf5ce..e7a82534516 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 @@ private void resumeContentInternal() { 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 09/51] 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 e7a82534516..d619c1fe84f 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 @@ private VideoProgressUpdate getAdVideoProgressUpdate() { 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 void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) { 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 a4f1ec92cc4..6d69547278b 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.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 @@ public static boolean isAdGroupLoadError(AdError adError) { || 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 10/51] 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 31f21e4c576..387fb247a8a 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 d619c1fe84f..265ffe585b9 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 @@ private void handleTimelineOrPositionChanged() { 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 11/51] 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 24213918f50..4fdfaddea62 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 12/51] 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 556b04b8cad..0c25f2d43ea 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 Builder setClipStartsAtKeyFrame(boolean startsAtKeyFrame) { } /** - * 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 Builder setDrmLicenseUri(@Nullable Uri licenseUri) { } /** - * 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 Builder setDrmMultiSession(boolean multiSession) { } /** - * 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 static final class DrmConfiguration { 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 static final class DrmConfiguration { 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 @@ private DrmConfiguration( 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 7ab90b023ee..6a20cf7bdaf 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.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 byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws MediaDrmCa 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 7859254401f..f4a7b89fc7c 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 @@ 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 DrmSessionManager create(MediaItem mediaItem) { 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 DrmSessionManager create(MediaItem mediaItem) { : 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 13/51] 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 387fb247a8a..b1115d5c393 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 265ffe585b9..65c8920971c 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 void requestAds(DataSpec adTagDataSpec, @Nullable ViewGroup adViewGroup) 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 14/51] 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 58b9a853e70..ca84f901d86 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 15/51] 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 b1115d5c393..44a0ecedd75 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 4ce0ea8df52..907607f859f 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.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 @@ */ 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 @@ private void applyStyleRecord(ParsableByteArray parsableByteArray, 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 ca84f901d86..b64466cc00b 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 void decodeWithStyl() throws Exception { 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 16/51] 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 44a0ecedd75..55087d52677 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 660605ebe5d..c8f4cadcb1d 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 @@ private long scaleTimecodeToUs(long unscaledTimecode) throws ParserException { } 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 void initializeOutput(ExtractorOutput output, int trackId) throws ParserE 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 17/51] 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 88bc4e14c53..9527d35cef9 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.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 void playbackWithPrerollAdTag_playsAdAndContent() throws Exception { 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 void playbackWithMidrolls_playsAdAndContent() throws Exception { 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 void playbackWithMidrolls1And7_playsAdsAndContent() throws Exception { 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 void playbackWithMidrolls10And20WithSeekTo12_playsAdsAndContent() throws 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 int hashCode() { 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 @@ private ImaHostedTest(Uri contentUri, String adsResponse, AdId... expectedAdIds) // 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 void onPositionDiscontinuity( } }); 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 @@ protected MediaSource buildSource( 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 1543e11d268..98c59ec45d8 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 7b693747fc2..58c1834df3d 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 3456649b291..d55f960381e 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 bbf216bf125..01d62c3a82e 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 18/51] 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 b52a3e6f82b..7c899c1ea21 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 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 ed2bad6eeb6..f17404b7a23 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 void setSubTextAtPosition(int position, String subText) { } } - 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 SettingViewHolder(View itemView) { 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 void setCheckPosition(int checkPosition) { } } - 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 SubSettingViewHolder(View itemView) { 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 19/51] 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 55087d52677..5950cbfaa6b 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 c35080c47fa..60bdcf1986f 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 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 @@ /** 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 void onClick(View view) { } } - // PlaybackPreparer implementation - - @Override - public void preparePlayback() { - player.prepare(); - } - // PlayerControlView.VisibilityListener implementation @Override @@ -304,7 +296,6 @@ protected boolean initializePlayer() { 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 6538160b8b9..6da02bb3242 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 LeanbackPlayerAdapter(Context context, Player player, final int updatePer } /** - * 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 long getCurrentPosition() { 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 85d0155bd77..e78c55b2afb 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 void onPlay() { 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 7b78147e129..0d5e55fc833 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 @@ */ 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 d46b939c1fc..25c468330c9 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 DefaultControlDispatcher(long fastForwardIncrementMs, long rewindIncremen 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 8ff7f504025..3ef38c8520a 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 65a9a5ed8f7..1ae7812bd46 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 void setProgressUpdateListener(@Nullable ProgressUpdateListener listener) } /** - * 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 @@ private void dispatchPlayPause(Player player) { } } + @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 7c899c1ea21..b183fddbb6c 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 final void setPlayer(@Nullable Player player) { } /** - * 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 @@ protected NotificationCompat.Builder createNotification( @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 void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { 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 void onReceive(Context context, Intent intent) { 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 049af9b64a1..c1587d8cdff 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 void setControllerVisibilityListener( } /** - * 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 f17404b7a23..3cff0ef3cb5 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 void setProgressUpdateListener(@Nullable ProgressUpdateListener listener) } /** - * 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 @@ private void dispatchPlayPause(Player player) { } } + @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 38d8bc9710b..4ae1b322152 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.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 void setControllerOnFullScreenModeChangedListener( } /** - * 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 20/51] 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 5950cbfaa6b..f4b485aaee8 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 fa5d316b60b..fe7f8c0f403 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 @@ private void updateVolumeAndNotifyIfChanged() { } 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 @@ private static boolean getMutedFromManager( 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 21/51] 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 5080e863450..674826e3873 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 @@ 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 22/51] 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 c4f4a2bbb56..b4208c5282a 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 @@ private 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 @@ private 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 @@ private 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 441ce84f8d7..61c762615cd 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 static int getAudioUsageForStreamType(@C.StreamType int streamType) { 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 static int getAudioContentTypeForStreamType(@C.StreamType int streamType) 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 23/51] 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 07f4dd2f6ee..cbae6fdc067 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 void onPrepared(@NonNull DownloadHelper helper) { widevineOfflineLicenseFetchTask = new WidevineOfflineLicenseFetchTask( format, - mediaItem.playbackProperties.drmConfiguration.licenseUri, + mediaItem.playbackProperties.drmConfiguration, httpDataSourceFactory, /* dialogHelper= */ this, helper); @@ -373,7 +373,7 @@ private DownloadRequest buildDownloadRequest() { 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 @@ private static final class WidevineOfflineLicenseFetchTask extends AsyncTask createMediaItems(Intent intent, DownloadTracker d .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 @@ private static List createMediaItems(Intent intent, DownloadTracker d } 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 24/51] 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 f4b485aaee8..79cb8ab8051 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 25/51] 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 79cb8ab8051..e37f2fe1f2c 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 f7a99a50dc8..4e96d39e7c1 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 DefaultTimeBar( 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 DefaultTimeBar( } else { barHeight = defaultBarHeight; touchTargetHeight = defaultTouchTargetHeight; + barGravity = BAR_GRAVITY_CENTER; adMarkerWidth = defaultAdMarkerWidth; scrubberEnabledSize = defaultScrubberEnabledSize; scrubberDisabledSize = defaultScrubberDisabledSize; @@ -659,7 +671,14 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto 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 439afb19c27..00456222c48 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 93bfd8828df..3c4e9988525 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 38daccb3772..cbba94210a2 100644 --- a/library/ui/src/main/res/values/styles.xml +++ b/library/ui/src/main/res/values/styles.xml @@ -187,6 +187,7 @@ +