From ab054084193f210d01c6f1a5a0dd73d600fbfd62 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 15:24:20 +0100 Subject: [PATCH 001/210] Update release notes to better describe Java 8 chang Apps need to set the target compatibility to VERSION_1_8 to enable the automatic desugaring if they haven't done so already. Issue:#4907 --- RELEASENOTES.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 12b4bb4872..7a7560820d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,9 +2,8 @@ ### 2.9.0 ### -* Turn on Java 8 compiler support for the ExoPlayer library. Apps that depend - on ExoPlayer via its source code rather than an AAR may need to add - `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their +* Turn on Java 8 compiler support for the ExoPlayer library. Apps may need to + add `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their gradle settings to ensure bytecode compatibility. * Set `compileSdkVersion` and `targetSdkVersion` to 28. * Support for automatic audio focus handling via From fa9d7d5e3eb1a0bf5362d5c80b0c5b33a8b6151b Mon Sep 17 00:00:00 2001 From: ojw28 Date: Fri, 5 Oct 2018 13:24:01 +0100 Subject: [PATCH 002/210] Update RELEASENOTES.md Fix markdown --- RELEASENOTES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7a7560820d..dd7c9c95a9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -325,18 +325,18 @@ begins, and poll the audio timestamp less frequently once it starts advancing ([#3841](https://github.com/google/ExoPlayer/issues/3841)). * Add an option to skip silent audio in `PlaybackParameters` - ((#2635)[https://github.com/google/ExoPlayer/issues/2635]). + ([#2635](https://github.com/google/ExoPlayer/issues/2635)). * Fix an issue where playback of TrueHD streams would get stuck after seeking due to not finding a syncframe - ((#3845)[https://github.com/google/ExoPlayer/issues/3845]). + ([#3845](https://github.com/google/ExoPlayer/issues/3845)). * Fix an issue with eac3-joc playback where a codec would fail to configure - ((#4165)[https://github.com/google/ExoPlayer/issues/4165]). + ([#4165](https://github.com/google/ExoPlayer/issues/4165)). * Handle non-empty end-of-stream buffers, to fix gapless playback of streams with encoder padding when the decoder returns a non-empty final buffer. * Allow trimming more than one sample when applying an elst audio edit via gapless playback info. * Allow overriding skipping/scaling with custom `AudioProcessor`s - ((#3142)[https://github.com/google/ExoPlayer/issues/3142]). + ([#3142](https://github.com/google/ExoPlayer/issues/3142)). * Caching: * Add release method to the `Cache` interface, and prevent multiple instances of `SimpleCache` using the same folder at the same time. From a51c114942f1d90eba2003f5029270da5b506cd5 Mon Sep 17 00:00:00 2001 From: hacker1024 Date: Fri, 28 Sep 2018 08:10:57 +1000 Subject: [PATCH 003/210] Call rating with extras --- .../ext/mediasession/MediaSessionConnector.java | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 0915ee4b03..d0cde5f693 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 @@ -259,6 +259,9 @@ public final class MediaSessionConnector { /** See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat)}. */ void onSetRating(Player player, RatingCompat rating); + + /** See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat, Bundle)}. */ + void onSetRating(Player player, RatingCompat rating, Bundle extras); } /** @@ -1002,6 +1005,13 @@ public final class MediaSessionConnector { ratingCallback.onSetRating(player, rating); } } + + @Override + public void onSetRating(RatingCompat rating, Bundle extras) { + if (canDispatchToRatingCallback(PlaybackStateCompat.ACTION_SET_RATING)) { + ratingCallback.onSetRating(player, rating, extras); + } + } @Override public void onAddQueueItem(MediaDescriptionCompat description) { From edee8cfbde860042b57c446a661d79b3cafaafdf Mon Sep 17 00:00:00 2001 From: Andrew Shu Date: Thu, 27 Sep 2018 18:34:49 -0700 Subject: [PATCH 004/210] Make DefaultTrackSelector.AudioTrackScore protected This allows selectAudioTrack() to be overridden. --- .../android/exoplayer2/trackselection/DefaultTrackSelector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index f5a347b351..4a75b6f722 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -2141,7 +2141,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { } /** Represents how well an audio track matches the selection {@link Parameters}. */ - private static final class AudioTrackScore implements Comparable { + protected static final class AudioTrackScore implements Comparable { private final Parameters parameters; private final int withinRendererCapabilitiesScore; From 6b562f0a744600d73a1289c10ede3104ad2ebafc Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 28 Sep 2018 10:04:14 -0700 Subject: [PATCH 005/210] Add @Documented to @IntDef and @StringDef annotations. This makes the annotations appear in the generated JavaDoc. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214952419 --- .../ext/cronet/CronetEngineWrapper.java | 2 ++ .../exoplayer2/ext/flac/FlacExtractor.java | 2 ++ .../exoplayer2/ext/ima/ImaAdsLoader.java | 2 ++ .../ext/vp9/LibvpxVideoRenderer.java | 9 +++++-- .../java/com/google/android/exoplayer2/C.java | 18 +++++++++++++ .../exoplayer2/DefaultRenderersFactory.java | 2 ++ .../exoplayer2/ExoPlaybackException.java | 2 ++ .../com/google/android/exoplayer2/Player.java | 4 +++ .../google/android/exoplayer2/Renderer.java | 2 ++ .../android/exoplayer2/audio/Ac3Util.java | 2 ++ .../exoplayer2/audio/AudioFocusManager.java | 3 +++ .../audio/AudioTimestampPoller.java | 2 ++ .../audio/AudioTrackPositionTracker.java | 2 ++ .../exoplayer2/audio/DefaultAudioSink.java | 7 ++--- .../audio/SilenceSkippingAudioProcessor.java | 2 ++ .../audio/SimpleDecoderAudioRenderer.java | 9 +++++-- .../decoder/DecoderInputBuffer.java | 2 ++ .../drm/DefaultDrmSessionManager.java | 2 ++ .../android/exoplayer2/drm/DrmSession.java | 2 ++ .../drm/UnsupportedDrmException.java | 2 ++ .../extractor/BinarySearchSeeker.java | 2 ++ .../exoplayer2/extractor/Extractor.java | 2 ++ .../extractor/amr/AmrExtractor.java | 2 ++ .../extractor/flv/FlvExtractor.java | 15 +++++++---- .../extractor/mkv/DefaultEbmlReader.java | 2 ++ .../extractor/mkv/EbmlReaderOutput.java | 2 ++ .../extractor/mkv/MatroskaExtractor.java | 2 ++ .../extractor/mp3/Mp3Extractor.java | 2 ++ .../extractor/mp4/FragmentedMp4Extractor.java | 2 ++ .../extractor/mp4/Mp4Extractor.java | 8 +++--- .../exoplayer2/extractor/mp4/Track.java | 2 ++ .../exoplayer2/extractor/ts/Ac3Reader.java | 3 +++ .../extractor/ts/AdtsExtractor.java | 2 ++ .../ts/DefaultTsPayloadReaderFactory.java | 2 ++ .../exoplayer2/extractor/ts/TsExtractor.java | 2 ++ .../mediacodec/MediaCodecRenderer.java | 26 ++++++++++++++----- .../exoplayer2/offline/DownloadManager.java | 3 +++ .../exoplayer2/scheduler/Requirements.java | 2 ++ .../source/ClippingMediaSource.java | 2 ++ .../exoplayer2/source/MergingMediaSource.java | 2 ++ .../source/ads/AdPlaybackState.java | 2 ++ .../exoplayer2/source/ads/AdsMediaSource.java | 2 ++ .../exoplayer2/text/CaptionStyleCompat.java | 2 ++ .../google/android/exoplayer2/text/Cue.java | 4 +++ .../android/exoplayer2/text/TextRenderer.java | 9 +++++-- .../exoplayer2/text/ttml/TtmlStyle.java | 12 +++++++-- .../text/webvtt/WebvttCssStyle.java | 5 ++++ .../trackselection/MappingTrackSelector.java | 2 ++ .../android/exoplayer2/upstream/DataSpec.java | 3 +++ .../exoplayer2/upstream/HttpDataSource.java | 3 +++ .../android/exoplayer2/upstream/Loader.java | 2 ++ .../upstream/cache/CacheDataSource.java | 3 +++ .../exoplayer2/util/EGLSurfaceTexture.java | 2 ++ .../google/android/exoplayer2/util/Log.java | 2 ++ .../exoplayer2/util/NotificationUtil.java | 2 ++ .../exoplayer2/util/RepeatModeUtil.java | 2 ++ .../video/spherical/Projection.java | 2 ++ .../source/dash/DashMediaPeriod.java | 2 ++ .../source/hls/playlist/HlsMediaPlaylist.java | 2 ++ .../exoplayer2/ui/AspectRatioFrameLayout.java | 2 ++ .../ui/PlayerNotificationManager.java | 3 +++ .../android/exoplayer2/ui/PlayerView.java | 2 ++ 62 files changed, 208 insertions(+), 25 deletions(-) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java index dd39ea2822..829b53f863 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java @@ -19,6 +19,7 @@ import android.content.Context; import android.support.annotation.IntDef; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; @@ -43,6 +44,7 @@ public final class CronetEngineWrapper { * Source of {@link CronetEngine}. One of {@link #SOURCE_NATIVE}, {@link #SOURCE_GMS}, {@link * #SOURCE_UNKNOWN}, {@link #SOURCE_USER_PROVIDED} or {@link #SOURCE_UNAVAILABLE}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({SOURCE_NATIVE, SOURCE_GMS, SOURCE_UNKNOWN, SOURCE_USER_PROVIDED, SOURCE_UNAVAILABLE}) public @interface CronetEngineSource {} diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java index a1fbcc69d6..8f5dcef16b 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.util.FlacStreamInfo; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -54,6 +55,7 @@ public final class FlacExtractor implements Extractor { * Flags controlling the behavior of the extractor. Possible flag value is {@link * #FLAG_DISABLE_ID3_METADATA}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, 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 224f5aa6ee..95a3a588b4 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 @@ -60,6 +60,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -230,6 +231,7 @@ public final class ImaAdsLoader private static final int TIMEOUT_UNSET = -1; /** The state of ad playback. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED}) private @interface ImaAdState {} diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index c09d2fe55a..e3081cd2d2 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -64,9 +65,13 @@ import java.lang.annotation.RetentionPolicy; */ public class LibvpxVideoRenderer extends BaseRenderer { + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, - REINITIALIZATION_STATE_WAIT_END_OF_STREAM}) + @IntDef({ + REINITIALIZATION_STATE_NONE, + REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, + REINITIALIZATION_STATE_WAIT_END_OF_STREAM + }) private @interface ReinitializationState {} /** * The decoder does not need to be re-initialized. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 0cbdc14b1c..6c72dd8d0a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.UUID; @@ -114,6 +115,7 @@ public final class C { * Crypto modes for a codec. One of {@link #CRYPTO_MODE_UNENCRYPTED}, {@link #CRYPTO_MODE_AES_CTR} * or {@link #CRYPTO_MODE_AES_CBC}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC}) public @interface CryptoMode {} @@ -144,6 +146,7 @@ public final class C { * #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link * #ENCODING_DOLBY_TRUEHD}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ Format.NO_VALUE, @@ -169,6 +172,7 @@ public final class C { * #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link * #ENCODING_PCM_MU_LAW} or {@link #ENCODING_PCM_A_LAW}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ Format.NO_VALUE, @@ -215,6 +219,7 @@ public final class C { * #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link * #STREAM_TYPE_USE_DEFAULT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ STREAM_TYPE_ALARM, @@ -269,6 +274,7 @@ public final class C { * #CONTENT_TYPE_MOVIE}, {@link #CONTENT_TYPE_MUSIC}, {@link #CONTENT_TYPE_SONIFICATION}, {@link * #CONTENT_TYPE_SPEECH} or {@link #CONTENT_TYPE_UNKNOWN}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ CONTENT_TYPE_MOVIE, @@ -309,6 +315,7 @@ public final class C { *

Note that {@code FLAG_HW_AV_SYNC} is not available because the player takes care of setting * the flag when tunneling is enabled via a track selector. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -331,6 +338,7 @@ public final class C { * #USAGE_UNKNOWN}, {@link #USAGE_VOICE_COMMUNICATION} or {@link * #USAGE_VOICE_COMMUNICATION_SIGNALLING}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ USAGE_ALARM, @@ -427,6 +435,7 @@ public final class C { * #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link * #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ AUDIOFOCUS_NONE, @@ -454,6 +463,7 @@ public final class C { * #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_ENCRYPTED} and * {@link #BUFFER_FLAG_DECODE_ONLY}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -482,6 +492,7 @@ public final class C { * Video scaling modes for {@link MediaCodec}-based {@link Renderer}s. One of {@link * #VIDEO_SCALING_MODE_SCALE_TO_FIT} or {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}) public @interface VideoScalingMode {} @@ -504,6 +515,7 @@ public final class C { * Track selection flags. Possible flag values are {@link #SELECTION_FLAG_DEFAULT}, {@link * #SELECTION_FLAG_FORCED} and {@link #SELECTION_FLAG_AUTOSELECT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -530,6 +542,7 @@ public final class C { * Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link * #TYPE_HLS} or {@link #TYPE_OTHER}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER}) public @interface ContentType {} @@ -796,6 +809,7 @@ public final class C { * #STEREO_MODE_MONO}, {@link #STEREO_MODE_TOP_BOTTOM}, {@link #STEREO_MODE_LEFT_RIGHT} or {@link * #STEREO_MODE_STEREO_MESH}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ Format.NO_VALUE, @@ -827,6 +841,7 @@ public final class C { * Video colorspaces. One of {@link Format#NO_VALUE}, {@link #COLOR_SPACE_BT709}, {@link * #COLOR_SPACE_BT601} or {@link #COLOR_SPACE_BT2020}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020}) public @interface ColorSpace {} @@ -847,6 +862,7 @@ public final class C { * Video color transfer characteristics. One of {@link Format#NO_VALUE}, {@link * #COLOR_TRANSFER_SDR}, {@link #COLOR_TRANSFER_ST2084} or {@link #COLOR_TRANSFER_HLG}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG}) public @interface ColorTransfer {} @@ -867,6 +883,7 @@ public final class C { * Video color range. One of {@link Format#NO_VALUE}, {@link #COLOR_RANGE_LIMITED} or {@link * #COLOR_RANGE_FULL}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL}) public @interface ColorRange {} @@ -899,6 +916,7 @@ public final class C { * #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link #NETWORK_TYPE_ETHERNET} or * {@link #NETWORK_TYPE_OTHER}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ NETWORK_TYPE_UNKNOWN, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index 4e69bc316e..cc16c43b05 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.spherical.CameraMotionRenderer; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; @@ -56,6 +57,7 @@ public class DefaultRenderersFactory implements RenderersFactory { * Modes for using extension renderers. One of {@link #EXTENSION_RENDERER_MODE_OFF}, {@link * #EXTENSION_RENDERER_MODE_ON} or {@link #EXTENSION_RENDERER_MODE_PREFER}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, EXTENSION_RENDERER_MODE_PREFER}) public @interface ExtensionRendererMode {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java index d591876a51..6b84245141 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -31,6 +32,7 @@ public final class ExoPlaybackException extends Exception { * The type of source that produced the error. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} * or {@link #TYPE_UNEXPECTED}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED}) public @interface Type {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index d4b965dbd6..83014c6b10 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoListener; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -446,6 +447,7 @@ public interface Player { * Repeat modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link * #REPEAT_MODE_ALL}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL}) @interface RepeatMode {} @@ -467,6 +469,7 @@ public interface Player { * {@link #DISCONTINUITY_REASON_SEEK}, {@link #DISCONTINUITY_REASON_SEEK_ADJUSTMENT}, {@link * #DISCONTINUITY_REASON_AD_INSERTION} or {@link #DISCONTINUITY_REASON_INTERNAL}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ DISCONTINUITY_REASON_PERIOD_TRANSITION, @@ -497,6 +500,7 @@ public interface Player { * Reasons for timeline and/or manifest changes. One of {@link #TIMELINE_CHANGE_REASON_PREPARED}, * {@link #TIMELINE_CHANGE_REASON_RESET} or {@link #TIMELINE_CHANGE_REASON_DYNAMIC}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ TIMELINE_CHANGE_REASON_PREPARED, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index d1e1541cdc..c6456e5f7f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.util.MediaClock; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -38,6 +39,7 @@ public interface Renderer extends PlayerMessage.Target { * The renderer states. One of {@link #STATE_DISABLED}, {@link #STATE_ENABLED} or {@link * #STATE_STARTED}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED}) @interface State {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index 3e00bcc902..230b96d01f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -40,6 +41,7 @@ public final class Ac3Util { * AC3 stream types. See also ETSI TS 102 366 E.1.3.1.1. One of {@link #STREAM_TYPE_UNDEFINED}, * {@link #STREAM_TYPE_TYPE0}, {@link #STREAM_TYPE_TYPE1} or {@link #STREAM_TYPE_TYPE2}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STREAM_TYPE_UNDEFINED, STREAM_TYPE_TYPE0, STREAM_TYPE_TYPE1, STREAM_TYPE_TYPE2}) public @interface StreamType {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java index 5fb571d195..ca4e0c299e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -56,6 +57,7 @@ public final class AudioFocusManager { * Player commands. One of {@link #PLAYER_COMMAND_DO_NOT_PLAY}, {@link * #PLAYER_COMMAND_WAIT_FOR_CALLBACK} or {@link #PLAYER_COMMAND_PLAY_WHEN_READY}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ PLAYER_COMMAND_DO_NOT_PLAY, @@ -71,6 +73,7 @@ public final class AudioFocusManager { public static final int PLAYER_COMMAND_PLAY_WHEN_READY = 1; /** Audio focus state. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ AUDIO_FOCUS_STATE_LOST_FOCUS, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java index 47120e7375..569260efeb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java @@ -22,6 +22,7 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -45,6 +46,7 @@ import java.lang.annotation.RetentionPolicy; /* package */ final class AudioTimestampPoller { /** Timestamp polling states. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ STATE_INITIALIZING, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java index 0095001299..62b120f00a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrackPositionTracker.java @@ -25,6 +25,7 @@ import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; @@ -97,6 +98,7 @@ import java.lang.reflect.Method; } /** {@link AudioTrack} playback states. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({PLAYSTATE_STOPPED, PLAYSTATE_PAUSED, PLAYSTATE_PLAYING}) private @interface PlayState {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index fbd5b027c1..4ce34ad41a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -30,6 +30,7 @@ import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -195,12 +196,12 @@ public final class DefaultAudioSink implements AudioSink { private static final String TAG = "AudioTrack"; - /** - * Represents states of the {@link #startMediaTimeUs} value. - */ + /** Represents states of the {@link #startMediaTimeUs} value. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({START_NOT_SET, START_IN_SYNC, START_NEED_SYNC}) private @interface StartMediaTimeState {} + private static final int START_NOT_SET = 0; private static final int START_IN_SYNC = 1; private static final int START_NEED_SYNC = 2; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java index 7c4eacecfb..a1ff7028c1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -54,6 +55,7 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor { private static final byte SILENCE_THRESHOLD_LEVEL_MSB = (SILENCE_THRESHOLD_LEVEL + 128) >> 8; /** Trimming states. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ STATE_NOISY, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index a527a58be4..cecb17d96c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -43,6 +43,7 @@ import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -65,9 +66,13 @@ import java.lang.annotation.RetentionPolicy; */ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock { + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, - REINITIALIZATION_STATE_WAIT_END_OF_STREAM}) + @IntDef({ + REINITIALIZATION_STATE_NONE, + REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, + REINITIALIZATION_STATE_WAIT_END_OF_STREAM + }) private @interface ReinitializationState {} /** * The decoder does not need to be re-initialized. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java index 7a32ef128b..983c96f89d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.decoder; import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -31,6 +32,7 @@ public class DecoderInputBuffer extends Buffer { * #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} or {@link * #BUFFER_REPLACEMENT_MODE_DIRECT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ BUFFER_REPLACEMENT_MODE_DISABLED, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index 1c49011ee4..6062a6652a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.EventDispatcher; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -70,6 +71,7 @@ public class DefaultDrmSessionManager implements DrmSe * Determines the action to be done after a session acquired. One of {@link #MODE_PLAYBACK}, * {@link #MODE_QUERY}, {@link #MODE_DOWNLOAD} or {@link #MODE_RELEASE}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE}) public @interface Mode {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java index bed3545d78..f2fbe94895 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.drm; import android.annotation.TargetApi; import android.media.MediaDrm; import android.support.annotation.IntDef; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; @@ -43,6 +44,7 @@ public interface DrmSession { * The state of the DRM session. One of {@link #STATE_RELEASED}, {@link #STATE_ERROR}, {@link * #STATE_OPENING}, {@link #STATE_OPENED} or {@link #STATE_OPENED_WITH_KEYS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) @interface State {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java index 5bea83d020..7f4a0f5f03 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.drm; import android.support.annotation.IntDef; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,6 +29,7 @@ public final class UnsupportedDrmException extends Exception { * The reason for the exception. One of {@link #REASON_UNSUPPORTED_SCHEME} or {@link * #REASON_INSTANTIATION_ERROR}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR}) public @interface Reason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java index 435fb13648..3b0b834427 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -437,6 +438,7 @@ public abstract class BinarySearchSeeker { public static final int RESULT_POSITION_UNDERESTIMATED = -2; public static final int RESULT_NO_TIMESTAMP = -3; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ RESULT_TARGET_TIMESTAMP_FOUND, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java index 26d0788b33..05f5d98d3c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor; import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -48,6 +49,7 @@ public interface Extractor { * Result values that can be returned by {@link #read(ExtractorInput, PositionHolder)}. One of * {@link #RESULT_CONTINUE}, {@link #RESULT_SEEK} or {@link #RESULT_END_OF_INPUT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef(value = {RESULT_CONTINUE, RESULT_SEEK, RESULT_END_OF_INPUT}) @interface ReadResult {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java index dfdce02450..b93969acfe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/amr/AmrExtractor.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -51,6 +52,7 @@ public final class AmrExtractor implements Extractor { * Flags controlling the behavior of the extractor. Possible flag value is {@link * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java index 604a520526..4211cab489 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,13 +38,17 @@ public final class FlvExtractor implements Extractor { /** Factory for {@link FlvExtractor} instances. */ public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new FlvExtractor()}; - /** - * Extractor states. - */ + /** Extractor states. */ + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({STATE_READING_FLV_HEADER, STATE_SKIPPING_TO_TAG_HEADER, STATE_READING_TAG_HEADER, - STATE_READING_TAG_DATA}) + @IntDef({ + STATE_READING_FLV_HEADER, + STATE_SKIPPING_TO_TAG_HEADER, + STATE_READING_TAG_HEADER, + STATE_READING_TAG_DATA + }) private @interface States {} + private static final int STATE_READING_FLV_HEADER = 1; private static final int STATE_SKIPPING_TO_TAG_HEADER = 2; private static final int STATE_READING_TAG_HEADER = 3; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java index c0494e1ee0..0987bc473f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.Assertions; import java.io.EOFException; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; @@ -31,6 +32,7 @@ import java.util.ArrayDeque; */ /* package */ final class DefaultEbmlReader implements EbmlReader { + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ELEMENT_STATE_READ_ID, ELEMENT_STATE_READ_CONTENT_SIZE, ELEMENT_STATE_READ_CONTENT}) private @interface ElementState {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java index 067c88b552..cc17af5632 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.extractor.ExtractorInput; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -31,6 +32,7 @@ import java.lang.annotation.RetentionPolicy; * EBML element types. One of {@link #TYPE_UNKNOWN}, {@link #TYPE_MASTER}, {@link * #TYPE_UNSIGNED_INT}, {@link #TYPE_STRING}, {@link #TYPE_BINARY} or {@link #TYPE_FLOAT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNKNOWN, TYPE_MASTER, TYPE_UNSIGNED_INT, TYPE_STRING, TYPE_BINARY, TYPE_FLOAT}) @interface ElementType {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 35b6969084..63fee48771 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.video.AvcConfig; import com.google.android.exoplayer2.video.ColorInfo; import com.google.android.exoplayer2.video.HevcConfig; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -68,6 +69,7 @@ public final class MatroskaExtractor implements Extractor { * Flags controlling the behavior of the extractor. Possible flag value is {@link * #FLAG_DISABLE_SEEK_FOR_CUES}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 26a8bcce75..92cf590a49 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -35,6 +35,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,6 +51,7 @@ public final class Mp3Extractor implements Extractor { * Flags controlling the behavior of the extractor. Possible flag values are {@link * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} and {@link #FLAG_DISABLE_ID3_METADATA}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index d8adcde28c..0f1fd8f649 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -43,6 +43,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; @@ -67,6 +68,7 @@ public final class FragmentedMp4Extractor implements Extractor { * {@link #FLAG_ENABLE_EMSG_TRACK}, {@link #FLAG_SIDELOADED} and {@link * #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 7cf61b4ff3..17c82c2c5b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -35,6 +35,7 @@ import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; @@ -53,6 +54,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { * Flags controlling the behavior of the extractor. Possible flag value is {@link * #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -63,12 +65,12 @@ public final class Mp4Extractor implements Extractor, SeekMap { */ public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1; - /** - * Parser states. - */ + /** Parser states. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_READING_ATOM_HEADER, STATE_READING_ATOM_PAYLOAD, STATE_READING_SAMPLE}) private @interface State {} + private static final int STATE_READING_ATOM_HEADER = 0; private static final int STATE_READING_ATOM_PAYLOAD = 1; private static final int STATE_READING_SAMPLE = 2; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java index 867e037f4b..59cd602209 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -31,6 +32,7 @@ public final class Track { * The transformation to apply to samples in the track, if any. One of {@link * #TRANSFORMATION_NONE} or {@link #TRANSFORMATION_CEA608_CDAT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT}) public @interface Transformation {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java index 4141f83370..2ef9704a7a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -33,9 +34,11 @@ import java.lang.annotation.RetentionPolicy; */ public final class Ac3Reader implements ElementaryStreamReader { + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_FINDING_SYNC, STATE_READING_HEADER, STATE_READING_SAMPLE}) private @interface State {} + private static final int STATE_FINDING_SYNC = 0; private static final int STATE_READING_HEADER = 1; private static final int STATE_READING_SAMPLE = 2; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java index 0c2a0545dc..04a6b571bd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -47,6 +48,7 @@ public final class AdtsExtractor implements Extractor { * Flags controlling the behavior of the extractor. Possible flag value is {@link * #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 06a60776c2..94fd7ceb13 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.text.cea.Cea708InitializationData; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -39,6 +40,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact * #FLAG_IGNORE_H264_STREAM}, {@link #FLAG_DETECT_ACCESS_UNITS}, {@link * #FLAG_IGNORE_SPLICE_INFO_STREAM} and {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 978b1e8813..f47a481d7e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -57,6 +58,7 @@ public final class TsExtractor implements Extractor { * Modes for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} or {@link * #MODE_HLS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS}) public @interface Mode {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 7e933e9474..86bbb330b7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -46,6 +46,7 @@ import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.TimedValueQueue; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; @@ -182,6 +183,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * The possible return values for {@link #canKeepCodec(MediaCodec, MediaCodecInfo, Format, * Format)}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ KEEP_CODEC_RESULT_NO, @@ -199,9 +201,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { */ protected static final int KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION = 3; + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({RECONFIGURATION_STATE_NONE, RECONFIGURATION_STATE_WRITE_PENDING, - RECONFIGURATION_STATE_QUEUE_PENDING}) + @IntDef({ + RECONFIGURATION_STATE_NONE, + RECONFIGURATION_STATE_WRITE_PENDING, + RECONFIGURATION_STATE_QUEUE_PENDING + }) private @interface ReconfigurationState {} /** * There is no pending adaptive reconfiguration work. @@ -217,9 +223,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { */ private static final int RECONFIGURATION_STATE_QUEUE_PENDING = 2; + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, - REINITIALIZATION_STATE_WAIT_END_OF_STREAM}) + @IntDef({ + REINITIALIZATION_STATE_NONE, + REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM, + REINITIALIZATION_STATE_WAIT_END_OF_STREAM + }) private @interface ReinitializationState {} /** * The codec does not need to be re-initialized. @@ -238,9 +248,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { */ private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2; + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({ADAPTATION_WORKAROUND_MODE_NEVER, ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION, - ADAPTATION_WORKAROUND_MODE_ALWAYS}) + @IntDef({ + ADAPTATION_WORKAROUND_MODE_NEVER, + ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION, + ADAPTATION_WORKAROUND_MODE_ALWAYS + }) private @interface AdaptationWorkaroundMode {} /** * The adaptation workaround is never used. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java index 3b26741897..409f79f30b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.util.Util; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -532,6 +533,7 @@ public final class DownloadManager { * -> failed * */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_QUEUED, STATE_STARTED, STATE_COMPLETED, STATE_CANCELED, STATE_FAILED}) public @interface State {} @@ -621,6 +623,7 @@ public final class DownloadManager { * +-----------+------+-------+---------+-----------+-----------+--------+--------+------+ * */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ STATE_QUEUED, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java index 0ee8d76bc7..4d6dbd83be 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java @@ -27,6 +27,7 @@ import android.os.PowerManager; import android.support.annotation.IntDef; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -39,6 +40,7 @@ public final class Requirements { * Network types. One of {@link #NETWORK_TYPE_NONE}, {@link #NETWORK_TYPE_ANY}, {@link * #NETWORK_TYPE_UNMETERED}, {@link #NETWORK_TYPE_NOT_ROAMING} or {@link #NETWORK_TYPE_METERED}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ NETWORK_TYPE_NONE, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index 88e98e811f..5f80725805 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -41,6 +42,7 @@ public final class ClippingMediaSource extends CompositeMediaSource { * The reason clipping failed. One of {@link #REASON_INVALID_PERIOD_COUNT}, {@link * #REASON_NOT_SEEKABLE_TO_START} or {@link #REASON_START_EXCEEDS_END}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({REASON_INVALID_PERIOD_COUNT, REASON_NOT_SEEKABLE_TO_START, REASON_START_EXCEEDS_END}) public @interface Reason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index 746af5719e..ecb4b10c6a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.TransferListener; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -41,6 +42,7 @@ public final class MergingMediaSource extends CompositeMediaSource { public static final class IllegalMergeException extends IOException { /** The reason the merge failed. One of {@link #REASON_PERIOD_COUNT_MISMATCH}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({REASON_PERIOD_COUNT_MISMATCH}) public @interface Reason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 72fc162bc3..41adb78906 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -20,6 +20,7 @@ import android.support.annotation.CheckResult; import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -239,6 +240,7 @@ public final class AdPlaybackState { * #AD_STATE_AVAILABLE}, {@link #AD_STATE_SKIPPED}, {@link #AD_STATE_PLAYED} or {@link * #AD_STATE_ERROR}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ AD_STATE_UNAVAILABLE, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 3d5c41e8bc..7fc0f22bf3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -39,6 +39,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -87,6 +88,7 @@ public final class AdsMediaSource extends CompositeMediaSource { * Types of ad load exceptions. One of {@link #TYPE_AD}, {@link #TYPE_AD_GROUP}, {@link * #TYPE_ALL_ADS} or {@link #TYPE_UNEXPECTED}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_AD, TYPE_AD_GROUP, TYPE_ALL_ADS, TYPE_UNEXPECTED}) public @interface Type {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java b/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java index 87dcb97a81..e7bb0e16bf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java @@ -22,6 +22,7 @@ import android.support.annotation.IntDef; import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager.CaptionStyle; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,6 +36,7 @@ public final class CaptionStyleCompat { * #EDGE_TYPE_OUTLINE}, {@link #EDGE_TYPE_DROP_SHADOW}, {@link #EDGE_TYPE_RAISED} or {@link * #EDGE_TYPE_DEPRESSED}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ EDGE_TYPE_NONE, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java index e1305acd14..a5c666c44a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -19,6 +19,7 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.support.annotation.IntDef; import android.text.Layout.Alignment; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -36,6 +37,7 @@ public class Cue { * The type of anchor, which may be unset. One of {@link #TYPE_UNSET}, {@link #ANCHOR_TYPE_START}, * {@link #ANCHOR_TYPE_MIDDLE} or {@link #ANCHOR_TYPE_END}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END}) public @interface AnchorType {} @@ -66,6 +68,7 @@ public class Cue { * The type of line, which may be unset. One of {@link #TYPE_UNSET}, {@link #LINE_TYPE_FRACTION} * or {@link #LINE_TYPE_NUMBER}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER}) public @interface LineType {} @@ -85,6 +88,7 @@ public class Cue { * {@link #TEXT_SIZE_TYPE_FRACTIONAL}, {@link #TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING} or {@link * #TEXT_SIZE_TYPE_ABSOLUTE}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ TYPE_UNSET, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java index 5b74bd1505..16f82a7293 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; @@ -49,9 +50,13 @@ public final class TextRenderer extends BaseRenderer implements Callback { @Deprecated public interface Output extends TextOutput {} + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef({REPLACEMENT_STATE_NONE, REPLACEMENT_STATE_SIGNAL_END_OF_STREAM, - REPLACEMENT_STATE_WAIT_END_OF_STREAM}) + @IntDef({ + REPLACEMENT_STATE_NONE, + REPLACEMENT_STATE_SIGNAL_END_OF_STREAM, + REPLACEMENT_STATE_WAIT_END_OF_STREAM + }) private @interface ReplacementState {} /** * The decoder does not need to be replaced. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java index 90f93d5b21..a4f0cca955 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java @@ -19,6 +19,7 @@ import android.graphics.Typeface; import android.support.annotation.IntDef; import android.text.Layout; import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -29,25 +30,32 @@ import java.lang.annotation.RetentionPolicy; public static final int UNSPECIFIED = -1; + @Documented @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, - STYLE_BOLD_ITALIC}) + @IntDef( + flag = true, + value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, STYLE_BOLD_ITALIC}) public @interface StyleFlags {} + public static final int STYLE_NORMAL = Typeface.NORMAL; public static final int STYLE_BOLD = Typeface.BOLD; public static final int STYLE_ITALIC = Typeface.ITALIC; public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) public @interface FontSizeUnit {} + public static final int FONT_SIZE_UNIT_PIXEL = 1; public static final int FONT_SIZE_UNIT_EM = 2; public static final int FONT_SIZE_UNIT_PERCENT = 3; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({UNSPECIFIED, OFF, ON}) private @interface OptionalBoolean {} + private static final int OFF = 0; private static final int ON = 1; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java index 0e46fa0d2f..fe274a6241 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java @@ -19,6 +19,7 @@ import android.graphics.Typeface; import android.support.annotation.IntDef; import android.text.Layout; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -39,6 +40,7 @@ public final class WebvttCssStyle { * Style flag enum. Possible flag values are {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link * #STYLE_BOLD}, {@link #STYLE_ITALIC} and {@link #STYLE_BOLD_ITALIC}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -54,6 +56,7 @@ public final class WebvttCssStyle { * Font size unit enum. One of {@link #UNSPECIFIED}, {@link #FONT_SIZE_UNIT_PIXEL}, {@link * #FONT_SIZE_UNIT_EM} or {@link #FONT_SIZE_UNIT_PERCENT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) public @interface FontSizeUnit {} @@ -62,9 +65,11 @@ public final class WebvttCssStyle { public static final int FONT_SIZE_UNIT_EM = 2; public static final int FONT_SIZE_UNIT_PERCENT = 3; + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({UNSPECIFIED, OFF, ON}) private @interface OptionalBoolean {} + private static final int OFF = 0; private static final int ON = 1; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index c2fda67728..59a4f96fb0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.RendererConfiguration; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -48,6 +49,7 @@ public abstract class MappingTrackSelector extends TrackSelector { * {@link #RENDERER_SUPPORT_NO_TRACKS}, {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS}, {@link * #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS} or {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ RENDERER_SUPPORT_NO_TRACKS, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java index c968921822..4a4cc021f4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java @@ -20,6 +20,7 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -33,6 +34,7 @@ public final class DataSpec { * The flags that apply to any request for data. Possible flag values are {@link #FLAG_ALLOW_GZIP} * and {@link #FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -61,6 +63,7 @@ public final class DataSpec { * The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. One of {@link * #HTTP_METHOD_GET}, {@link #HTTP_METHOD_POST} or {@link #HTTP_METHOD_HEAD}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD}) public @interface HttpMethod {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index daf5d3281a..0be7b857df 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -20,6 +20,7 @@ import android.text.TextUtils; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; @@ -226,9 +227,11 @@ public interface HttpDataSource extends DataSource { */ class HttpDataSourceException extends IOException { + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_OPEN, TYPE_READ, TYPE_CLOSE}) public @interface Type {} + public static final int TYPE_OPEN = 1; public static final int TYPE_READ = 2; public static final int TYPE_CLOSE = 3; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java index 03219380c7..ac3b3c5c5e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.ExecutorService; @@ -136,6 +137,7 @@ public final class Loader implements LoaderErrorThrower { } /** Types of action that can be taken in response to a load error. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ ACTION_TYPE_RETRY, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index a91e3246cc..fa2068b99d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.upstream.cache.Cache.CacheException; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; import java.io.InterruptedIOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -59,6 +60,7 @@ public final class CacheDataSource implements DataSource { * Flags controlling the cache's behavior. Possible flag values are {@link #FLAG_BLOCK_ON_CACHE}, * {@link #FLAG_IGNORE_CACHE_ON_ERROR} and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, @@ -91,6 +93,7 @@ public final class CacheDataSource implements DataSource { * Reasons the cache may be ignored. One of {@link #CACHE_IGNORED_REASON_ERROR} or {@link * #CACHE_IGNORED_REASON_UNSET_LENGTH}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({CACHE_IGNORED_REASON_ERROR, CACHE_IGNORED_REASON_UNSET_LENGTH}) public @interface CacheIgnoredReason {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java index 90e37de828..deb981f0e8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java @@ -26,6 +26,7 @@ import android.opengl.GLES20; import android.os.Handler; import android.support.annotation.IntDef; import android.support.annotation.Nullable; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -43,6 +44,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL * Secure mode to be used by the EGL surface and context. One of {@link #SECURE_MODE_NONE}, {@link * #SECURE_MODE_SURFACELESS_CONTEXT} or {@link #SECURE_MODE_PROTECTED_PBUFFER}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({SECURE_MODE_NONE, SECURE_MODE_SURFACELESS_CONTEXT, SECURE_MODE_PROTECTED_PBUFFER}) public @interface SecureMode {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java index 6a1e686dec..34fb684d25 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Log.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.text.TextUtils; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,6 +29,7 @@ public final class Log { * Log level for ExoPlayer logcat logging. One of {@link #LOG_LEVEL_ALL}, {@link #LOG_LEVEL_INFO}, * {@link #LOG_LEVEL_WARNING}, {@link #LOG_LEVEL_ERROR} or {@link #LOG_LEVEL_OFF}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({LOG_LEVEL_ALL, LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR, LOG_LEVEL_OFF}) @interface LogLevel {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java index e70f576754..e45ab0952e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.support.annotation.StringRes; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -36,6 +37,7 @@ public final class NotificationUtil { * #IMPORTANCE_NONE}, {@link #IMPORTANCE_MIN}, {@link #IMPORTANCE_LOW}, {@link * #IMPORTANCE_DEFAULT} or {@link #IMPORTANCE_HIGH}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ IMPORTANCE_UNSPECIFIED, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java index de92e1ad93..5816e40623 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.util; import android.support.annotation.IntDef; import com.google.android.exoplayer2.Player; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,6 +31,7 @@ public final class RepeatModeUtil { * {@link #REPEAT_TOGGLE_MODE_NONE}, {@link #REPEAT_TOGGLE_MODE_ONE} and {@link * #REPEAT_TOGGLE_MODE_ALL}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java index 0a9d04bf0f..3d4879d50a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/spherical/Projection.java @@ -19,6 +19,7 @@ import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C.StereoMode; import com.google.android.exoplayer2.util.Assertions; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,6 +27,7 @@ import java.lang.annotation.RetentionPolicy; public final class Projection { /** Enforces allowed (sub) mesh draw modes. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({DRAW_MODE_TRIANGLES, DRAW_MODE_TRIANGLES_STRIP, DRAW_MODE_TRIANGLES_FAN}) public @interface DrawMode {} diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index a501435262..5c9a933508 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -47,6 +47,7 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.MimeTypes; import java.io.IOException; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -660,6 +661,7 @@ import java.util.List; private static final class TrackGroupInfo { + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({CATEGORY_PRIMARY, CATEGORY_EMBEDDED, CATEGORY_MANIFEST_EVENTS}) public @interface TrackGroupCategory {} diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index a29808933b..81d4e7a818 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -21,6 +21,7 @@ import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.offline.StreamKey; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; @@ -157,6 +158,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { * Type of the playlist, as defined by #EXT-X-PLAYLIST-TYPE. One of {@link * #PLAYLIST_TYPE_UNKNOWN}, {@link #PLAYLIST_TYPE_VOD} or {@link #PLAYLIST_TYPE_EVENT}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({PLAYLIST_TYPE_UNKNOWN, PLAYLIST_TYPE_VOD, PLAYLIST_TYPE_EVENT}) public @interface PlaylistType {} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java index 9d977d63b3..0d4c6a4038 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java @@ -20,6 +20,7 @@ import android.content.res.TypedArray; import android.support.annotation.IntDef; import android.util.AttributeSet; import android.widget.FrameLayout; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,6 +51,7 @@ public final class AspectRatioFrameLayout extends FrameLayout { * #RESIZE_MODE_FIXED_WIDTH}, {@link #RESIZE_MODE_FIXED_HEIGHT}, {@link #RESIZE_MODE_FILL} or * {@link #RESIZE_MODE_ZOOM}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ RESIZE_MODE_FIT, 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 778f81375e..47025d9bba 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 @@ -42,6 +42,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -250,6 +251,7 @@ public class PlayerNotificationManager { * NotificationCompat#VISIBILITY_PRIVATE}, {@link NotificationCompat#VISIBILITY_PUBLIC} or {@link * NotificationCompat#VISIBILITY_SECRET}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ NotificationCompat.VISIBILITY_PRIVATE, @@ -264,6 +266,7 @@ public class PlayerNotificationManager { * NotificationCompat#PRIORITY_HIGH}, {@link NotificationCompat#PRIORITY_LOW }or {@link * NotificationCompat#PRIORITY_MIN}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ NotificationCompat.PRIORITY_DEFAULT, 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 8127fd617a..e429f5bfa0 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 @@ -67,6 +67,7 @@ import com.google.android.exoplayer2.util.ErrorMessageProvider; import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoListener; +import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -249,6 +250,7 @@ public class PlayerView extends FrameLayout { * Determines when the buffering view is shown. One of {@link #SHOW_BUFFERING_NEVER}, {@link * #SHOW_BUFFERING_WHEN_PLAYING} or {@link #SHOW_BUFFERING_ALWAYS}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({SHOW_BUFFERING_NEVER, SHOW_BUFFERING_WHEN_PLAYING, SHOW_BUFFERING_ALWAYS}) public @interface ShowBuffering {} From 49215950416e931d0decac36ad1724768a86df9e Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 1 Oct 2018 07:50:26 -0700 Subject: [PATCH 006/210] Fix prepare position of DeferredMediaPeriods for windows with non-zero offset. If we prepare a deferred media period before the actual timeline is available, we either prepare with position zero (= the default) or with a non-zero initial seek position. So far, the zero (default) position got replaced by the actual default position (including any potential non-zero window offset) when the timeline became known. However, a non-zero initial seek position was not corrected by the non-zero window offset. This is fixed by this change. Related to that, we always assumed that the deferred media period will the first period in the actual timeline. This is not true if we prepare with an offset (either because of an initial seek position or because of a default window position). So, we also determine the actual first period when the timeline becomes known. Issue:#4873 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215213030 --- RELEASENOTES.md | 6 ++ .../source/ConcatenatingMediaSource.java | 89 +++++++++++++---- .../source/DeferredMediaPeriod.java | 30 +++--- .../android/exoplayer2/ExoPlayerTest.java | 98 ++++++++++++++++++- .../analytics/AnalyticsCollectorTest.java | 2 +- .../exoplayer2/testutil/ActionSchedule.java | 11 ++- 6 files changed, 195 insertions(+), 41 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index dd7c9c95a9..a74af7fb59 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,11 @@ # Release notes # +### 2.9.1 ### + +* Fix an issue with blind seeking to windows with non-zero offset in a + `ConcatenatingMediaSource` + ([#4873](https://github.com/google/ExoPlayer/issues/4873)). + ### 2.9.0 ### * Turn on Java 8 compiler support for the ExoPlayer library. Apps may need to diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index e0b7da8506..7418e84449 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source; import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; @@ -65,6 +66,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource periodPosition = + timeline.getPeriodPosition(window, period, /* windowIndex= */ 0, windowStartPositionUs); + Object periodUid = periodPosition.first; + long periodPositionUs = periodPosition.second; + mediaSourceHolder.timeline = DeferredTimeline.createWithRealTimeline(timeline, periodUid); + if (deferredMediaPeriod != null) { + deferredMediaPeriod.overridePreparePositionUs(periodPositionUs); MediaPeriodId idInSource = deferredMediaPeriod.id.copyWithPeriodUid( getChildPeriodUid(mediaSourceHolder, deferredMediaPeriod.id.periodUid)); deferredMediaPeriod.createPeriod(idInSource); } - mediaSourceHolder.isPrepared = true; } + mediaSourceHolder.isPrepared = true; scheduleListenerNotification(/* actionOnCompletion= */ null); } @@ -897,18 +932,32 @@ public class ConcatenatingMediaSource extends CompositeMediaSource 0 - ? timeline.getUidOfPeriod(0) - : replacedId); + /** + * Returns a copy with an updated timeline. This keeps the existing period replacement. + * + * @param timeline The new timeline. + */ + public DeferredTimeline cloneWithUpdatedTimeline(Timeline timeline) { + return new DeferredTimeline(timeline, replacedId); } + /** Returns wrapped timeline. */ public Timeline getTimeline() { return timeline; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java index 5f84731b8d..26c25a749e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DeferredMediaPeriod.java @@ -79,23 +79,19 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb this.listener = listener; } + /** Returns the position at which the deferred media period was prepared, in microseconds. */ + public long getPreparePositionUs() { + return preparePositionUs; + } + /** - * Sets the default prepare position at which to prepare the media period. This value is only used - * if the call to {@link MediaPeriod#prepare(Callback, long)} is being deferred and the call was - * made with a (presumably default) prepare position of 0. + * Overrides the default prepare position at which to prepare the media period. This value is only + * used if the call to {@link MediaPeriod#prepare(Callback, long)} is being deferred. * - *

Note that this will override an intentional seek to zero in the corresponding non-seekable - * timeline window. This is unlikely to be a problem as a non-zero default position usually only - * occurs for live playbacks and seeking to zero in a live window would cause - * BehindLiveWindowExceptions anyway. - * - * @param defaultPreparePositionUs The actual default prepare position, in microseconds. + * @param defaultPreparePositionUs The default prepare position to use, in microseconds. */ - public void setDefaultPreparePositionUs(long defaultPreparePositionUs) { - if (preparePositionUs == 0 && defaultPreparePositionUs != 0) { - preparePositionOverrideUs = defaultPreparePositionUs; - preparePositionUs = defaultPreparePositionUs; - } + public void overridePreparePositionUs(long defaultPreparePositionUs) { + preparePositionOverrideUs = defaultPreparePositionUs; } /** @@ -108,6 +104,10 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb public void createPeriod(MediaPeriodId id) { mediaPeriod = mediaSource.createPeriod(id, allocator); if (callback != null) { + long preparePositionUs = + preparePositionOverrideUs != C.TIME_UNSET + ? preparePositionOverrideUs + : this.preparePositionUs; mediaPeriod.prepare(this, preparePositionUs); } } @@ -157,7 +157,7 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb @Override public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { - if (preparePositionOverrideUs != C.TIME_UNSET && positionUs == 0) { + if (preparePositionOverrideUs != C.TIME_UNSET && positionUs == preparePositionUs) { positionUs = preparePositionOverrideUs; preparePositionOverrideUs = C.TIME_UNSET; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 8414be1588..407e9a3827 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -60,6 +60,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; import org.junit.Test; @@ -1346,7 +1347,7 @@ public final class ExoPlayerTest { () -> concatenatingMediaSource.addMediaSources( Arrays.asList(mediaSource, mediaSource))) - .waitForTimelineChanged(null) + .waitForTimelineChanged() .executeRunnable( new PlayerRunnable() { @Override @@ -2192,14 +2193,14 @@ public final class ExoPlayerTest { startPositionUs + expectedDurationUs); Clock clock = new AutoAdvancingFakeClock(); AtomicReference playerReference = new AtomicReference<>(); - AtomicReference positionAtDiscontinuityMs = new AtomicReference<>(); - AtomicReference clockAtStartMs = new AtomicReference<>(); - AtomicReference clockAtDiscontinuityMs = new AtomicReference<>(); + AtomicLong positionAtDiscontinuityMs = new AtomicLong(C.TIME_UNSET); + AtomicLong clockAtStartMs = new AtomicLong(C.TIME_UNSET); + AtomicLong clockAtDiscontinuityMs = new AtomicLong(C.TIME_UNSET); EventListener eventListener = new EventListener() { @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - if (playbackState == Player.STATE_READY && clockAtStartMs.get() == null) { + if (playbackState == Player.STATE_READY && clockAtStartMs.get() == C.TIME_UNSET) { clockAtStartMs.set(clock.elapsedRealtime()); } } @@ -2446,6 +2447,93 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); } + @Test + public void seekToUnpreparedWindowWithNonZeroOffsetInConcatenationStartsAtCorrectPosition() + throws Exception { + Timeline timeline = new FakeTimeline(/* windowCount= */ 1); + FakeMediaSource mediaSource = new FakeMediaSource(/* timeline= */ null, /* manifest= */ null); + MediaSource clippedMediaSource = + new ClippingMediaSource( + mediaSource, + /* startPositionUs= */ 3 * C.MICROS_PER_SECOND, + /* endPositionUs= */ C.TIME_END_OF_SOURCE); + MediaSource concatenatedMediaSource = new ConcatenatingMediaSource(clippedMediaSource); + AtomicLong positionWhenReady = new AtomicLong(); + ActionSchedule actionSchedule = + new ActionSchedule.Builder("seekToUnpreparedWindowWithNonZeroOffsetInConcatenation") + .pause() + .waitForPlaybackState(Player.STATE_BUFFERING) + .seek(/* positionMs= */ 10) + .waitForTimelineChanged() + .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline, /* newManifest= */ null)) + .waitForTimelineChanged() + .waitForPlaybackState(Player.STATE_READY) + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + positionWhenReady.set(player.getContentPosition()); + } + }) + .play() + .build(); + new Builder() + .setMediaSource(concatenatedMediaSource) + .setActionSchedule(actionSchedule) + .build(context) + .start() + .blockUntilEnded(TIMEOUT_MS); + + assertThat(positionWhenReady.get()).isEqualTo(10); + } + + @Test + public void seekToUnpreparedWindowWithMultiplePeriodsInConcatenationStartsAtCorrectPeriod() + throws Exception { + long periodDurationMs = 5000; + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount =*/ 2, + /* id= */ new Object(), + /* isSeekable= */ true, + /* isDynamic= */ false, + /* durationUs= */ 2 * periodDurationMs * 1000)); + FakeMediaSource mediaSource = new FakeMediaSource(/* timeline= */ null, /* manifest= */ null); + MediaSource concatenatedMediaSource = new ConcatenatingMediaSource(mediaSource); + AtomicInteger periodIndexWhenReady = new AtomicInteger(); + AtomicLong positionWhenReady = new AtomicLong(); + ActionSchedule actionSchedule = + new ActionSchedule.Builder("seekToUnpreparedWindowWithMultiplePeriodsInConcatenation") + .pause() + .waitForPlaybackState(Player.STATE_BUFFERING) + // Seek 10ms into the second period. + .seek(/* positionMs= */ periodDurationMs + 10) + .waitForTimelineChanged() + .executeRunnable(() -> mediaSource.setNewSourceInfo(timeline, /* newManifest= */ null)) + .waitForTimelineChanged() + .waitForPlaybackState(Player.STATE_READY) + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + periodIndexWhenReady.set(player.getCurrentPeriodIndex()); + positionWhenReady.set(player.getContentPosition()); + } + }) + .play() + .build(); + new Builder() + .setMediaSource(concatenatedMediaSource) + .setActionSchedule(actionSchedule) + .build(context) + .start() + .blockUntilEnded(TIMEOUT_MS); + + assertThat(periodIndexWhenReady.get()).isEqualTo(1); + assertThat(positionWhenReady.get()).isEqualTo(periodDurationMs + 10); + } + // Internal methods. private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index e0feae6f49..3649685f3e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -585,7 +585,7 @@ public final class AnalyticsCollectorTest { () -> concatenatedMediaSource.moveMediaSource( /* currentIndex= */ 0, /* newIndex= */ 1)) - .waitForTimelineChanged(/* expectedTimeline= */ null) + .waitForTimelineChanged() .play() .build(); TestAnalyticsListener listener = runAnalyticsTest(concatenatedMediaSource, actionSchedule); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index 6e37d7d070..54d97fb905 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -375,6 +375,15 @@ public final class ActionSchedule { return apply(new SendMessages(tag, target, windowIndex, positionMs, deleteAfterDelivery)); } + /** + * Schedules a delay until any timeline change. + * + * @return The builder, for convenience. + */ + public Builder waitForTimelineChanged() { + return apply(new WaitForTimelineChanged(tag, /* expectedTimeline= */ null)); + } + /** * Schedules a delay until the timeline changed to a specified expected timeline. * @@ -382,7 +391,7 @@ public final class ActionSchedule { * change. * @return The builder, for convenience. */ - public Builder waitForTimelineChanged(@Nullable Timeline expectedTimeline) { + public Builder waitForTimelineChanged(Timeline expectedTimeline) { return apply(new WaitForTimelineChanged(tag, expectedTimeline)); } From b59781b3eb34f00957a5872ad57da70c870bfc7e Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 1 Oct 2018 08:05:01 -0700 Subject: [PATCH 007/210] Increase supported libflac version to 1.3.2 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215214894 --- extensions/flac/README.md | 9 +++++---- extensions/flac/src/main/jni/Android.mk | 4 ++-- extensions/flac/src/main/jni/Application.mk | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/extensions/flac/README.md b/extensions/flac/README.md index fda5f0085d..54701eea1d 100644 --- a/extensions/flac/README.md +++ b/extensions/flac/README.md @@ -28,18 +28,19 @@ EXOPLAYER_ROOT="$(pwd)" FLAC_EXT_PATH="${EXOPLAYER_ROOT}/extensions/flac/src/main" ``` -* Download the [Android NDK][] and set its location in an environment variable: +* Download the [Android NDK][] (version <= 17c) and set its location in an + environment variable: ``` NDK_PATH="" ``` -* Download and extract flac-1.3.1 as "${FLAC_EXT_PATH}/jni/flac" folder: +* Download and extract flac-1.3.2 as "${FLAC_EXT_PATH}/jni/flac" folder: ``` cd "${FLAC_EXT_PATH}/jni" && \ -curl https://ftp.osuosl.org/pub/xiph/releases/flac/flac-1.3.1.tar.xz | tar xJ && \ -mv flac-1.3.1 flac +curl https://ftp.osuosl.org/pub/xiph/releases/flac/flac-1.3.2.tar.xz | tar xJ && \ +mv flac-1.3.2 flac ``` * Build the JNI native libraries from the command line: diff --git a/extensions/flac/src/main/jni/Android.mk b/extensions/flac/src/main/jni/Android.mk index ff54c1b3c0..69520a16e5 100644 --- a/extensions/flac/src/main/jni/Android.mk +++ b/extensions/flac/src/main/jni/Android.mk @@ -30,9 +30,9 @@ LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/flac/src/libFLAC/include LOCAL_SRC_FILES := $(FLAC_SOURCES) -LOCAL_CFLAGS += '-DVERSION="1.3.1"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY -DFLAC__NO_ASM +LOCAL_CFLAGS += '-DPACKAGE_VERSION="1.3.2"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_COMMON_IMPLEMENTATION -fPIC -DHAVE_SYS_PARAM_H -LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions +LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions -DFLAC__NO_ASM '-DFLAC__HAS_OGG=0' LOCAL_LDLIBS := -llog -lz -lm include $(BUILD_SHARED_LIBRARY) diff --git a/extensions/flac/src/main/jni/Application.mk b/extensions/flac/src/main/jni/Application.mk index 59bf5f8f87..eba20352f4 100644 --- a/extensions/flac/src/main/jni/Application.mk +++ b/extensions/flac/src/main/jni/Application.mk @@ -17,4 +17,4 @@ APP_OPTIM := release APP_STL := gnustl_static APP_CPPFLAGS := -frtti -APP_PLATFORM := android-9 +APP_PLATFORM := android-14 From e2c82a256ea15754690d2aee038e440804335d8d Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 3 Oct 2018 02:33:50 -0700 Subject: [PATCH 008/210] Fix positioning of subtitles. SubtitleView forwards the cue box position to SubtitlePainter. This should be the position relative to the canvas of the SubtitleView. Currently, however, we forward the position relative to the parent of SubtitleView. That causes problems if SubtitleView has a non-zero offset position to its parent. Issue:#4788 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215535281 --- RELEASENOTES.md | 3 +++ .../google/android/exoplayer2/ui/SubtitleView.java | 14 ++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a74af7fb59..eb405260df 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -5,6 +5,9 @@ * Fix an issue with blind seeking to windows with non-zero offset in a `ConcatenatingMediaSource` ([#4873](https://github.com/google/ExoPlayer/issues/4873)). +* Fix issue where subtitles have a wrong position if SubtitleView has a non-zero + offset to its parent + ([#4788](https://github.com/google/ExoPlayer/issues/4788)). ### 2.9.0 ### diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java index 7426671041..50a923bced 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java @@ -247,19 +247,17 @@ public final class SubtitleView extends View implements TextOutput { @Override public void dispatchDraw(Canvas canvas) { int cueCount = (cues == null) ? 0 : cues.size(); - int rawTop = getTop(); - int rawBottom = getBottom(); + int rawViewHeight = getHeight(); - // Calculate the bounds after padding is taken into account. - int left = getLeft() + getPaddingLeft(); - int top = rawTop + getPaddingTop(); - int right = getRight() - getPaddingRight(); - int bottom = rawBottom - getPaddingBottom(); + // Calculate the cue box bounds relative to the canvas after padding is taken into account. + int left = getPaddingLeft(); + int top = getPaddingTop(); + int right = getWidth() - getPaddingRight(); + int bottom = rawViewHeight - getPaddingBottom(); if (bottom <= top || right <= left) { // No space to draw subtitles. return; } - int rawViewHeight = rawBottom - rawTop; int viewHeightMinusPadding = bottom - top; float defaultViewTextSizePx = From 6ee946515ad4cb91b50f7c6e933df04b775d8200 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 3 Oct 2018 02:33:53 -0700 Subject: [PATCH 009/210] Fix left position for subtitles. When SubtitlePainter positions the cues centred in the given box, it must add the left offset of the box to get the correct position. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215535289 --- .../java/com/google/android/exoplayer2/ui/SubtitlePainter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index d8c61f5e05..4f22362de6 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -310,7 +310,7 @@ import com.google.android.exoplayer2.util.Util; textLeft = Math.max(textLeft, parentLeft); textRight = Math.min(textLeft + textWidth, parentRight); } else { - textLeft = (parentWidth - textWidth) / 2; + textLeft = (parentWidth - textWidth) / 2 + parentLeft; textRight = textLeft + textWidth; } From 4d6b008ef664b053133d6b22ce5bc6ff4f8dd19e Mon Sep 17 00:00:00 2001 From: ojw28 Date: Wed, 3 Oct 2018 22:15:11 +0100 Subject: [PATCH 010/210] Merge pull request #4582 from szaboa/feature/4306_srt_position_tags #4306 - Extract tags from SubRip subtitles, add support for alignment --- .../exoplayer2/text/subrip/SubripDecoder.java | 158 +++++++++++++++++- .../src/test/assets/subrip/typical_with_tags | 56 +++++++ .../text/subrip/SubripDecoderTest.java | 89 ++++++++++ 3 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 library/core/src/test/assets/subrip/typical_with_tags diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index 5598e063a6..182f1cf4b0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -15,7 +15,9 @@ */ package com.google.android.exoplayer2.text.subrip; +import android.support.annotation.StringDef; import android.text.Html; +import android.text.Layout; import android.text.Spanned; import android.text.TextUtils; import com.google.android.exoplayer2.text.Cue; @@ -23,6 +25,9 @@ import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.LongArray; import com.google.android.exoplayer2.util.ParsableByteArray; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -38,6 +43,33 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { private static final Pattern SUBRIP_TIMING_LINE = Pattern.compile("\\s*(" + SUBRIP_TIMECODE + ")\\s*-->\\s*(" + SUBRIP_TIMECODE + ")?\\s*"); + private static final Pattern SUBRIP_TAG_PATTERN = Pattern.compile("\\{\\\\.*?\\}"); + private static final String SUBRIP_ALIGNMENT_TAG = "\\{\\\\an[1-9]\\}"; + + private static final float DEFAULT_START_FRACTION = 0.08f; + private static final float DEFAULT_END_FRACTION = 1 - DEFAULT_START_FRACTION; + private static final float DEFAULT_MID_FRACTION = 0.5f; + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + ALIGN_BOTTOM_LEFT, ALIGN_BOTTOM_MID, ALIGN_BOTTOM_RIGHT, + ALIGN_MID_LEFT, ALIGN_MID_MID, ALIGN_MID_RIGHT, + ALIGN_TOP_LEFT, ALIGN_TOP_MID, ALIGN_TOP_RIGHT + }) + + private @interface SubRipTag {} + + // Possible valid alignment tags based on SSA v4+ specs + private static final String ALIGN_BOTTOM_LEFT = "{\\an1}"; + private static final String ALIGN_BOTTOM_MID = "{\\an2}"; + private static final String ALIGN_BOTTOM_RIGHT = "{\\an3}"; + private static final String ALIGN_MID_LEFT = "{\\an4}"; + private static final String ALIGN_MID_MID = "{\\an5}"; + private static final String ALIGN_MID_RIGHT = "{\\an6}"; + private static final String ALIGN_TOP_LEFT = "{\\an7}"; + private static final String ALIGN_TOP_MID = "{\\an8}"; + private static final String ALIGN_TOP_RIGHT = "{\\an9}"; + private final StringBuilder textBuilder; public SubripDecoder() { @@ -87,16 +119,32 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { } // Read and parse the text. + ArrayList tags = new ArrayList<>(); textBuilder.setLength(0); while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { if (textBuilder.length() > 0) { textBuilder.append("
"); } - textBuilder.append(currentLine.trim()); + textBuilder.append(processLine(currentLine, tags)); } Spanned text = Html.fromHtml(textBuilder.toString()); - cues.add(new Cue(text)); + Cue cue = null; + + // At end of this loop the clue must be created with the applied tags + for (String tag : tags) { + + // Check if the tag is an alignment tag + if (tag.matches(SUBRIP_ALIGNMENT_TAG)) { + cue = buildCue(text, tag); + + // Based on the specs, in case of alignment tags only the first appearance counts, so break + break; + } + } + + cues.add(cue == null ? new Cue(text) : cue); + if (haveEndTimecode) { cues.add(null); } @@ -108,6 +156,111 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { return new SubripSubtitle(cuesArray, cueTimesUsArray); } + /** + * Process the given line by first trimming it then extracting the tags from it + *

+ * The pattern that is used to extract the tags is specified in SSA v4+ specs and + * has the following form: "{\...}". + *

+ * "All override codes appear within braces {}" + * "All override codes are always preceded by a backslash \" + * + * @param currentLine Current line + * @param tags Extracted tags will be stored in this array list + * @return Processed line + */ + private String processLine(String currentLine, ArrayList tags) { + // Trim line + String trimmedLine = currentLine.trim(); + + // Extract tags + int replacedCharacters = 0; + StringBuilder processedLine = new StringBuilder(trimmedLine); + Matcher matcher = SUBRIP_TAG_PATTERN.matcher(trimmedLine); + + while (matcher.find()) { + String tag = matcher.group(); + tags.add(tag); + processedLine.replace(matcher.start() - replacedCharacters, matcher.end() - replacedCharacters, ""); + replacedCharacters += tag.length(); + } + + return processedLine.toString(); + } + + /** + * Build a {@link Cue} based on the given text and tag + *

+ * Match the alignment tag and calculate the line, position, position anchor accordingly + *

+ * Based on SSA v4+ specs the alignment tag can have the following form: {\an[1-9}, + * where the number specifies the direction (based on the numpad layout). + * Note. older SSA scripts may contain tags like {\a1[1-9]} but these are based on + * other direction rules, but multiple sources says that these are deprecated, so no support here either + * + * @param alignmentTag Alignment tag + * @return Built cue + */ + private Cue buildCue(Spanned text, String alignmentTag) { + float line, position; + @Cue.AnchorType int positionAnchor; + @Cue.AnchorType int lineAnchor; + + // Set position and position anchor (horizontal alignment) + switch (alignmentTag) { + case ALIGN_BOTTOM_LEFT: + case ALIGN_MID_LEFT: + case ALIGN_TOP_LEFT: + position = DEFAULT_START_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_START; + break; + case ALIGN_BOTTOM_MID: + case ALIGN_MID_MID: + case ALIGN_TOP_MID: + position = DEFAULT_MID_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; + break; + case ALIGN_BOTTOM_RIGHT: + case ALIGN_MID_RIGHT: + case ALIGN_TOP_RIGHT: + position = DEFAULT_END_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_END; + break; + default: + position = DEFAULT_MID_FRACTION; + positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; + break; + } + + // Set line and line anchor (vertical alignment) + switch (alignmentTag) { + case ALIGN_BOTTOM_LEFT: + case ALIGN_BOTTOM_MID: + case ALIGN_BOTTOM_RIGHT: + line = DEFAULT_END_FRACTION; + lineAnchor = Cue.ANCHOR_TYPE_END; + break; + case ALIGN_MID_LEFT: + case ALIGN_MID_MID: + case ALIGN_MID_RIGHT: + line = DEFAULT_MID_FRACTION; + lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; + break; + case ALIGN_TOP_LEFT: + case ALIGN_TOP_MID: + case ALIGN_TOP_RIGHT: + line = DEFAULT_START_FRACTION; + lineAnchor = Cue.ANCHOR_TYPE_START; + break; + default: + line = DEFAULT_END_FRACTION; + lineAnchor = Cue.ANCHOR_TYPE_END; + break; + } + + return new Cue(text, null, line, Cue.LINE_TYPE_FRACTION, lineAnchor, position, positionAnchor, Cue.DIMEN_UNSET); + } + private static long parseTimecode(Matcher matcher, int groupOffset) { long timestampMs = Long.parseLong(matcher.group(groupOffset + 1)) * 60 * 60 * 1000; timestampMs += Long.parseLong(matcher.group(groupOffset + 2)) * 60 * 1000; @@ -115,5 +268,4 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { timestampMs += Long.parseLong(matcher.group(groupOffset + 4)); return timestampMs * 1000; } - } diff --git a/library/core/src/test/assets/subrip/typical_with_tags b/library/core/src/test/assets/subrip/typical_with_tags new file mode 100644 index 0000000000..af196f8a04 --- /dev/null +++ b/library/core/src/test/assets/subrip/typical_with_tags @@ -0,0 +1,56 @@ +1 +00:00:00,000 --> 00:00:01,234 +This is {\an1} the first subtitle. + +2 +00:00:02,345 --> 00:00:03,456 +This is the second subtitle. +Second {\ an 2} subtitle with second line. + +3 +00:00:04,567 --> 00:00:08,901 +This {\an2} is the third {\ tag} subtitle. + +4 +00:00:09,567 --> 00:00:12,901 +This { \an2} is the fourth subtitle. + +5 +00:00:013,567 --> 00:00:14,901 +This {\an2} is the fifth subtitle with multiple {\xyz} valid {\qwe} tags. + +6 +00:00:015,567 --> 00:00:15,901 +This {\an1} is a lines. + +7 +00:00:016,567 --> 00:00:16,901 +This {\an2} is a line. + +8 +00:00:017,567 --> 00:00:17,901 +This {\an3} is a line. + +9 +00:00:018,567 --> 00:00:18,901 +This {\an4} is a line. + +10 +00:00:019,567 --> 00:00:19,901 +This {\an5} is a line. + +11 +00:00:020,567 --> 00:00:20,901 +This {\an6} is a line. + +12 +00:00:021,567 --> 00:00:22,901 +This {\an7} is a line. + +13 +00:00:023,567 --> 00:00:23,901 +This {\an8} is a line. + +14 +00:00:024,567 --> 00:00:24,901 +This {\an9} is a line. \ No newline at end of file diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java index e9abaca075..554184da5d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java @@ -18,6 +18,8 @@ package com.google.android.exoplayer2.text.subrip; import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.text.Cue; + import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,6 +38,7 @@ public final class SubripDecoderTest { private static final String TYPICAL_MISSING_SEQUENCE = "subrip/typical_missing_sequence"; private static final String TYPICAL_NEGATIVE_TIMESTAMPS = "subrip/typical_negative_timestamps"; private static final String TYPICAL_UNEXPECTED_END = "subrip/typical_unexpected_end"; + private static final String TYPICAL_WITH_TAGS = "subrip/typical_with_tags"; private static final String NO_END_TIMECODES_FILE = "subrip/no_end_timecodes"; @Test @@ -154,6 +157,92 @@ public final class SubripDecoderTest { .isEqualTo("Or to the end of the media."); } + @Test + public void testDecodeCueWithTag() throws IOException{ + SubripDecoder decoder = new SubripDecoder(); + byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_WITH_TAGS); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); + assertThat(subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString()) + .isEqualTo("This is the first subtitle."); + assertThat(subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString()) + .isEqualTo("This is the second subtitle.\nSecond subtitle with second line."); + assertThat(subtitle.getCues(subtitle.getEventTime(4)).get(0).text.toString()) + .isEqualTo("This is the third subtitle."); + + // Based on the SSA v4+ specs the curly bracket must be followed by a backslash, so this is + // not a valid tag (won't be parsed / replaced) + assertThat(subtitle.getCues(subtitle.getEventTime(6)).get(0).text.toString()) + .isEqualTo("This { \\an2} is the fourth subtitle."); + + assertThat(subtitle.getCues(subtitle.getEventTime(8)).get(0).text.toString()) + .isEqualTo("This is the fifth subtitle with multiple valid tags."); + + // Verify positions + + // {/an1} + assertThat(subtitle.getCues(subtitle.getEventTime(10)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + + assertThat(subtitle.getCues(subtitle.getEventTime(10)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + // {/an2} + assertThat(subtitle.getCues(subtitle.getEventTime(12)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + assertThat(subtitle.getCues(subtitle.getEventTime(12)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + // {/an3} + assertThat(subtitle.getCues(subtitle.getEventTime(14)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + assertThat(subtitle.getCues(subtitle.getEventTime(14)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + // {/an4} + assertThat(subtitle.getCues(subtitle.getEventTime(16)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + + assertThat(subtitle.getCues(subtitle.getEventTime(16)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + // {/an5} + assertThat(subtitle.getCues(subtitle.getEventTime(18)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + assertThat(subtitle.getCues(subtitle.getEventTime(18)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + // {/an6} + assertThat(subtitle.getCues(subtitle.getEventTime(20)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + assertThat(subtitle.getCues(subtitle.getEventTime(20)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + // {/an7} + assertThat(subtitle.getCues(subtitle.getEventTime(22)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + + assertThat(subtitle.getCues(subtitle.getEventTime(22)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + + // {/an8} + assertThat(subtitle.getCues(subtitle.getEventTime(24)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); + + assertThat(subtitle.getCues(subtitle.getEventTime(24)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + + // {/an9} + assertThat(subtitle.getCues(subtitle.getEventTime(26)).get(0).positionAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_END); + + assertThat(subtitle.getCues(subtitle.getEventTime(26)).get(0).lineAnchor) + .isEqualTo(Cue.ANCHOR_TYPE_START); + } + private static void assertTypicalCue1(SubripSubtitle subtitle, int eventIndex) { assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0); assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString()) From 4347bf04161b4a063de5ca15144778e5f7e543bb Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 3 Oct 2018 22:19:13 +0100 Subject: [PATCH 011/210] Subrip cleanup --- RELEASENOTES.md | 2 + .../exoplayer2/text/subrip/SubripDecoder.java | 171 ++++++++---------- .../src/test/assets/subrip/typical_with_tags | 4 +- .../text/subrip/SubripDecoderTest.java | 105 +++-------- 4 files changed, 111 insertions(+), 171 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index eb405260df..e5a04470a2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,6 +8,8 @@ * Fix issue where subtitles have a wrong position if SubtitleView has a non-zero offset to its parent ([#4788](https://github.com/google/ExoPlayer/issues/4788)). +* SubRip: Add support for alignment tags, and remove tags from the displayed + captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index 182f1cf4b0..3b039061b0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -15,9 +15,8 @@ */ package com.google.android.exoplayer2.text.subrip; -import android.support.annotation.StringDef; +import android.support.annotation.Nullable; import android.text.Html; -import android.text.Layout; import android.text.Spanned; import android.text.TextUtils; import com.google.android.exoplayer2.text.Cue; @@ -25,9 +24,6 @@ import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.LongArray; import com.google.android.exoplayer2.util.ParsableByteArray; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,6 +33,11 @@ import java.util.regex.Pattern; */ public final class SubripDecoder extends SimpleSubtitleDecoder { + // Fractional positions for use when alignment tags are present. + /* package */ static final float START_FRACTION = 0.08f; + /* package */ static final float END_FRACTION = 1 - START_FRACTION; + /* package */ static final float MID_FRACTION = 0.5f; + private static final String TAG = "SubripDecoder"; private static final String SUBRIP_TIMECODE = "(?:(\\d+):)?(\\d+):(\\d+),(\\d+)"; @@ -46,35 +47,24 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { private static final Pattern SUBRIP_TAG_PATTERN = Pattern.compile("\\{\\\\.*?\\}"); private static final String SUBRIP_ALIGNMENT_TAG = "\\{\\\\an[1-9]\\}"; - private static final float DEFAULT_START_FRACTION = 0.08f; - private static final float DEFAULT_END_FRACTION = 1 - DEFAULT_START_FRACTION; - private static final float DEFAULT_MID_FRACTION = 0.5f; - - @Retention(RetentionPolicy.SOURCE) - @StringDef({ - ALIGN_BOTTOM_LEFT, ALIGN_BOTTOM_MID, ALIGN_BOTTOM_RIGHT, - ALIGN_MID_LEFT, ALIGN_MID_MID, ALIGN_MID_RIGHT, - ALIGN_TOP_LEFT, ALIGN_TOP_MID, ALIGN_TOP_RIGHT - }) - - private @interface SubRipTag {} - - // Possible valid alignment tags based on SSA v4+ specs - private static final String ALIGN_BOTTOM_LEFT = "{\\an1}"; - private static final String ALIGN_BOTTOM_MID = "{\\an2}"; + // Alignment tags for SSA V4+. + private static final String ALIGN_BOTTOM_LEFT = "{\\an1}"; + private static final String ALIGN_BOTTOM_MID = "{\\an2}"; private static final String ALIGN_BOTTOM_RIGHT = "{\\an3}"; - private static final String ALIGN_MID_LEFT = "{\\an4}"; - private static final String ALIGN_MID_MID = "{\\an5}"; - private static final String ALIGN_MID_RIGHT = "{\\an6}"; - private static final String ALIGN_TOP_LEFT = "{\\an7}"; - private static final String ALIGN_TOP_MID = "{\\an8}"; - private static final String ALIGN_TOP_RIGHT = "{\\an9}"; + private static final String ALIGN_MID_LEFT = "{\\an4}"; + private static final String ALIGN_MID_MID = "{\\an5}"; + private static final String ALIGN_MID_RIGHT = "{\\an6}"; + private static final String ALIGN_TOP_LEFT = "{\\an7}"; + private static final String ALIGN_TOP_MID = "{\\an8}"; + private static final String ALIGN_TOP_RIGHT = "{\\an9}"; private final StringBuilder textBuilder; + private final ArrayList tags; public SubripDecoder() { super("SubripDecoder"); textBuilder = new StringBuilder(); + tags = new ArrayList<>(); } @Override @@ -118,9 +108,9 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { continue; } - // Read and parse the text. - ArrayList tags = new ArrayList<>(); + // Read and parse the text and tags. textBuilder.setLength(0); + tags.clear(); while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { if (textBuilder.length() > 0) { textBuilder.append("
"); @@ -129,21 +119,17 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { } Spanned text = Html.fromHtml(textBuilder.toString()); - Cue cue = null; - // At end of this loop the clue must be created with the applied tags - for (String tag : tags) { - - // Check if the tag is an alignment tag + String alignmentTag = null; + for (int i = 0; i < tags.size(); i++) { + String tag = tags.get(i); if (tag.matches(SUBRIP_ALIGNMENT_TAG)) { - cue = buildCue(text, tag); - - // Based on the specs, in case of alignment tags only the first appearance counts, so break + alignmentTag = tag; + // Subsequent alignment tags should be ignored. break; } } - - cues.add(cue == null ? new Cue(text) : cue); + cues.add(buildCue(text, alignmentTag)); if (haveEndTimecode) { cues.add(null); @@ -157,108 +143,93 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { } /** - * Process the given line by first trimming it then extracting the tags from it - *

- * The pattern that is used to extract the tags is specified in SSA v4+ specs and - * has the following form: "{\...}". - *

- * "All override codes appear within braces {}" - * "All override codes are always preceded by a backslash \" + * Trims and removes tags from the given line. The removed tags are added to {@code tags}. * - * @param currentLine Current line - * @param tags Extracted tags will be stored in this array list - * @return Processed line + * @param line The line to process. + * @param tags A list to which removed tags will be added. + * @return The processed line. */ - private String processLine(String currentLine, ArrayList tags) { - // Trim line - String trimmedLine = currentLine.trim(); - - // Extract tags - int replacedCharacters = 0; - StringBuilder processedLine = new StringBuilder(trimmedLine); - Matcher matcher = SUBRIP_TAG_PATTERN.matcher(trimmedLine); + private String processLine(String line, ArrayList tags) { + line = line.trim(); + int removedCharacterCount = 0; + StringBuilder processedLine = new StringBuilder(line); + Matcher matcher = SUBRIP_TAG_PATTERN.matcher(line); while (matcher.find()) { String tag = matcher.group(); tags.add(tag); - processedLine.replace(matcher.start() - replacedCharacters, matcher.end() - replacedCharacters, ""); - replacedCharacters += tag.length(); + int start = matcher.start() - removedCharacterCount; + int tagLength = tag.length(); + processedLine.replace(start, /* end= */ start + tagLength, /* str= */ ""); + removedCharacterCount += tagLength; } return processedLine.toString(); } /** - * Build a {@link Cue} based on the given text and tag - *

- * Match the alignment tag and calculate the line, position, position anchor accordingly - *

- * Based on SSA v4+ specs the alignment tag can have the following form: {\an[1-9}, - * where the number specifies the direction (based on the numpad layout). - * Note. older SSA scripts may contain tags like {\a1[1-9]} but these are based on - * other direction rules, but multiple sources says that these are deprecated, so no support here either + * Build a {@link Cue} based on the given text and alignment tag. * - * @param alignmentTag Alignment tag + * @param text The text. + * @param alignmentTag The alignment tag, or {@code null} if no alignment tag is available. * @return Built cue */ - private Cue buildCue(Spanned text, String alignmentTag) { - float line, position; - @Cue.AnchorType int positionAnchor; - @Cue.AnchorType int lineAnchor; + private Cue buildCue(Spanned text, @Nullable String alignmentTag) { + if (alignmentTag == null) { + return new Cue(text); + } - // Set position and position anchor (horizontal alignment) + // Horizontal alignment. + @Cue.AnchorType int positionAnchor; switch (alignmentTag) { case ALIGN_BOTTOM_LEFT: case ALIGN_MID_LEFT: case ALIGN_TOP_LEFT: - position = DEFAULT_START_FRACTION; positionAnchor = Cue.ANCHOR_TYPE_START; break; - case ALIGN_BOTTOM_MID: - case ALIGN_MID_MID: - case ALIGN_TOP_MID: - position = DEFAULT_MID_FRACTION; - positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; - break; case ALIGN_BOTTOM_RIGHT: case ALIGN_MID_RIGHT: case ALIGN_TOP_RIGHT: - position = DEFAULT_END_FRACTION; positionAnchor = Cue.ANCHOR_TYPE_END; break; + case ALIGN_BOTTOM_MID: + case ALIGN_MID_MID: + case ALIGN_TOP_MID: default: - position = DEFAULT_MID_FRACTION; positionAnchor = Cue.ANCHOR_TYPE_MIDDLE; break; } - // Set line and line anchor (vertical alignment) + // Vertical alignment. + @Cue.AnchorType int lineAnchor; switch (alignmentTag) { case ALIGN_BOTTOM_LEFT: case ALIGN_BOTTOM_MID: case ALIGN_BOTTOM_RIGHT: - line = DEFAULT_END_FRACTION; lineAnchor = Cue.ANCHOR_TYPE_END; break; - case ALIGN_MID_LEFT: - case ALIGN_MID_MID: - case ALIGN_MID_RIGHT: - line = DEFAULT_MID_FRACTION; - lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; - break; case ALIGN_TOP_LEFT: case ALIGN_TOP_MID: case ALIGN_TOP_RIGHT: - line = DEFAULT_START_FRACTION; lineAnchor = Cue.ANCHOR_TYPE_START; break; + case ALIGN_MID_LEFT: + case ALIGN_MID_MID: + case ALIGN_MID_RIGHT: default: - line = DEFAULT_END_FRACTION; - lineAnchor = Cue.ANCHOR_TYPE_END; + lineAnchor = Cue.ANCHOR_TYPE_MIDDLE; break; } - return new Cue(text, null, line, Cue.LINE_TYPE_FRACTION, lineAnchor, position, positionAnchor, Cue.DIMEN_UNSET); + return new Cue( + text, + /* textAlignment= */ null, + getFractionalPositionForAnchorType(lineAnchor), + Cue.LINE_TYPE_FRACTION, + lineAnchor, + getFractionalPositionForAnchorType(positionAnchor), + positionAnchor, + Cue.DIMEN_UNSET); } private static long parseTimecode(Matcher matcher, int groupOffset) { @@ -268,4 +239,16 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { timestampMs += Long.parseLong(matcher.group(groupOffset + 4)); return timestampMs * 1000; } + + /* package */ static float getFractionalPositionForAnchorType(@Cue.AnchorType int anchorType) { + switch (anchorType) { + case Cue.ANCHOR_TYPE_START: + return SubripDecoder.START_FRACTION; + case Cue.ANCHOR_TYPE_MIDDLE: + return SubripDecoder.MID_FRACTION; + case Cue.ANCHOR_TYPE_END: + default: + return SubripDecoder.END_FRACTION; + } + } } diff --git a/library/core/src/test/assets/subrip/typical_with_tags b/library/core/src/test/assets/subrip/typical_with_tags index af196f8a04..85e304b498 100644 --- a/library/core/src/test/assets/subrip/typical_with_tags +++ b/library/core/src/test/assets/subrip/typical_with_tags @@ -13,7 +13,7 @@ This {\an2} is the third {\ tag} subtitle. 4 00:00:09,567 --> 00:00:12,901 -This { \an2} is the fourth subtitle. +This { \an2} is not a valid tag due to the space after the opening bracket. 5 00:00:013,567 --> 00:00:14,901 @@ -53,4 +53,4 @@ This {\an8} is a line. 14 00:00:024,567 --> 00:00:24,901 -This {\an9} is a line. \ No newline at end of file +This {\an9} is a line. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java index 554184da5d..1430c70e09 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.text.Cue; - import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; @@ -158,89 +157,30 @@ public final class SubripDecoderTest { } @Test - public void testDecodeCueWithTag() throws IOException{ + public void testDecodeCueWithTag() throws IOException { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(RuntimeEnvironment.application, TYPICAL_WITH_TAGS); SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); - assertThat(subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString()) - .isEqualTo("This is the first subtitle."); - assertThat(subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString()) - .isEqualTo("This is the second subtitle.\nSecond subtitle with second line."); - assertThat(subtitle.getCues(subtitle.getEventTime(4)).get(0).text.toString()) - .isEqualTo("This is the third subtitle."); - // Based on the SSA v4+ specs the curly bracket must be followed by a backslash, so this is - // not a valid tag (won't be parsed / replaced) + assertTypicalCue1(subtitle, 0); + assertTypicalCue2(subtitle, 2); + assertTypicalCue3(subtitle, 4); + assertThat(subtitle.getCues(subtitle.getEventTime(6)).get(0).text.toString()) - .isEqualTo("This { \\an2} is the fourth subtitle."); + .isEqualTo("This { \\an2} is not a valid tag due to the space after the opening bracket."); assertThat(subtitle.getCues(subtitle.getEventTime(8)).get(0).text.toString()) .isEqualTo("This is the fifth subtitle with multiple valid tags."); - // Verify positions - - // {/an1} - assertThat(subtitle.getCues(subtitle.getEventTime(10)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); - - assertThat(subtitle.getCues(subtitle.getEventTime(10)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - // {/an2} - assertThat(subtitle.getCues(subtitle.getEventTime(12)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - assertThat(subtitle.getCues(subtitle.getEventTime(12)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - // {/an3} - assertThat(subtitle.getCues(subtitle.getEventTime(14)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - assertThat(subtitle.getCues(subtitle.getEventTime(14)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - // {/an4} - assertThat(subtitle.getCues(subtitle.getEventTime(16)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); - - assertThat(subtitle.getCues(subtitle.getEventTime(16)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - // {/an5} - assertThat(subtitle.getCues(subtitle.getEventTime(18)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - assertThat(subtitle.getCues(subtitle.getEventTime(18)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - // {/an6} - assertThat(subtitle.getCues(subtitle.getEventTime(20)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - assertThat(subtitle.getCues(subtitle.getEventTime(20)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - // {/an7} - assertThat(subtitle.getCues(subtitle.getEventTime(22)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); - - assertThat(subtitle.getCues(subtitle.getEventTime(22)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); - - // {/an8} - assertThat(subtitle.getCues(subtitle.getEventTime(24)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_MIDDLE); - - assertThat(subtitle.getCues(subtitle.getEventTime(24)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); - - // {/an9} - assertThat(subtitle.getCues(subtitle.getEventTime(26)).get(0).positionAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_END); - - assertThat(subtitle.getCues(subtitle.getEventTime(26)).get(0).lineAnchor) - .isEqualTo(Cue.ANCHOR_TYPE_START); + assertAlignmentCue(subtitle, 10, Cue.ANCHOR_TYPE_END, Cue.ANCHOR_TYPE_START); // {/an1} + assertAlignmentCue(subtitle, 12, Cue.ANCHOR_TYPE_END, Cue.ANCHOR_TYPE_MIDDLE); // {/an2} + assertAlignmentCue(subtitle, 14, Cue.ANCHOR_TYPE_END, Cue.ANCHOR_TYPE_END); // {/an3} + assertAlignmentCue(subtitle, 16, Cue.ANCHOR_TYPE_MIDDLE, Cue.ANCHOR_TYPE_START); // {/an4} + assertAlignmentCue(subtitle, 18, Cue.ANCHOR_TYPE_MIDDLE, Cue.ANCHOR_TYPE_MIDDLE); // {/an5} + assertAlignmentCue(subtitle, 20, Cue.ANCHOR_TYPE_MIDDLE, Cue.ANCHOR_TYPE_END); // {/an6} + assertAlignmentCue(subtitle, 22, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_START); // {/an7} + assertAlignmentCue(subtitle, 24, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_MIDDLE); // {/an8} + assertAlignmentCue(subtitle, 26, Cue.ANCHOR_TYPE_START, Cue.ANCHOR_TYPE_END); // {/an9} } private static void assertTypicalCue1(SubripSubtitle subtitle, int eventIndex) { @@ -263,4 +203,19 @@ public final class SubripDecoderTest { .isEqualTo("This is the third subtitle."); assertThat(subtitle.getEventTime(eventIndex + 1)).isEqualTo(8901000); } + + private static void assertAlignmentCue( + SubripSubtitle subtitle, + int eventIndex, + @Cue.AnchorType int lineAnchor, + @Cue.AnchorType int positionAnchor) { + long eventTimeUs = subtitle.getEventTime(eventIndex); + Cue cue = subtitle.getCues(eventTimeUs).get(0); + assertThat(cue.lineType).isEqualTo(Cue.LINE_TYPE_FRACTION); + assertThat(cue.lineAnchor).isEqualTo(lineAnchor); + assertThat(cue.line).isEqualTo(SubripDecoder.getFractionalPositionForAnchorType(lineAnchor)); + assertThat(cue.positionAnchor).isEqualTo(positionAnchor); + assertThat(cue.position) + .isEqualTo(SubripDecoder.getFractionalPositionForAnchorType(positionAnchor)); + } } From a35bf5151da9b02d55d050edd89de8f936173f71 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 03:32:54 -0700 Subject: [PATCH 012/210] Fix updates of loading period and buffered positions in PlaybackInfo. This makes the following changes to improve consistency among the PlaybackInfo values: 1. Update buffered position and total buffered duration after loading period is set as both values are affected by a loading period update. 2. Add copyWithPosition to allow updating the position without resetting the loading period. 3. Forward the total buffered duration to playing position updates as it may have changed with the new playing position. Issue:#4899 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215712328 --- RELEASENOTES.md | 3 + .../android/exoplayer2/ExoPlayerImpl.java | 2 +- .../exoplayer2/ExoPlayerImplInternal.java | 55 +++++++--- .../android/exoplayer2/PlaybackInfo.java | 102 +++++++++++++++++- .../android/exoplayer2/ExoPlayerTest.java | 53 +++++++++ 5 files changed, 198 insertions(+), 17 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e5a04470a2..1a424e502e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -10,6 +10,9 @@ ([#4788](https://github.com/google/ExoPlayer/issues/4788)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). +* Fix issue where buffered position is not updated correctly when transitioning + between periods + ([#4899](https://github.com/google/ExoPlayer/issues/4899)). ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 027f316493..7912878e20 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -692,7 +692,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (playbackInfo.startPositionUs == C.TIME_UNSET) { // Replace internal unset start position with externally visible start position of zero. playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( playbackInfo.periodId, /* startPositionUs= */ 0, playbackInfo.contentPositionUs); } if ((!this.playbackInfo.timeline.isEmpty() || hasPendingPrepare) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index d861020d26..83b1243f42 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -448,7 +448,11 @@ import java.util.Collections; seekToPeriodPosition(periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true); if (newPositionUs != playbackInfo.positionUs) { playbackInfo = - playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs); + playbackInfo.copyWithNewPosition( + periodId, + newPositionUs, + playbackInfo.contentPositionUs, + getTotalBufferedDurationUs()); if (sendDiscontinuity) { playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); } @@ -483,8 +487,12 @@ import java.util.Collections; // A MediaPeriod may report a discontinuity at the current playback position to ensure the // renderers are flushed. Only report the discontinuity externally if the position changed. if (periodPositionUs != playbackInfo.positionUs) { - playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs, - playbackInfo.contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + playbackInfo.periodId, + periodPositionUs, + playbackInfo.contentPositionUs, + getTotalBufferedDurationUs()); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); } } else { @@ -647,7 +655,9 @@ import java.util.Collections; periodPositionUs = newPeriodPositionUs; } } finally { - playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + periodId, periodPositionUs, contentPositionUs, getTotalBufferedDurationUs()); if (seekPositionAdjusted) { playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT); } @@ -1020,8 +1030,12 @@ import java.util.Collections; playbackInfo.positionUs, recreateStreams, streamResetFlags); if (playbackInfo.playbackState != Player.STATE_ENDED && periodPositionUs != playbackInfo.positionUs) { - playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs, - playbackInfo.contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + playbackInfo.periodId, + periodPositionUs, + playbackInfo.contentPositionUs, + getTotalBufferedDurationUs()); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); resetRendererPosition(periodPositionUs); } @@ -1165,7 +1179,7 @@ import java.util.Collections; resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true); } catch (IllegalSeekPositionException e) { playbackInfo = - playbackInfo.fromNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET); + playbackInfo.resetToNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET); throw e; } pendingInitialSeekPosition = null; @@ -1178,7 +1192,7 @@ import java.util.Collections; long positionUs = periodPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, positionUs); playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs); } } else if (playbackInfo.startPositionUs == C.TIME_UNSET) { @@ -1192,7 +1206,7 @@ import java.util.Collections; long startPositionUs = defaultPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs); playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( periodId, periodId.isAd() ? 0 : startPositionUs, /* contentPositionUs= */ startPositionUs); @@ -1211,7 +1225,7 @@ import java.util.Collections; long startPositionUs = defaultPosition.second; MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs); playbackInfo = - playbackInfo.fromNewPosition( + playbackInfo.resetToNewPosition( periodId, /* startPositionUs= */ periodId.isAd() ? 0 : startPositionUs, /* contentPositionUs= */ startPositionUs); @@ -1250,7 +1264,9 @@ import java.util.Collections; } // Actually do the seek. long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs); - playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs()); return; } @@ -1262,7 +1278,9 @@ import java.util.Collections; // The previously playing ad should no longer be played, so skip it. long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs); - playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs()); return; } } @@ -1418,8 +1436,12 @@ import java.util.Collections; MediaPeriodHolder oldPlayingPeriodHolder = playingPeriodHolder; playingPeriodHolder = queue.advancePlayingPeriod(); updatePlayingPeriodRenderers(oldPlayingPeriodHolder); - playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id, - playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs); + playbackInfo = + playbackInfo.copyWithNewPosition( + playingPeriodHolder.info.id, + playingPeriodHolder.info.startPositionUs, + playingPeriodHolder.info.contentPositionUs, + getTotalBufferedDurationUs()); playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason); updatePlaybackPositions(); advancedPlayingPeriod = true; @@ -1667,6 +1689,11 @@ import java.util.Collections; if (loadingMediaPeriodChanged) { playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId); } + playbackInfo.bufferedPositionUs = + loadingMediaPeriodHolder == null + ? playbackInfo.positionUs + : loadingMediaPeriodHolder.getBufferedPositionUs(); + playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs(); if ((loadingMediaPeriodChanged || loadingTrackSelectionChanged) && loadingMediaPeriodHolder != null && loadingMediaPeriodHolder.prepared) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 02058c0484..8c73fde3be 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2; +import android.support.annotation.CheckResult; import android.support.annotation.Nullable; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -40,7 +41,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; public final MediaPeriodId periodId; /** * The start position at which playback started in {@link #periodId} relative to the start of the - * associated period in the {@link #timeline}, in microseconds. + * associated period in the {@link #timeline}, in microseconds. Note that this value changes for + * each position discontinuity. */ public final long startPositionUs; /** @@ -103,6 +105,23 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; startPositionUs); } + /** + * Create playback info. + * + * @param timeline See {@link #timeline}. + * @param manifest See {@link #manifest}. + * @param periodId See {@link #periodId}. + * @param startPositionUs See {@link #startPositionUs}. + * @param contentPositionUs See {@link #contentPositionUs}. + * @param playbackState See {@link #playbackState}. + * @param isLoading See {@link #isLoading}. + * @param trackGroups See {@link #trackGroups}. + * @param trackSelectorResult See {@link #trackSelectorResult}. + * @param loadingMediaPeriodId See {@link #loadingMediaPeriodId}. + * @param bufferedPositionUs See {@link #bufferedPositionUs}. + * @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}. + * @param positionUs See {@link #positionUs}. + */ public PlaybackInfo( Timeline timeline, @Nullable Object manifest, @@ -132,7 +151,17 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; this.positionUs = positionUs; } - public PlaybackInfo fromNewPosition( + /** + * Copies playback info and resets playing and loading position. + * + * @param periodId New playing and loading {@link MediaPeriodId}. + * @param startPositionUs New start position. See {@link #startPositionUs}. + * @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored + * if {@code periodId.isAd()} is true. + * @return Copied playback info with reset position. + */ + @CheckResult + public PlaybackInfo resetToNewPosition( MediaPeriodId periodId, long startPositionUs, long contentPositionUs) { return new PlaybackInfo( timeline, @@ -150,6 +179,46 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; startPositionUs); } + /** + * Copied playback info with new playing position. + * + * @param periodId New playing media period. See {@link #periodId}. + * @param positionUs New position. See {@link #positionUs}. + * @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored + * if {@code periodId.isAd()} is true. + * @param totalBufferedDurationUs New buffered duration. See {@link #totalBufferedDurationUs}. + * @return Copied playback info with new playing position. + */ + @CheckResult + public PlaybackInfo copyWithNewPosition( + MediaPeriodId periodId, + long positionUs, + long contentPositionUs, + long totalBufferedDurationUs) { + return new PlaybackInfo( + timeline, + manifest, + periodId, + positionUs, + periodId.isAd() ? contentPositionUs : C.TIME_UNSET, + playbackState, + isLoading, + trackGroups, + trackSelectorResult, + loadingMediaPeriodId, + bufferedPositionUs, + totalBufferedDurationUs, + positionUs); + } + + /** + * Copies playback info with new timeline and manifest. + * + * @param timeline New timeline. See {@link #timeline}. + * @param manifest New manifest. See {@link #manifest}. + * @return Copied playback info with new timeline and manifest. + */ + @CheckResult public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) { return new PlaybackInfo( timeline, @@ -167,6 +236,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new playback state. + * + * @param playbackState New playback state. See {@link #playbackState}. + * @return Copied playback info with new playback state. + */ + @CheckResult public PlaybackInfo copyWithPlaybackState(int playbackState) { return new PlaybackInfo( timeline, @@ -184,6 +260,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new loading state. + * + * @param isLoading New loading state. See {@link #isLoading}. + * @return Copied playback info with new loading state. + */ + @CheckResult public PlaybackInfo copyWithIsLoading(boolean isLoading) { return new PlaybackInfo( timeline, @@ -201,6 +284,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new track information. + * + * @param trackGroups New track groups. See {@link #trackGroups}. + * @param trackSelectorResult New track selector result. See {@link #trackSelectorResult}. + * @return Copied playback info with new track information. + */ + @CheckResult public PlaybackInfo copyWithTrackInfo( TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { return new PlaybackInfo( @@ -219,6 +310,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; positionUs); } + /** + * Copies playback info with new loading media period. + * + * @param loadingMediaPeriodId New loading media period id. See {@link #loadingMediaPeriodId}. + * @return Copied playback info with new loading media period. + */ + @CheckResult public PlaybackInfo copyWithLoadingMediaPeriodId(MediaPeriodId loadingMediaPeriodId) { return new PlaybackInfo( timeline, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 407e9a3827..d131ed0f51 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -2534,6 +2534,59 @@ public final class ExoPlayerTest { assertThat(positionWhenReady.get()).isEqualTo(periodDurationMs + 10); } + @Test + public void periodTransitionReportsCorrectBufferedPosition() throws Exception { + int periodCount = 3; + long periodDurationUs = 5 * C.MICROS_PER_SECOND; + long windowDurationUs = periodCount * periodDurationUs; + Timeline timeline = + new FakeTimeline( + new TimelineWindowDefinition( + periodCount, + /* id= */ new Object(), + /* isSeekable= */ true, + /* isDynamic= */ false, + windowDurationUs)); + AtomicReference playerReference = new AtomicReference<>(); + AtomicLong bufferedPositionAtFirstDiscontinuityMs = new AtomicLong(C.TIME_UNSET); + EventListener eventListener = + new EventListener() { + @Override + public void onPositionDiscontinuity(@DiscontinuityReason int reason) { + if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) { + if (bufferedPositionAtFirstDiscontinuityMs.get() == C.TIME_UNSET) { + bufferedPositionAtFirstDiscontinuityMs.set( + playerReference.get().getBufferedPosition()); + } + } + } + }; + ActionSchedule actionSchedule = + new ActionSchedule.Builder("periodTransitionReportsCorrectBufferedPosition") + .executeRunnable( + new PlayerRunnable() { + @Override + public void run(SimpleExoPlayer player) { + playerReference.set(player); + player.addListener(eventListener); + } + }) + .pause() + // Wait until all periods are fully buffered. + .waitForIsLoading(/* targetIsLoading= */ true) + .waitForIsLoading(/* targetIsLoading= */ false) + .play() + .build(); + new Builder() + .setTimeline(timeline) + .setActionSchedule(actionSchedule) + .build(context) + .start() + .blockUntilEnded(TIMEOUT_MS); + + assertThat(bufferedPositionAtFirstDiscontinuityMs.get()).isEqualTo(C.usToMs(windowDurationUs)); + } + // Internal methods. private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) { From 4d8b6803afde3e49dcbba147bf3a7fe8033d81a0 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 4 Oct 2018 10:40:29 -0700 Subject: [PATCH 013/210] Minor fixes for period clipping - Always clip to period duration for the last chunk. We previously did this only when the last chunk explicitly exceeded the period end time. We now also do it when the chunk claims to end at the period boundary, but still contains samples that exceed it. - If pendingResetPositionUs == chunk.startTimeUs == 0 but the chunk still contains samples with negative timestamps, we now clip them by setting the decode only flag. Previously we only clipped such samples if the first chunk explicitly preceeded the start of the period. Issue: #4899 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215763467 --- .../android/exoplayer2/source/chunk/ChunkSampleStream.java | 4 ++-- .../exoplayer2/source/dash/DefaultDashChunkSource.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index a993c79b4a..9fac69b281 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -308,7 +308,7 @@ public class ChunkSampleStream implements SampleStream, S // chunk even if the sample timestamps are slightly offset from the chunk start times. seekInsideBuffer = primarySampleQueue.setReadPosition(seekToMediaChunk.getFirstSampleIndex(0)); - decodeOnlyUntilPositionUs = Long.MIN_VALUE; + decodeOnlyUntilPositionUs = 0; } else { seekInsideBuffer = primarySampleQueue.advanceTo( @@ -583,7 +583,7 @@ public class ChunkSampleStream implements SampleStream, S if (pendingReset) { boolean resetToMediaChunk = mediaChunk.startTimeUs == pendingResetPositionUs; // Only enable setting of the decode only flag if we're not resetting to a chunk boundary. - decodeOnlyUntilPositionUs = resetToMediaChunk ? Long.MIN_VALUE : pendingResetPositionUs; + decodeOnlyUntilPositionUs = resetToMediaChunk ? 0 : pendingResetPositionUs; pendingResetPositionUs = C.TIME_UNSET; } mediaChunk.init(mediaChunkOutput); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 37c9e313ae..1ea25ecc36 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -544,7 +544,7 @@ public class DefaultDashChunkSource implements DashChunkSource { long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1); long periodDurationUs = representationHolder.periodDurationUs; long clippedEndTimeUs = - periodDurationUs != C.TIME_UNSET && periodDurationUs < endTimeUs + periodDurationUs != C.TIME_UNSET && periodDurationUs <= endTimeUs ? periodDurationUs : C.TIME_UNSET; DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl), From db0f107fb3bdc1127d7f0dcdad76405e763491cd Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 11 Oct 2018 04:27:22 -0700 Subject: [PATCH 014/210] Project start position for preroll ad to content transitions ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216675981 --- RELEASENOTES.md | 3 ++ .../android/exoplayer2/MediaPeriodQueue.java | 41 +++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1a424e502e..a5af7506e4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -13,6 +13,9 @@ * Fix issue where buffered position is not updated correctly when transitioning between periods ([#4899](https://github.com/google/ExoPlayer/issues/4899)). +* IMA extension: + * For preroll to live stream transitions, project forward the loading position + to avoid being behind the live window. ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 2edf7bb8c6..c51c1cc149 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -532,6 +532,11 @@ import com.google.android.exoplayer2.util.Assertions; // until the timeline is updated. Store whether the next timeline period is ready when the // timeline is updated, to avoid repeatedly checking the same timeline. MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info; + // The expected delay until playback transitions to the new period is equal the duration of + // media that's currently buffered (assuming no interruptions). This is used to project forward + // the start position for transitions to new windows. + long bufferedDurationUs = + mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs; if (mediaPeriodInfo.isLastInTimelinePeriod) { int currentPeriodIndex = timeline.getIndexOfPeriod(mediaPeriodInfo.id.periodUid); int nextPeriodIndex = @@ -549,19 +554,15 @@ import com.google.android.exoplayer2.util.Assertions; long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber; if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) { // We're starting to buffer a new window. When playback transitions to this window we'll - // want it to be from its default start position. The expected delay until playback - // transitions is equal the duration of media that's currently buffered (assuming no - // interruptions). Hence we project the default start position forward by the duration of - // the buffer, and start buffering from this point. - long defaultPositionProjectionUs = - mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs; + // want it to be from its default start position, so project the default start position + // forward by the duration of the buffer, and start buffering from this point. Pair defaultPosition = timeline.getPeriodPosition( window, period, nextWindowIndex, - C.TIME_UNSET, - Math.max(0, defaultPositionProjectionUs)); + /* windowPositionUs= */ C.TIME_UNSET, + /* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs)); if (defaultPosition == null) { return null; } @@ -601,11 +602,27 @@ import com.google.android.exoplayer2.util.Assertions; mediaPeriodInfo.contentPositionUs, currentPeriodId.windowSequenceNumber); } else { - // Play content from the ad group position. + // Play content from the ad group position. As a special case, if we're transitioning from a + // preroll ad group to content and there are no other ad groups, project the start position + // forward as if this were a transition to a new window. No attempt is made to handle + // midrolls in live streams, as it's unclear what content position should play after an ad + // (server-side dynamic ad insertion is more appropriate for this use case). + long startPositionUs = mediaPeriodInfo.contentPositionUs; + if (period.getAdGroupCount() == 1 && period.getAdGroupTimeUs(0) == 0) { + Pair defaultPosition = + timeline.getPeriodPosition( + window, + period, + period.windowIndex, + /* windowPositionUs= */ C.TIME_UNSET, + /* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs)); + if (defaultPosition == null) { + return null; + } + startPositionUs = defaultPosition.second; + } return getMediaPeriodInfoForContent( - currentPeriodId.periodUid, - mediaPeriodInfo.contentPositionUs, - currentPeriodId.windowSequenceNumber); + currentPeriodId.periodUid, startPositionUs, currentPeriodId.windowSequenceNumber); } } else if (mediaPeriodInfo.id.endPositionUs != C.TIME_END_OF_SOURCE) { // Play the next ad group if it's available. From 362a21e17b3515eb210639b14ab824ac30ff7c65 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 11 Oct 2018 09:00:00 -0700 Subject: [PATCH 015/210] Handle rotation signaled in MKV track name from HTC devices ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=216704331 --- .../extractor/mkv/MatroskaExtractor.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 63fee48771..86b750e821 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -157,6 +157,7 @@ public final class MatroskaExtractor implements Extractor { private static final int ID_FLAG_DEFAULT = 0x88; private static final int ID_FLAG_FORCED = 0x55AA; private static final int ID_DEFAULT_DURATION = 0x23E383; + private static final int ID_NAME = 0x536E; private static final int ID_CODEC_ID = 0x86; private static final int ID_CODEC_PRIVATE = 0x63A2; private static final int ID_CODEC_DELAY = 0x56AA; @@ -815,6 +816,9 @@ public final class MatroskaExtractor implements Extractor { throw new ParserException("DocType " + value + " not supported"); } break; + case ID_NAME: + currentTrack.name = value; + break; case ID_CODEC_ID: currentTrack.codecId = value; break; @@ -1463,6 +1467,7 @@ public final class MatroskaExtractor implements Extractor { case ID_MAX_FALL: return TYPE_UNSIGNED_INT; case ID_DOC_TYPE: + case ID_NAME: case ID_CODEC_ID: case ID_LANGUAGE: return TYPE_STRING; @@ -1609,6 +1614,7 @@ public final class MatroskaExtractor implements Extractor { private static final int DEFAULT_MAX_FALL = 200; // nits. // Common elements. + public String name; public String codecId; public int number; public int type; @@ -1833,10 +1839,34 @@ public final class MatroskaExtractor implements Extractor { byte[] hdrStaticInfo = getHdrStaticInfo(); colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo); } - format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, - Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, - Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, - drmInitData); + int rotationDegrees = Format.NO_VALUE; + // Some HTC devices signal rotation in track names. + if ("htc_video_rotA-000".equals(name)) { + rotationDegrees = 0; + } else if ("htc_video_rotA-090".equals(name)) { + rotationDegrees = 90; + } else if ("htc_video_rotA-180".equals(name)) { + rotationDegrees = 180; + } else if ("htc_video_rotA-270".equals(name)) { + rotationDegrees = 270; + } + format = + Format.createVideoSampleFormat( + Integer.toString(trackId), + mimeType, + /* codecs= */ null, + /* bitrate= */ Format.NO_VALUE, + maxInputSize, + width, + height, + /* frameRate= */ Format.NO_VALUE, + initializationData, + rotationDegrees, + pixelWidthHeightRatio, + projectionData, + stereoMode, + colorInfo, + drmInitData); } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { type = C.TRACK_TYPE_TEXT; format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, selectionFlags, From 8bf8b447994095a0465331a94bf8c0867ef5ad29 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Oct 2018 01:03:23 -0700 Subject: [PATCH 016/210] Fix DashManifestParser to properly skip unknown tags Robustness fix to make sure we ignore tags with known names, but which are nested inside of unknown tags. For example we don't want to parse the third period in: ... ... ... ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217101630 --- .../dash/manifest/DashManifestParser.java | 75 +++++++++++++++---- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 153856af8c..31ff7d283e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -155,6 +155,8 @@ public class DashManifestParser extends DefaultHandler : (period.startMs + periodDurationMs); periods.add(period); } + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "MPD")); @@ -221,6 +223,8 @@ public class DashManifestParser extends DefaultHandler segmentBase = parseSegmentList(xpp, null); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { segmentBase = parseSegmentTemplate(xpp, null); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "Period")); @@ -409,22 +413,26 @@ public class DashManifestParser extends DefaultHandler } else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) { String robustnessLevel = xpp.getAttributeValue(null, "robustness_level"); requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW"); - } else if (data == null) { - if (XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh") - && xpp.next() == XmlPullParser.TEXT) { - // The cenc:pssh element is defined in 23001-7:2015. - data = Base64.decode(xpp.getText(), Base64.DEFAULT); - uuid = PsshAtomUtil.parseUuid(data); - if (uuid == null) { - Log.w(TAG, "Skipping malformed cenc:pssh data"); - data = null; - } - } else if (C.PLAYREADY_UUID.equals(uuid) && XmlPullParserUtil.isStartTag(xpp, "mspr:pro") - && xpp.next() == XmlPullParser.TEXT) { - // The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady. - data = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID, - Base64.decode(xpp.getText(), Base64.DEFAULT)); + } else if (data == null + && XmlPullParserUtil.isStartTagIgnorePrefix(xpp, "pssh") + && xpp.next() == XmlPullParser.TEXT) { + // The cenc:pssh element is defined in 23001-7:2015. + data = Base64.decode(xpp.getText(), Base64.DEFAULT); + uuid = PsshAtomUtil.parseUuid(data); + if (uuid == null) { + Log.w(TAG, "Skipping malformed cenc:pssh data"); + data = null; } + } else if (data == null + && C.PLAYREADY_UUID.equals(uuid) + && XmlPullParserUtil.isStartTag(xpp, "mspr:pro") + && xpp.next() == XmlPullParser.TEXT) { + // The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady. + data = + PsshAtomUtil.buildPsshAtom( + C.PLAYREADY_UUID, Base64.decode(xpp.getText(), Base64.DEFAULT)); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); SchemeData schemeData = @@ -462,7 +470,7 @@ public class DashManifestParser extends DefaultHandler */ protected void parseAdaptationSetChild(XmlPullParser xpp) throws XmlPullParserException, IOException { - // pass + maybeSkipTag(xpp); } // Representation parsing. @@ -526,6 +534,8 @@ public class DashManifestParser extends DefaultHandler inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream")); } else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) { supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty")); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "Representation")); @@ -664,6 +674,8 @@ public class DashManifestParser extends DefaultHandler xpp.next(); if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { initialization = parseInitialization(xpp); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase")); @@ -701,6 +713,8 @@ public class DashManifestParser extends DefaultHandler segments = new ArrayList<>(); } segments.add(parseSegmentUrl(xpp)); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList")); @@ -747,6 +761,8 @@ public class DashManifestParser extends DefaultHandler initialization = parseInitialization(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { timeline = parseSegmentTimeline(xpp); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTemplate")); @@ -794,6 +810,8 @@ public class DashManifestParser extends DefaultHandler EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale, scratchOutputStream); eventMessages.add(event); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "EventStream")); @@ -932,6 +950,8 @@ public class DashManifestParser extends DefaultHandler segmentTimeline.add(buildSegmentTimelineElement(elapsedTime, duration)); elapsedTime += duration; } + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTimeline")); return segmentTimeline; @@ -995,6 +1015,29 @@ public class DashManifestParser extends DefaultHandler // Utility methods. + /** + * If the provided {@link XmlPullParser} is currently positioned at the start of a tag, skips + * forward to the end of that tag. + * + * @param xpp The {@link XmlPullParser}. + * @throws XmlPullParserException If an error occurs parsing the stream. + * @throws IOException If an error occurs reading the stream. + */ + public static void maybeSkipTag(XmlPullParser xpp) throws IOException, XmlPullParserException { + if (!XmlPullParserUtil.isStartTag(xpp)) { + return; + } + int depth = 1; + while (depth != 0) { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp)) { + depth++; + } else if (XmlPullParserUtil.isEndTag(xpp)) { + depth--; + } + } + } + /** * Removes unnecessary {@link SchemeData}s with null {@link SchemeData#data}. */ From 0d169ca45613c261c01f252cd3bf40889e11689d Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Oct 2018 02:49:05 -0700 Subject: [PATCH 017/210] Remove assertion causing failure on some Samsung devices The assertion was so weak it probably wouldn't detect genuine misuse of the DefaultAllocator API, so it seems fine just to remove it. We don't really know what happens when the player is allowed to continue on the affected devices, but hopefully it either "just works" or fails in a more graceful way. Issue: #4532 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217113060 --- .../exoplayer2/upstream/DefaultAllocator.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java index 06ca83dd93..71e2d8d19f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java @@ -117,19 +117,6 @@ public final class DefaultAllocator implements Allocator { Math.max(availableAllocations.length * 2, availableCount + allocations.length)); } for (Allocation allocation : allocations) { - // Weak sanity check that the allocation probably originated from this pool. - if (allocation.data != initialAllocationBlock - && allocation.data.length != individualAllocationSize) { - throw new IllegalArgumentException( - "Unexpected allocation: " - + System.identityHashCode(allocation.data) - + ", " - + System.identityHashCode(initialAllocationBlock) - + ", " - + allocation.data.length - + ", " - + individualAllocationSize); - } availableAllocations[availableCount++] = allocation; } allocatedCount -= allocations.length; From 225230b98429ad027b2c7303c1894b004fbe8486 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 15 Oct 2018 19:44:39 -0700 Subject: [PATCH 018/210] Support seeking based on MLLT metadata Issue: #3241 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217252254 --- RELEASENOTES.md | 3 + .../extractor/GaplessInfoHolder.java | 10 -- .../exoplayer2/extractor/mp3/MlltSeeker.java | 121 ++++++++++++++++++ .../extractor/mp3/Mp3Extractor.java | 41 +++++- .../exoplayer2/metadata/id3/Id3Decoder.java | 33 +++++ .../exoplayer2/metadata/id3/MlltFrame.java | 112 ++++++++++++++++ .../metadata/id3/MlltFrameTest.java | 49 +++++++ 7 files changed, 354 insertions(+), 15 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/MlltFrame.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a5af7506e4..cd8309d902 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -10,6 +10,9 @@ ([#4788](https://github.com/google/ExoPlayer/issues/4788)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). +* Audio: + * Support seeking based on MLLT metadata + ([#3241](https://github.com/google/ExoPlayer/issues/3241)). * Fix issue where buffered position is not updated correctly when transitioning between periods ([#4899](https://github.com/google/ExoPlayer/issues/4899)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java index 0742d96a06..a0effc0df8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.extractor; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.CommentFrame; -import com.google.android.exoplayer2.metadata.id3.Id3Decoder.FramePredicate; import com.google.android.exoplayer2.metadata.id3.InternalFrame; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,15 +27,6 @@ import java.util.regex.Pattern; */ public final class GaplessInfoHolder { - /** - * A {@link FramePredicate} suitable for use when decoding {@link Metadata} that will be passed to - * {@link #setFromMetadata(Metadata)}. Only frames that might contain gapless playback information - * are decoded. - */ - public static final FramePredicate GAPLESS_INFO_ID3_FRAME_PREDICATE = - (majorVersion, id0, id1, id2, id3) -> - id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2); - private static final String GAPLESS_DOMAIN = "com.apple.iTunes"; private static final String GAPLESS_DESCRIPTION = "iTunSMPB"; private static final Pattern GAPLESS_COMMENT_PATTERN = diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java new file mode 100644 index 0000000000..ff607b9482 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor.mp3; + +import android.util.Pair; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.extractor.SeekPoint; +import com.google.android.exoplayer2.metadata.id3.MlltFrame; +import com.google.android.exoplayer2.util.Util; + +/** MP3 seeker that uses metadata from an {@link MlltFrame}. */ +/* package */ final class MlltSeeker implements Mp3Extractor.Seeker { + + /** + * Returns an {@link MlltSeeker} for seeking in the stream. + * + * @param firstFramePosition The position of the start of the first frame in the stream. + * @param mlltFrame The MLLT frame with seeking metadata. + * @return An {@link MlltSeeker} for seeking in the stream. + */ + public static MlltSeeker create(long firstFramePosition, MlltFrame mlltFrame) { + int referenceCount = mlltFrame.bytesDeviations.length; + long[] referencePositions = new long[1 + referenceCount]; + long[] referenceTimesMs = new long[1 + referenceCount]; + referencePositions[0] = firstFramePosition; + referenceTimesMs[0] = 0; + long position = firstFramePosition; + long timeMs = 0; + for (int i = 1; i <= referenceCount; i++) { + position += mlltFrame.bytesBetweenReference + mlltFrame.bytesDeviations[i - 1]; + timeMs += mlltFrame.millisecondsBetweenReference + mlltFrame.millisecondsDeviations[i - 1]; + referencePositions[i] = position; + referenceTimesMs[i] = timeMs; + } + return new MlltSeeker(referencePositions, referenceTimesMs); + } + + private final long[] referencePositions; + private final long[] referenceTimesMs; + private final long durationUs; + + private MlltSeeker(long[] referencePositions, long[] referenceTimesMs) { + this.referencePositions = referencePositions; + this.referenceTimesMs = referenceTimesMs; + // Use the last reference point as the duration, as extrapolating variable bitrate at the end of + // the stream may give a large error. + durationUs = C.msToUs(referenceTimesMs[referenceTimesMs.length - 1]); + } + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public SeekPoints getSeekPoints(long timeUs) { + timeUs = Util.constrainValue(timeUs, 0, durationUs); + Pair timeMsAndPosition = + linearlyInterpolate(C.usToMs(timeUs), referenceTimesMs, referencePositions); + timeUs = C.msToUs(timeMsAndPosition.first); + long position = timeMsAndPosition.second; + return new SeekPoints(new SeekPoint(timeUs, position)); + } + + @Override + public long getTimeUs(long position) { + Pair positionAndTimeMs = + linearlyInterpolate(position, referencePositions, referenceTimesMs); + return C.msToUs(positionAndTimeMs.second); + } + + @Override + public long getDurationUs() { + return durationUs; + } + + /** + * Given a set of reference points as coordinates in {@code xReferences} and {@code yReferences} + * and an x-axis value, linearly interpolates between corresponding reference points to give a + * y-axis value. + * + * @param x The x-axis value for which a y-axis value is needed. + * @param xReferences x coordinates of reference points. + * @param yReferences y coordinates of reference points. + * @return The linearly interpolated y-axis value. + */ + private static Pair linearlyInterpolate( + long x, long[] xReferences, long[] yReferences) { + int previousReferenceIndex = + Util.binarySearchFloor(xReferences, x, /* inclusive= */ true, /* stayInBounds= */ true); + long xPreviousReference = xReferences[previousReferenceIndex]; + long yPreviousReference = yReferences[previousReferenceIndex]; + int nextReferenceIndex = previousReferenceIndex + 1; + if (nextReferenceIndex == xReferences.length) { + return Pair.create(xPreviousReference, yPreviousReference); + } else { + long xNextReference = xReferences[nextReferenceIndex]; + long yNextReference = yReferences[nextReferenceIndex]; + double proportion = + xNextReference == xPreviousReference + ? 0.0 + : ((double) x - xPreviousReference) / (xNextReference - xPreviousReference); + long y = (long) (proportion * (yNextReference - yPreviousReference)) + yPreviousReference; + return Pair.create(x, y); + } + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 92cf590a49..84f620734b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.extractor.mp3; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; @@ -31,6 +32,8 @@ import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.Id3Decoder; +import com.google.android.exoplayer2.metadata.id3.Id3Decoder.FramePredicate; +import com.google.android.exoplayer2.metadata.id3.MlltFrame; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.EOFException; @@ -68,6 +71,12 @@ public final class Mp3Extractor implements Extractor { */ public static final int FLAG_DISABLE_ID3_METADATA = 2; + /** Predicate that matches ID3 frames containing only required gapless/seeking metadata. */ + private static final FramePredicate REQUIRED_ID3_FRAME_PREDICATE = + (majorVersion, id0, id1, id2, id3) -> + ((id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2)) + || (id0 == 'M' && id1 == 'L' && id2 == 'L' && (id3 == 'T' || majorVersion == 2))); + /** * The maximum number of bytes to search when synchronizing, before giving up. */ @@ -174,7 +183,15 @@ public final class Mp3Extractor implements Extractor { } } if (seeker == null) { - seeker = maybeReadSeekFrame(input); + // Read past any seek frame and set the seeker based on metadata or a seek frame. Metadata + // takes priority as it can provide greater precision. + Seeker seekFrameSeeker = maybeReadSeekFrame(input); + Seeker metadataSeeker = maybeHandleSeekMetadata(metadata, input.getPosition()); + if (metadataSeeker != null) { + seeker = metadataSeeker; + } else if (seekFrameSeeker != null) { + seeker = seekFrameSeeker; + } if (seeker == null || (!seeker.isSeekable() && (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0)) { seeker = getConstantBitrateSeeker(input); @@ -253,11 +270,11 @@ public final class Mp3Extractor implements Extractor { int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES; input.resetPeekPosition(); if (input.getPosition() == 0) { - // We need to parse enough ID3 metadata to retrieve any gapless playback information even - // if ID3 metadata parsing is disabled. - boolean onlyDecodeGaplessInfoFrames = (flags & FLAG_DISABLE_ID3_METADATA) != 0; + // We need to parse enough ID3 metadata to retrieve any gapless/seeking playback information + // even if ID3 metadata parsing is disabled. + boolean parseAllId3Frames = (flags & FLAG_DISABLE_ID3_METADATA) == 0; Id3Decoder.FramePredicate id3FramePredicate = - onlyDecodeGaplessInfoFrames ? GaplessInfoHolder.GAPLESS_INFO_ID3_FRAME_PREDICATE : null; + parseAllId3Frames ? null : REQUIRED_ID3_FRAME_PREDICATE; metadata = id3Peeker.peekId3Data(input, id3FramePredicate); if (metadata != null) { gaplessInfoHolder.setFromMetadata(metadata); @@ -401,6 +418,20 @@ public final class Mp3Extractor implements Extractor { return SEEK_HEADER_UNSET; } + @Nullable + private static MlltSeeker maybeHandleSeekMetadata(Metadata metadata, long firstFramePosition) { + if (metadata != null) { + int length = metadata.length(); + for (int i = 0; i < length; i++) { + Metadata.Entry entry = metadata.get(i); + if (entry instanceof MlltFrame) { + return MlltSeeker.create(firstFramePosition, (MlltFrame) entry); + } + } + } + return null; + } + /** * {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be * used to work out the new sample basis timestamp after seeking and resynchronization. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 0bf9d3b249..63bf30dd11 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataDecoder; import com.google.android.exoplayer2.metadata.MetadataInputBuffer; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.UnsupportedEncodingException; @@ -382,6 +383,8 @@ public final class Id3Decoder implements MetadataDecoder { } else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') { frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, frameHeaderSize, framePredicate); + } else if (frameId0 == 'M' && frameId1 == 'L' && frameId2 == 'L' && frameId3 == 'T') { + frame = decodeMlltFrame(id3Data, frameSize); } else { String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); frame = decodeBinaryFrame(id3Data, frameSize, id); @@ -662,6 +665,36 @@ public final class Id3Decoder implements MetadataDecoder { return new ChapterTocFrame(elementId, isRoot, isOrdered, children, subFrameArray); } + private static MlltFrame decodeMlltFrame(ParsableByteArray id3Data, int frameSize) { + // See ID3v2.4.0 native frames subsection 4.6. + int mpegFramesBetweenReference = id3Data.readUnsignedShort(); + int bytesBetweenReference = id3Data.readUnsignedInt24(); + int millisecondsBetweenReference = id3Data.readUnsignedInt24(); + int bitsForBytesDeviation = id3Data.readUnsignedByte(); + int bitsForMillisecondsDeviation = id3Data.readUnsignedByte(); + + ParsableBitArray references = new ParsableBitArray(); + references.reset(id3Data); + int referencesBits = 8 * (frameSize - 10); + int bitsPerReference = bitsForBytesDeviation + bitsForMillisecondsDeviation; + int referencesCount = referencesBits / bitsPerReference; + int[] bytesDeviations = new int[referencesCount]; + int[] millisecondsDeviations = new int[referencesCount]; + for (int i = 0; i < referencesCount; i++) { + int bytesDeviation = references.readBits(bitsForBytesDeviation); + int millisecondsDeviation = references.readBits(bitsForMillisecondsDeviation); + bytesDeviations[i] = bytesDeviation; + millisecondsDeviations[i] = millisecondsDeviation; + } + + return new MlltFrame( + mpegFramesBetweenReference, + bytesBetweenReference, + millisecondsBetweenReference, + bytesDeviations, + millisecondsDeviations); + } + private static BinaryFrame decodeBinaryFrame(ParsableByteArray id3Data, int frameSize, String id) { byte[] frame = new byte[frameSize]; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/MlltFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/MlltFrame.java new file mode 100644 index 0000000000..06a4dd9d2d --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/MlltFrame.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.support.annotation.Nullable; +import java.util.Arrays; + +/** MPEG location lookup table frame. */ +public final class MlltFrame extends Id3Frame { + + public static final String ID = "MLLT"; + + public final int mpegFramesBetweenReference; + public final int bytesBetweenReference; + public final int millisecondsBetweenReference; + public final int[] bytesDeviations; + public final int[] millisecondsDeviations; + + public MlltFrame( + int mpegFramesBetweenReference, + int bytesBetweenReference, + int millisecondsBetweenReference, + int[] bytesDeviations, + int[] millisecondsDeviations) { + super(ID); + this.mpegFramesBetweenReference = mpegFramesBetweenReference; + this.bytesBetweenReference = bytesBetweenReference; + this.millisecondsBetweenReference = millisecondsBetweenReference; + this.bytesDeviations = bytesDeviations; + this.millisecondsDeviations = millisecondsDeviations; + } + + /* package */ MlltFrame(Parcel in) { + super(ID); + this.mpegFramesBetweenReference = in.readInt(); + this.bytesBetweenReference = in.readInt(); + this.millisecondsBetweenReference = in.readInt(); + this.bytesDeviations = in.createIntArray(); + this.millisecondsDeviations = in.createIntArray(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + MlltFrame other = (MlltFrame) obj; + return mpegFramesBetweenReference == other.mpegFramesBetweenReference + && bytesBetweenReference == other.bytesBetweenReference + && millisecondsBetweenReference == other.millisecondsBetweenReference + && Arrays.equals(bytesDeviations, other.bytesDeviations) + && Arrays.equals(millisecondsDeviations, other.millisecondsDeviations); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + mpegFramesBetweenReference; + result = 31 * result + bytesBetweenReference; + result = 31 * result + millisecondsBetweenReference; + result = 31 * result + Arrays.hashCode(bytesDeviations); + result = 31 * result + Arrays.hashCode(millisecondsDeviations); + return result; + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mpegFramesBetweenReference); + dest.writeInt(bytesBetweenReference); + dest.writeInt(millisecondsBetweenReference); + dest.writeIntArray(bytesDeviations); + dest.writeIntArray(millisecondsDeviations); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public MlltFrame createFromParcel(Parcel in) { + return new MlltFrame(in); + } + + @Override + public MlltFrame[] newArray(int size) { + return new MlltFrame[size]; + } + }; +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java new file mode 100644 index 0000000000..3e6520beca --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/id3/MlltFrameTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.metadata.id3; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Test for {@link MlltFrame}. */ +@RunWith(RobolectricTestRunner.class) +public final class MlltFrameTest { + + @Test + public void testParcelable() { + MlltFrame mlltFrameToParcel = + new MlltFrame( + /* mpegFramesBetweenReference= */ 1, + /* bytesBetweenReference= */ 1, + /* millisecondsBetweenReference= */ 1, + /* bytesDeviations= */ new int[] {1, 2}, + /* millisecondsDeviations= */ new int[] {1, 2}); + + Parcel parcel = Parcel.obtain(); + mlltFrameToParcel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + MlltFrame mlltFrameFromParcel = MlltFrame.CREATOR.createFromParcel(parcel); + assertThat(mlltFrameFromParcel).isEqualTo(mlltFrameToParcel); + + parcel.recycle(); + } + +} From e346707199d7034d4fd57285734bcbce570435f3 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 18 Oct 2018 04:01:53 -0700 Subject: [PATCH 019/210] Code shrinking doesn't like Class.super.defaultMethodName Just not doing it seems simplier and more obviously correct than suppressing the warnings in our proguard file. Issue: #4890 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217675527 --- .../android/exoplayer2/upstream/DefaultDataSource.java | 5 ++--- .../android/exoplayer2/upstream/cache/CacheDataSource.java | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java index acb2c59e0c..6504562c58 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -261,9 +262,7 @@ public final class DefaultDataSource implements DataSource { @Override public Map> getResponseHeaders() { - return dataSource == null - ? DataSource.super.getResponseHeaders() - : dataSource.getResponseHeaders(); + return dataSource == null ? Collections.emptyMap() : dataSource.getResponseHeaders(); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index fa2068b99d..3a96544c54 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -34,6 +34,7 @@ import java.io.InterruptedIOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -361,7 +362,7 @@ public final class CacheDataSource implements DataSource { // TODO: Implement. return isReadingFromUpstream() ? upstreamDataSource.getResponseHeaders() - : DataSource.super.getResponseHeaders(); + : Collections.emptyMap(); } @Override From 41129280cf46a1279134ead55e5a142a602cb6d9 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 18 Oct 2018 08:54:19 -0700 Subject: [PATCH 020/210] Update the cast framework gradle dependency in the Cast extension Issue:#4960 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217707957 --- extensions/cast/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/cast/build.gradle b/extensions/cast/build.gradle index bee73cac12..30fe10085f 100644 --- a/extensions/cast/build.gradle +++ b/extensions/cast/build.gradle @@ -31,7 +31,7 @@ android { } dependencies { - api 'com.google.android.gms:play-services-cast-framework:16.0.1' + api 'com.google.android.gms:play-services-cast-framework:16.0.3' implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-ui') testImplementation project(modulePrefix + 'testutils') From 40b91090fc05e9b9d2bfe548a62946623bf78b77 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Sat, 20 Oct 2018 14:34:21 +0100 Subject: [PATCH 021/210] Update release notes --- RELEASENOTES.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cd8309d902..65c9b6fb3a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,23 +2,25 @@ ### 2.9.1 ### -* Fix an issue with blind seeking to windows with non-zero offset in a - `ConcatenatingMediaSource` - ([#4873](https://github.com/google/ExoPlayer/issues/4873)). -* Fix issue where subtitles have a wrong position if SubtitleView has a non-zero - offset to its parent - ([#4788](https://github.com/google/ExoPlayer/issues/4788)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). -* Audio: - * Support seeking based on MLLT metadata - ([#3241](https://github.com/google/ExoPlayer/issues/3241)). -* Fix issue where buffered position is not updated correctly when transitioning - between periods +* MP3: Support seeking based on MLLT metadata + ([#3241](https://github.com/google/ExoPlayer/issues/3241)). +* IMA extension: For preroll to live stream transitions, project forward the + loading position to avoid being behind the live window. +* Fix issue with blind seeking to windows with non-zero offset in a + `ConcatenatingMediaSource` + ([#4873](https://github.com/google/ExoPlayer/issues/4873)). +* Fix issue where subtitles were positioned incorrectly if `SubtitleView` had a + non-zero position offset to its parent + ([#4788](https://github.com/google/ExoPlayer/issues/4788)). +* Fix issue where the buffered position was not updated correctly when + transitioning between periods ([#4899](https://github.com/google/ExoPlayer/issues/4899)). -* IMA extension: - * For preroll to live stream transitions, project forward the loading position - to avoid being behind the live window. +* Suppress a spurious assertion failure on some Samsung devices + ([#4532](https://github.com/google/ExoPlayer/issues/4532)). +* Suppress spurious "references unknown class member" shrinking warning + ([#4890](https://github.com/google/ExoPlayer/issues/4890)). ### 2.9.0 ### From 6ae015ecbd134c87b3ad47a8f71ffc04f174e9c9 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Oct 2018 09:41:59 -0700 Subject: [PATCH 022/210] Replace DefaultBandwidthMeter with CountryAndNetworkTypeBandwidthMeter. This removes the experimental bandwidth meter and uses it as the new default. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215404065 --- RELEASENOTES.md | 2 + .../upstream/DefaultBandwidthMeter.java | 376 ++++++++++++- .../upstream/DefaultBandwidthMeterTest.java | 531 ++++++++++++++++++ 3 files changed, 895 insertions(+), 14 deletions(-) create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 65c9b6fb3a..cd8caa0c8a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### 2.9.1 ### +* Improve initial bandwidth meter estimates using the current country and + network type. * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). * MP3: Support seeking based on MLLT metadata diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index 6e0fba27ae..e9f70ec92a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -15,20 +15,57 @@ */ package com.google.android.exoplayer2.upstream; +import android.content.Context; import android.os.Handler; import android.support.annotation.Nullable; +import android.util.SparseArray; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.EventDispatcher; import com.google.android.exoplayer2.util.SlidingPercentile; +import com.google.android.exoplayer2.util.Util; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; /** - * Estimates bandwidth by listening to data transfers. The bandwidth estimate is calculated using a - * {@link SlidingPercentile} and is updated each time a transfer ends. + * Estimates bandwidth by listening to data transfers. + * + *

The bandwidth estimate is calculated using a {@link SlidingPercentile} and is updated each + * time a transfer ends. The initial estimate is based on the current operator's network country + * code or the locale of the user, as well as the network connection type. This can be configured in + * the {@link Builder}. */ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferListener { - /** Default initial bitrate estimate in bits per second. */ + /** + * Country groups used to determine the default initial bitrate estimate. The group assignment for + * each country is an array of group indices for [Wifi, 2G, 3G, 4G]. + */ + public static final Map DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS = + createInitialBitrateCountryGroupAssignment(); + + /** Default initial Wifi bitrate estimate in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI = + new long[] {5_700_000, 3_400_000, 1_900_000, 1_000_000, 400_000}; + + /** Default initial 2G bitrate estimates in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_2G = + new long[] {169_000, 129_000, 114_000, 102_000, 87_000}; + + /** Default initial 3G bitrate estimates in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_3G = + new long[] {2_100_000, 1_300_000, 950_000, 700_000, 400_000}; + + /** Default initial 4G bitrate estimates in bits per second. */ + public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_4G = + new long[] {6_900_000, 4_300_000, 2_700_000, 1_600_000, 450_000}; + + /** + * Default initial bitrate estimate used when the device is offline or the network type cannot be + * determined, in bits per second. + */ public static final long DEFAULT_INITIAL_BITRATE_ESTIMATE = 1_000_000; /** Default maximum weight for the sliding window. */ @@ -37,15 +74,28 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList /** Builder for a bandwidth meter. */ public static final class Builder { - private @Nullable Handler eventHandler; - private @Nullable EventListener eventListener; - private long initialBitrateEstimate; + @Nullable private final Context context; + + @Nullable private Handler eventHandler; + @Nullable private EventListener eventListener; + private SparseArray initialBitrateEstimates; private int slidingWindowMaxWeight; private Clock clock; - /** Creates a builder with default parameters and without listener. */ + /** @deprecated Use {@link #Builder(Context)} instead. */ + @Deprecated public Builder() { - initialBitrateEstimate = DEFAULT_INITIAL_BITRATE_ESTIMATE; + this(/* context= */ null); + } + + /** + * Creates a builder with default parameters and without listener. + * + * @param context A context. + */ + public Builder(@Nullable Context context) { + this.context = context == null ? null : context.getApplicationContext(); + initialBitrateEstimates = getInitialBitrateEstimatesForCountry(Util.getCountryCode(context)); slidingWindowMaxWeight = DEFAULT_SLIDING_WINDOW_MAX_WEIGHT; clock = Clock.DEFAULT; } @@ -84,7 +134,38 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList * @return This builder. */ public Builder setInitialBitrateEstimate(long initialBitrateEstimate) { - this.initialBitrateEstimate = initialBitrateEstimate; + for (int i = 0; i < initialBitrateEstimates.size(); i++) { + initialBitrateEstimates.setValueAt(i, initialBitrateEstimate); + } + return this; + } + + /** + * Sets the initial bitrate estimate in bits per second for a network type that should be + * assumed when a bandwidth estimate is unavailable and the current network connection is of the + * specified type. + * + * @param networkType The {@link C.NetworkType} this initial estimate is for. + * @param initialBitrateEstimate The initial bitrate estimate in bits per second. + * @return This builder. + */ + public Builder setInitialBitrateEstimate( + @C.NetworkType int networkType, long initialBitrateEstimate) { + initialBitrateEstimates.put(networkType, initialBitrateEstimate); + return this; + } + + /** + * Sets the initial bitrate estimates to the default values of the specified country. The + * initial estimates are used when a bandwidth estimate is unavailable. + * + * @param countryCode The ISO 3166-1 alpha-2 country code of the country whose default bitrate + * estimates should be used. + * @return This builder. + */ + public Builder setInitialBitrateEstimate(String countryCode) { + initialBitrateEstimates = + getInitialBitrateEstimatesForCountry(Util.toUpperInvariant(countryCode)); return this; } @@ -106,6 +187,10 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList * @return A bandwidth meter with the configured properties. */ public DefaultBandwidthMeter build() { + Long initialBitrateEstimate = initialBitrateEstimates.get(Util.getNetworkType(context)); + if (initialBitrateEstimate == null) { + initialBitrateEstimate = initialBitrateEstimates.get(C.NETWORK_TYPE_UNKNOWN); + } DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(initialBitrateEstimate, slidingWindowMaxWeight, clock); if (eventHandler != null && eventListener != null) { @@ -113,6 +198,26 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } return bandwidthMeter; } + + private static SparseArray getInitialBitrateEstimatesForCountry(String countryCode) { + int[] groupIndices = getCountryGroupIndices(countryCode); + SparseArray result = new SparseArray<>(/* initialCapacity= */ 6); + result.append(C.NETWORK_TYPE_UNKNOWN, DEFAULT_INITIAL_BITRATE_ESTIMATE); + result.append(C.NETWORK_TYPE_WIFI, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); + result.append(C.NETWORK_TYPE_2G, DEFAULT_INITIAL_BITRATE_ESTIMATES_2G[groupIndices[1]]); + result.append(C.NETWORK_TYPE_3G, DEFAULT_INITIAL_BITRATE_ESTIMATES_3G[groupIndices[2]]); + result.append(C.NETWORK_TYPE_4G, DEFAULT_INITIAL_BITRATE_ESTIMATES_4G[groupIndices[3]]); + // Assume default Wifi bitrate for Ethernet to prevent using the slower fallback bitrate. + result.append( + C.NETWORK_TYPE_ETHERNET, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); + return result; + } + + private static int[] getCountryGroupIndices(String countryCode) { + int[] groupIndices = DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS.get(countryCode); + // Assume median group if not found. + return groupIndices == null ? new int[] {2, 2, 2, 2} : groupIndices; + } } private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; @@ -153,10 +258,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } } - private DefaultBandwidthMeter( - long initialBitrateEstimate, - int maxWeight, - Clock clock) { + private DefaultBandwidthMeter(long initialBitrateEstimate, int maxWeight, Clock clock) { this.eventDispatcher = new EventDispatcher<>(); this.slidingPercentile = new SlidingPercentile(maxWeight); this.clock = clock; @@ -169,7 +271,8 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList } @Override - public @Nullable TransferListener getTransferListener() { + @Nullable + public TransferListener getTransferListener() { return this; } @@ -237,4 +340,249 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private void notifyBandwidthSample(int elapsedMs, long bytes, long bitrate) { eventDispatcher.dispatch(listener -> listener.onBandwidthSample(elapsedMs, bytes, bitrate)); } + + private static Map createInitialBitrateCountryGroupAssignment() { + HashMap countryGroupAssignment = new HashMap<>(); + countryGroupAssignment.put("AD", new int[] {1, 0, 0, 0}); + countryGroupAssignment.put("AE", new int[] {1, 3, 4, 4}); + countryGroupAssignment.put("AF", new int[] {4, 4, 3, 2}); + countryGroupAssignment.put("AG", new int[] {3, 2, 1, 2}); + countryGroupAssignment.put("AI", new int[] {1, 0, 0, 2}); + countryGroupAssignment.put("AL", new int[] {1, 1, 1, 1}); + countryGroupAssignment.put("AM", new int[] {2, 2, 4, 3}); + countryGroupAssignment.put("AO", new int[] {2, 4, 2, 0}); + countryGroupAssignment.put("AR", new int[] {2, 3, 2, 3}); + countryGroupAssignment.put("AS", new int[] {3, 4, 4, 1}); + countryGroupAssignment.put("AT", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("AU", new int[] {0, 3, 0, 0}); + countryGroupAssignment.put("AW", new int[] {1, 1, 0, 4}); + countryGroupAssignment.put("AX", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("AZ", new int[] {3, 3, 2, 2}); + countryGroupAssignment.put("BA", new int[] {1, 1, 1, 2}); + countryGroupAssignment.put("BB", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("BD", new int[] {2, 1, 3, 2}); + countryGroupAssignment.put("BE", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("BF", new int[] {4, 4, 4, 1}); + countryGroupAssignment.put("BG", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("BH", new int[] {2, 1, 3, 4}); + countryGroupAssignment.put("BI", new int[] {4, 3, 4, 4}); + countryGroupAssignment.put("BJ", new int[] {4, 3, 4, 3}); + countryGroupAssignment.put("BL", new int[] {1, 0, 1, 2}); + countryGroupAssignment.put("BM", new int[] {1, 0, 0, 0}); + countryGroupAssignment.put("BN", new int[] {4, 3, 3, 3}); + countryGroupAssignment.put("BO", new int[] {2, 2, 1, 2}); + countryGroupAssignment.put("BQ", new int[] {1, 1, 2, 4}); + countryGroupAssignment.put("BR", new int[] {2, 3, 2, 2}); + countryGroupAssignment.put("BS", new int[] {1, 1, 0, 2}); + countryGroupAssignment.put("BT", new int[] {3, 0, 2, 1}); + countryGroupAssignment.put("BW", new int[] {4, 4, 2, 3}); + countryGroupAssignment.put("BY", new int[] {1, 1, 1, 1}); + countryGroupAssignment.put("BZ", new int[] {2, 3, 3, 1}); + countryGroupAssignment.put("CA", new int[] {0, 2, 2, 3}); + countryGroupAssignment.put("CD", new int[] {4, 4, 2, 1}); + countryGroupAssignment.put("CF", new int[] {4, 4, 3, 3}); + countryGroupAssignment.put("CG", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("CH", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("CI", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("CK", new int[] {2, 4, 2, 0}); + countryGroupAssignment.put("CL", new int[] {2, 2, 2, 3}); + countryGroupAssignment.put("CM", new int[] {3, 4, 3, 1}); + countryGroupAssignment.put("CN", new int[] {2, 0, 1, 2}); + countryGroupAssignment.put("CO", new int[] {2, 3, 2, 1}); + countryGroupAssignment.put("CR", new int[] {2, 2, 4, 4}); + countryGroupAssignment.put("CU", new int[] {4, 4, 4, 1}); + countryGroupAssignment.put("CV", new int[] {2, 2, 2, 4}); + countryGroupAssignment.put("CW", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("CX", new int[] {1, 2, 2, 2}); + countryGroupAssignment.put("CY", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("CZ", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("DE", new int[] {0, 2, 2, 2}); + countryGroupAssignment.put("DJ", new int[] {3, 4, 4, 0}); + countryGroupAssignment.put("DK", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("DM", new int[] {2, 0, 3, 4}); + countryGroupAssignment.put("DO", new int[] {3, 3, 4, 4}); + countryGroupAssignment.put("DZ", new int[] {3, 3, 4, 4}); + countryGroupAssignment.put("EC", new int[] {2, 3, 3, 1}); + countryGroupAssignment.put("EE", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("EG", new int[] {3, 3, 1, 1}); + countryGroupAssignment.put("EH", new int[] {2, 0, 2, 3}); + countryGroupAssignment.put("ER", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("ES", new int[] {0, 0, 1, 1}); + countryGroupAssignment.put("ET", new int[] {4, 4, 4, 0}); + countryGroupAssignment.put("FI", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("FJ", new int[] {3, 2, 3, 3}); + countryGroupAssignment.put("FK", new int[] {3, 4, 2, 1}); + countryGroupAssignment.put("FM", new int[] {4, 2, 4, 0}); + countryGroupAssignment.put("FO", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("FR", new int[] {1, 0, 2, 1}); + countryGroupAssignment.put("GA", new int[] {3, 3, 2, 1}); + countryGroupAssignment.put("GB", new int[] {0, 1, 3, 2}); + countryGroupAssignment.put("GD", new int[] {2, 0, 3, 0}); + countryGroupAssignment.put("GE", new int[] {1, 1, 0, 3}); + countryGroupAssignment.put("GF", new int[] {1, 2, 4, 4}); + countryGroupAssignment.put("GG", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("GH", new int[] {3, 2, 2, 2}); + countryGroupAssignment.put("GI", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("GL", new int[] {2, 4, 1, 4}); + countryGroupAssignment.put("GM", new int[] {4, 3, 3, 0}); + countryGroupAssignment.put("GN", new int[] {4, 4, 3, 4}); + countryGroupAssignment.put("GP", new int[] {2, 2, 1, 3}); + countryGroupAssignment.put("GQ", new int[] {4, 4, 3, 1}); + countryGroupAssignment.put("GR", new int[] {1, 1, 0, 1}); + countryGroupAssignment.put("GT", new int[] {3, 2, 3, 4}); + countryGroupAssignment.put("GU", new int[] {1, 0, 4, 4}); + countryGroupAssignment.put("GW", new int[] {4, 4, 4, 0}); + countryGroupAssignment.put("GY", new int[] {3, 4, 1, 0}); + countryGroupAssignment.put("HK", new int[] {0, 2, 3, 4}); + countryGroupAssignment.put("HN", new int[] {3, 3, 2, 2}); + countryGroupAssignment.put("HR", new int[] {1, 0, 0, 2}); + countryGroupAssignment.put("HT", new int[] {3, 3, 3, 3}); + countryGroupAssignment.put("HU", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("ID", new int[] {2, 3, 3, 4}); + countryGroupAssignment.put("IE", new int[] {0, 0, 1, 1}); + countryGroupAssignment.put("IL", new int[] {0, 1, 1, 3}); + countryGroupAssignment.put("IM", new int[] {0, 1, 0, 1}); + countryGroupAssignment.put("IN", new int[] {2, 3, 3, 4}); + countryGroupAssignment.put("IO", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("IQ", new int[] {3, 3, 4, 3}); + countryGroupAssignment.put("IR", new int[] {3, 2, 4, 4}); + countryGroupAssignment.put("IS", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("IT", new int[] {1, 0, 1, 3}); + countryGroupAssignment.put("JE", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("JM", new int[] {3, 3, 3, 2}); + countryGroupAssignment.put("JO", new int[] {1, 1, 1, 2}); + countryGroupAssignment.put("JP", new int[] {0, 1, 1, 2}); + countryGroupAssignment.put("KE", new int[] {3, 3, 3, 3}); + countryGroupAssignment.put("KG", new int[] {2, 2, 3, 3}); + countryGroupAssignment.put("KH", new int[] {1, 0, 4, 4}); + countryGroupAssignment.put("KI", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("KM", new int[] {4, 4, 2, 2}); + countryGroupAssignment.put("KN", new int[] {1, 0, 1, 3}); + countryGroupAssignment.put("KP", new int[] {1, 2, 2, 2}); + countryGroupAssignment.put("KR", new int[] {0, 4, 0, 2}); + countryGroupAssignment.put("KW", new int[] {1, 2, 1, 2}); + countryGroupAssignment.put("KY", new int[] {1, 1, 0, 2}); + countryGroupAssignment.put("KZ", new int[] {1, 2, 2, 3}); + countryGroupAssignment.put("LA", new int[] {3, 2, 2, 2}); + countryGroupAssignment.put("LB", new int[] {3, 2, 0, 0}); + countryGroupAssignment.put("LC", new int[] {2, 2, 1, 0}); + countryGroupAssignment.put("LI", new int[] {0, 0, 1, 2}); + countryGroupAssignment.put("LK", new int[] {1, 1, 2, 2}); + countryGroupAssignment.put("LR", new int[] {3, 4, 3, 1}); + countryGroupAssignment.put("LS", new int[] {3, 3, 2, 0}); + countryGroupAssignment.put("LT", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("LU", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("LV", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("LY", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("MA", new int[] {2, 1, 2, 2}); + countryGroupAssignment.put("MC", new int[] {1, 0, 1, 0}); + countryGroupAssignment.put("MD", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("ME", new int[] {1, 2, 2, 3}); + countryGroupAssignment.put("MF", new int[] {1, 4, 3, 3}); + countryGroupAssignment.put("MG", new int[] {3, 4, 1, 2}); + countryGroupAssignment.put("MH", new int[] {4, 0, 2, 3}); + countryGroupAssignment.put("MK", new int[] {1, 0, 0, 1}); + countryGroupAssignment.put("ML", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("MM", new int[] {2, 3, 1, 2}); + countryGroupAssignment.put("MN", new int[] {2, 2, 2, 4}); + countryGroupAssignment.put("MO", new int[] {0, 1, 4, 4}); + countryGroupAssignment.put("MP", new int[] {0, 0, 4, 4}); + countryGroupAssignment.put("MQ", new int[] {1, 1, 1, 3}); + countryGroupAssignment.put("MR", new int[] {4, 2, 4, 2}); + countryGroupAssignment.put("MS", new int[] {1, 2, 1, 2}); + countryGroupAssignment.put("MT", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("MU", new int[] {2, 2, 4, 4}); + countryGroupAssignment.put("MV", new int[] {4, 2, 0, 1}); + countryGroupAssignment.put("MW", new int[] {3, 2, 1, 1}); + countryGroupAssignment.put("MX", new int[] {2, 4, 3, 1}); + countryGroupAssignment.put("MY", new int[] {2, 3, 3, 3}); + countryGroupAssignment.put("MZ", new int[] {3, 3, 2, 4}); + countryGroupAssignment.put("NA", new int[] {4, 2, 1, 1}); + countryGroupAssignment.put("NC", new int[] {2, 1, 3, 3}); + countryGroupAssignment.put("NE", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("NF", new int[] {0, 2, 2, 2}); + countryGroupAssignment.put("NG", new int[] {3, 4, 2, 2}); + countryGroupAssignment.put("NI", new int[] {3, 4, 3, 3}); + countryGroupAssignment.put("NL", new int[] {0, 1, 3, 2}); + countryGroupAssignment.put("NO", new int[] {0, 0, 1, 0}); + countryGroupAssignment.put("NP", new int[] {2, 3, 2, 2}); + countryGroupAssignment.put("NR", new int[] {4, 3, 4, 1}); + countryGroupAssignment.put("NU", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("NZ", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("OM", new int[] {2, 2, 1, 3}); + countryGroupAssignment.put("PA", new int[] {1, 3, 2, 3}); + countryGroupAssignment.put("PE", new int[] {2, 2, 4, 4}); + countryGroupAssignment.put("PF", new int[] {2, 2, 0, 1}); + countryGroupAssignment.put("PG", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("PH", new int[] {3, 0, 4, 4}); + countryGroupAssignment.put("PK", new int[] {3, 3, 3, 3}); + countryGroupAssignment.put("PL", new int[] {1, 0, 1, 3}); + countryGroupAssignment.put("PM", new int[] {0, 2, 2, 3}); + countryGroupAssignment.put("PR", new int[] {2, 3, 4, 3}); + countryGroupAssignment.put("PS", new int[] {2, 3, 0, 4}); + countryGroupAssignment.put("PT", new int[] {1, 1, 1, 1}); + countryGroupAssignment.put("PW", new int[] {3, 2, 3, 0}); + countryGroupAssignment.put("PY", new int[] {2, 1, 3, 3}); + countryGroupAssignment.put("QA", new int[] {2, 3, 1, 2}); + countryGroupAssignment.put("RE", new int[] {1, 1, 2, 2}); + countryGroupAssignment.put("RO", new int[] {0, 1, 1, 3}); + countryGroupAssignment.put("RS", new int[] {1, 1, 0, 0}); + countryGroupAssignment.put("RU", new int[] {0, 1, 1, 1}); + countryGroupAssignment.put("RW", new int[] {3, 4, 3, 1}); + countryGroupAssignment.put("SA", new int[] {3, 2, 2, 3}); + countryGroupAssignment.put("SB", new int[] {4, 4, 3, 0}); + countryGroupAssignment.put("SC", new int[] {4, 2, 0, 1}); + countryGroupAssignment.put("SD", new int[] {3, 4, 4, 4}); + countryGroupAssignment.put("SE", new int[] {0, 0, 0, 0}); + countryGroupAssignment.put("SG", new int[] {1, 2, 3, 3}); + countryGroupAssignment.put("SH", new int[] {4, 2, 2, 2}); + countryGroupAssignment.put("SI", new int[] {0, 1, 0, 0}); + countryGroupAssignment.put("SJ", new int[] {3, 2, 0, 2}); + countryGroupAssignment.put("SK", new int[] {0, 1, 0, 1}); + countryGroupAssignment.put("SL", new int[] {4, 3, 2, 4}); + countryGroupAssignment.put("SM", new int[] {1, 0, 1, 1}); + countryGroupAssignment.put("SN", new int[] {4, 4, 4, 2}); + countryGroupAssignment.put("SO", new int[] {4, 4, 4, 3}); + countryGroupAssignment.put("SR", new int[] {3, 2, 2, 3}); + countryGroupAssignment.put("SS", new int[] {4, 3, 4, 2}); + countryGroupAssignment.put("ST", new int[] {3, 2, 2, 2}); + countryGroupAssignment.put("SV", new int[] {2, 3, 2, 3}); + countryGroupAssignment.put("SX", new int[] {2, 4, 2, 0}); + countryGroupAssignment.put("SY", new int[] {4, 4, 2, 0}); + countryGroupAssignment.put("SZ", new int[] {3, 4, 1, 1}); + countryGroupAssignment.put("TC", new int[] {2, 1, 2, 1}); + countryGroupAssignment.put("TD", new int[] {4, 4, 4, 3}); + countryGroupAssignment.put("TG", new int[] {3, 2, 2, 0}); + countryGroupAssignment.put("TH", new int[] {1, 3, 4, 4}); + countryGroupAssignment.put("TJ", new int[] {4, 4, 4, 4}); + countryGroupAssignment.put("TL", new int[] {4, 2, 4, 4}); + countryGroupAssignment.put("TM", new int[] {4, 1, 3, 3}); + countryGroupAssignment.put("TN", new int[] {2, 2, 1, 2}); + countryGroupAssignment.put("TO", new int[] {2, 3, 3, 1}); + countryGroupAssignment.put("TR", new int[] {1, 2, 0, 2}); + countryGroupAssignment.put("TT", new int[] {2, 1, 1, 0}); + countryGroupAssignment.put("TV", new int[] {4, 2, 2, 4}); + countryGroupAssignment.put("TW", new int[] {0, 0, 0, 1}); + countryGroupAssignment.put("TZ", new int[] {3, 3, 3, 2}); + countryGroupAssignment.put("UA", new int[] {0, 2, 1, 3}); + countryGroupAssignment.put("UG", new int[] {4, 3, 2, 2}); + countryGroupAssignment.put("US", new int[] {0, 1, 3, 3}); + countryGroupAssignment.put("UY", new int[] {2, 1, 2, 2}); + countryGroupAssignment.put("UZ", new int[] {4, 3, 2, 4}); + countryGroupAssignment.put("VA", new int[] {1, 2, 2, 2}); + countryGroupAssignment.put("VC", new int[] {2, 0, 3, 2}); + countryGroupAssignment.put("VE", new int[] {3, 4, 4, 3}); + countryGroupAssignment.put("VG", new int[] {3, 1, 3, 4}); + countryGroupAssignment.put("VI", new int[] {1, 0, 2, 4}); + countryGroupAssignment.put("VN", new int[] {0, 2, 4, 4}); + countryGroupAssignment.put("VU", new int[] {4, 1, 3, 2}); + countryGroupAssignment.put("WS", new int[] {3, 2, 3, 0}); + countryGroupAssignment.put("XK", new int[] {1, 2, 1, 0}); + countryGroupAssignment.put("YE", new int[] {4, 4, 4, 2}); + countryGroupAssignment.put("YT", new int[] {3, 1, 1, 2}); + countryGroupAssignment.put("ZA", new int[] {2, 3, 1, 2}); + countryGroupAssignment.put("ZM", new int[] {3, 3, 3, 1}); + countryGroupAssignment.put("ZW", new int[] {3, 3, 2, 1}); + return Collections.unmodifiableMap(countryGroupAssignment); + } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java new file mode 100644 index 0000000000..ebdb45909b --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeterTest.java @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.upstream; + +import static android.Manifest.permission.ACCESS_NETWORK_STATE; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.telephony.TelephonyManager; +import com.google.android.exoplayer2.C; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowNetworkInfo; + +/** Unit test for {@link DefaultBandwidthMeter}. */ +@RunWith(RobolectricTestRunner.class) +public final class DefaultBandwidthMeterTest { + + private static final String FAST_COUNTRY_ISO = "EE"; + private static final String SLOW_COUNTRY_ISO = "PG"; + + private TelephonyManager telephonyManager; + private ConnectivityManager connectivityManager; + private NetworkInfo networkInfoOffline; + private NetworkInfo networkInfoWifi; + private NetworkInfo networkInfo2g; + private NetworkInfo networkInfo3g; + private NetworkInfo networkInfo4g; + private NetworkInfo networkInfoEthernet; + + @Before + public void setUp() { + connectivityManager = + (ConnectivityManager) + RuntimeEnvironment.application.getSystemService(Context.CONNECTIVITY_SERVICE); + telephonyManager = + (TelephonyManager) + RuntimeEnvironment.application.getSystemService(Context.TELEPHONY_SERVICE); + Shadows.shadowOf(telephonyManager).setNetworkCountryIso(FAST_COUNTRY_ISO); + networkInfoOffline = + ShadowNetworkInfo.newInstance( + DetailedState.DISCONNECTED, + ConnectivityManager.TYPE_WIFI, + /* subType= */ 0, + /* isAvailable= */ false, + /* isConnected= */ false); + networkInfoWifi = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_WIFI, + /* subType= */ 0, + /* isAvailable= */ true, + /* isConnected= */ true); + networkInfo2g = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_GPRS, + /* isAvailable= */ true, + /* isConnected= */ true); + networkInfo3g = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_HSDPA, + /* isAvailable= */ true, + /* isConnected= */ true); + networkInfo4g = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_MOBILE, + TelephonyManager.NETWORK_TYPE_LTE, + /* isAvailable= */ true, + /* isConnected= */ true); + networkInfoEthernet = + ShadowNetworkInfo.newInstance( + DetailedState.CONNECTED, + ConnectivityManager.TYPE_ETHERNET, + /* subType= */ 0, + /* isAvailable= */ true, + /* isConnected= */ true); + } + + @Test + public void defaultInitialBitrateEstimate_forWifi_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfoWifi); + DefaultBandwidthMeter bandwidthMeterWifi = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateWifi = bandwidthMeterWifi.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + DefaultBandwidthMeter bandwidthMeter2g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimateWifi).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_forWifi_isGreaterThanEstimateFor3G() { + setActiveNetworkInfo(networkInfoWifi); + DefaultBandwidthMeter bandwidthMeterWifi = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateWifi = bandwidthMeterWifi.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo3g); + DefaultBandwidthMeter bandwidthMeter3g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + assertThat(initialEstimateWifi).isGreaterThan(initialEstimate3g); + } + + @Test + public void defaultInitialBitrateEstimate_forEthernet_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfoEthernet); + DefaultBandwidthMeter bandwidthMeterEthernet = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateEthernet = bandwidthMeterEthernet.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + DefaultBandwidthMeter bandwidthMeter2g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimateEthernet).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_forEthernet_isGreaterThanEstimateFor3G() { + setActiveNetworkInfo(networkInfoEthernet); + DefaultBandwidthMeter bandwidthMeterEthernet = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateEthernet = bandwidthMeterEthernet.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo3g); + DefaultBandwidthMeter bandwidthMeter3g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + assertThat(initialEstimateEthernet).isGreaterThan(initialEstimate3g); + } + + @Test + public void defaultInitialBitrateEstimate_for4G_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfo4g); + DefaultBandwidthMeter bandwidthMeter4g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate4g = bandwidthMeter4g.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + DefaultBandwidthMeter bandwidthMeter2g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimate4g).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_for4G_isGreaterThanEstimateFor3G() { + setActiveNetworkInfo(networkInfo4g); + DefaultBandwidthMeter bandwidthMeter4g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate4g = bandwidthMeter4g.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo3g); + DefaultBandwidthMeter bandwidthMeter3g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + assertThat(initialEstimate4g).isGreaterThan(initialEstimate3g); + } + + @Test + public void defaultInitialBitrateEstimate_for3G_isGreaterThanEstimateFor2G() { + setActiveNetworkInfo(networkInfo3g); + DefaultBandwidthMeter bandwidthMeter3g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate3g = bandwidthMeter3g.getBitrateEstimate(); + + setActiveNetworkInfo(networkInfo2g); + DefaultBandwidthMeter bandwidthMeter2g = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate2g = bandwidthMeter2g.getBitrateEstimate(); + + assertThat(initialEstimate3g).isGreaterThan(initialEstimate2g); + } + + @Test + public void defaultInitialBitrateEstimate_forOffline_isReasonable() { + setActiveNetworkInfo(networkInfoOffline); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isGreaterThan(100_000L); + assertThat(initialEstimate).isLessThan(50_000_000L); + } + + @Test + public void + defaultInitialBitrateEstimate_forWifi_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfoWifi); + setNetworkCountryIso(FAST_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_forEthernet_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfoEthernet); + setNetworkCountryIso(FAST_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_for2G_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfo2g); + setNetworkCountryIso(FAST_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_for3G_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfo3g); + setNetworkCountryIso(FAST_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void + defaultInitialBitrateEstimate_for4g_forFastCountry_isGreaterThanEstimateForSlowCountry() { + setActiveNetworkInfo(networkInfo4g); + setNetworkCountryIso(FAST_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterFast = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateFast = bandwidthMeterFast.getBitrateEstimate(); + + setNetworkCountryIso(SLOW_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + assertThat(initialEstimateFast).isGreaterThan(initialEstimateSlow); + } + + @Test + public void initialBitrateEstimateOverwrite_whileConnectedToNetwork_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_whileOffline_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoOffline); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_forWifi_whileConnectedToWifi_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forWifi_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfo2g); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forEthernet_whileConnectedToEthernet_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoEthernet); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_ETHERNET, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forEthernet_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfo2g); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_WIFI, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_for2G_whileConnectedTo2G_setsInitialEstimate() { + setActiveNetworkInfo(networkInfo2g); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_2G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_for2G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_2G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_for3G_whileConnectedTo3G_setsInitialEstimate() { + setActiveNetworkInfo(networkInfo3g); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_3G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_for3G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_3G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_for4G_whileConnectedTo4G_setsInitialEstimate() { + setActiveNetworkInfo(networkInfo4g); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_4G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_for4G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_4G, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_forOffline_whileOffline_setsInitialEstimate() { + setActiveNetworkInfo(networkInfoOffline); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_OFFLINE, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isEqualTo(123456789); + } + + @Test + public void + initialBitrateEstimateOverwrite_forOffline_whileConnectedToNetwork_doesNotSetInitialEstimate() { + setActiveNetworkInfo(networkInfoWifi); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(C.NETWORK_TYPE_OFFLINE, 123456789) + .build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isNotEqualTo(123456789); + } + + @Test + public void initialBitrateEstimateOverwrite_forCountry_usesDefaultValuesForCountry() { + setNetworkCountryIso(SLOW_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterSlow = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimateSlow = bandwidthMeterSlow.getBitrateEstimate(); + + setNetworkCountryIso(FAST_COUNTRY_ISO); + DefaultBandwidthMeter bandwidthMeterFastWithSlowOverwrite = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application) + .setInitialBitrateEstimate(SLOW_COUNTRY_ISO) + .build(); + long initialEstimateFastWithSlowOverwrite = + bandwidthMeterFastWithSlowOverwrite.getBitrateEstimate(); + + assertThat(initialEstimateFastWithSlowOverwrite).isEqualTo(initialEstimateSlow); + } + + @Test + public void defaultInitialBitrateEstimate_withoutContext_isReasonable() { + DefaultBandwidthMeter bandwidthMeterWithBuilder = + new DefaultBandwidthMeter.Builder(/* context= */ null).build(); + long initialEstimateWithBuilder = bandwidthMeterWithBuilder.getBitrateEstimate(); + + DefaultBandwidthMeter bandwidthMeterWithoutBuilder = new DefaultBandwidthMeter(); + long initialEstimateWithoutBuilder = bandwidthMeterWithoutBuilder.getBitrateEstimate(); + + assertThat(initialEstimateWithBuilder).isGreaterThan(100_000L); + assertThat(initialEstimateWithBuilder).isLessThan(50_000_000L); + assertThat(initialEstimateWithoutBuilder).isGreaterThan(100_000L); + assertThat(initialEstimateWithoutBuilder).isLessThan(50_000_000L); + } + + @Test + public void defaultInitialBitrateEstimate_withoutAccessNetworkStatePermission_isReasonable() { + Shadows.shadowOf(RuntimeEnvironment.application).denyPermissions(ACCESS_NETWORK_STATE); + DefaultBandwidthMeter bandwidthMeter = + new DefaultBandwidthMeter.Builder(RuntimeEnvironment.application).build(); + long initialEstimate = bandwidthMeter.getBitrateEstimate(); + + assertThat(initialEstimate).isGreaterThan(100_000L); + assertThat(initialEstimate).isLessThan(50_000_000L); + } + + private void setActiveNetworkInfo(NetworkInfo networkInfo) { + Shadows.shadowOf(connectivityManager).setActiveNetworkInfo(networkInfo); + } + + private void setNetworkCountryIso(String countryIso) { + Shadows.shadowOf(telephonyManager).setNetworkCountryIso(countryIso); + } +} From 9607c6de1bb8cca0e8e8449090bce3296d390a47 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 19 Oct 2018 10:36:35 -0700 Subject: [PATCH 023/210] Properly reset period id and start position in ExoPlayerImpl. This is a no-op change as the respective values are not used so far but the change makes the current state cleaner and is less error-prone. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217892421 --- .../android/exoplayer2/ExoPlayerImpl.java | 18 ++++++++----- .../exoplayer2/ExoPlayerImplInternal.java | 25 ++++++++----------- .../android/exoplayer2/PlaybackInfo.java | 20 ++++++++++++++- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 7912878e20..04bc1c611d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -731,20 +731,26 @@ import java.util.concurrent.CopyOnWriteArraySet; maskingPeriodIndex = getCurrentPeriodIndex(); maskingWindowPositionMs = getCurrentPosition(); } + MediaPeriodId mediaPeriodId = + resetPosition + ? playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window) + : playbackInfo.periodId; + long startPositionUs = resetPosition ? 0 : playbackInfo.positionUs; + long contentPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs; return new PlaybackInfo( resetState ? Timeline.EMPTY : playbackInfo.timeline, resetState ? null : playbackInfo.manifest, - playbackInfo.periodId, - playbackInfo.startPositionUs, - playbackInfo.contentPositionUs, + mediaPeriodId, + startPositionUs, + contentPositionUs, playbackState, /* isLoading= */ false, resetState ? TrackGroupArray.EMPTY : playbackInfo.trackGroups, resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult, - playbackInfo.periodId, - playbackInfo.startPositionUs, + mediaPeriodId, + startPositionUs, /* totalBufferedDurationUs= */ 0, - playbackInfo.startPositionUs); + startPositionUs); } private void updatePlaybackInfo( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 83b1243f42..fbf1536f57 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -607,7 +607,7 @@ import java.util.Collections; if (resolvedSeekPosition == null) { // The seek position was valid for the timeline that it was performed into, but the // timeline has changed or is not ready and a suitable seek position could not be resolved. - periodId = getFirstMediaPeriodId(); + periodId = playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window); periodPositionUs = C.TIME_UNSET; contentPositionUs = C.TIME_UNSET; seekPositionAdjusted = true; @@ -762,17 +762,6 @@ import java.util.Collections; } } - private MediaPeriodId getFirstMediaPeriodId() { - Timeline timeline = playbackInfo.timeline; - if (timeline.isEmpty()) { - return PlaybackInfo.DUMMY_MEDIA_PERIOD_ID; - } - int firstPeriodIndex = - timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window) - .firstPeriodIndex; - return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex)); - } - private void resetInternal( boolean releaseMediaSource, boolean resetPosition, boolean resetState) { handler.removeMessages(MSG_DO_SOME_WORK); @@ -801,8 +790,11 @@ import java.util.Collections; pendingMessages.clear(); nextPendingMessageIndex = 0; } + MediaPeriodId mediaPeriodId = + resetPosition + ? playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window) + : playbackInfo.periodId; // Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored. - MediaPeriodId mediaPeriodId = resetPosition ? getFirstMediaPeriodId() : playbackInfo.periodId; long startPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.positionUs; long contentPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs; playbackInfo = @@ -1178,8 +1170,13 @@ import java.util.Collections; periodPosition = resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true); } catch (IllegalSeekPositionException e) { + MediaPeriodId firstMediaPeriodId = + playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window); playbackInfo = - playbackInfo.resetToNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET); + playbackInfo.resetToNewPosition( + firstMediaPeriodId, + /* startPositionUs= */ C.TIME_UNSET, + /* contentPositionUs= */ C.TIME_UNSET); throw e; } pendingInitialSeekPosition = null; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 8c73fde3be..4333f51bf7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -30,7 +30,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; * Dummy media period id used while the timeline is empty and no period id is specified. This id * is used when playback infos are created with {@link #createDummy(long, TrackSelectorResult)}. */ - public static final MediaPeriodId DUMMY_MEDIA_PERIOD_ID = + private static final MediaPeriodId DUMMY_MEDIA_PERIOD_ID = new MediaPeriodId(/* periodUid= */ new Object()); /** The current {@link Timeline}. */ @@ -151,6 +151,24 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; this.positionUs = positionUs; } + /** + * Returns dummy media period id for the first-to-be-played period of the current timeline. + * + * @param shuffleModeEnabled Whether shuffle mode is enabled. + * @param window A writable {@link Timeline.Window}. + * @return A dummy media period id for the first-to-be-played period of the current timeline. + */ + public MediaPeriodId getDummyFirstMediaPeriodId( + boolean shuffleModeEnabled, Timeline.Window window) { + if (timeline.isEmpty()) { + return DUMMY_MEDIA_PERIOD_ID; + } + int firstPeriodIndex = + timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window) + .firstPeriodIndex; + return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex)); + } + /** * Copies playback info and resets playing and loading position. * From efb025154163c28f6645420119ba851c69b77943 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Sun, 21 Oct 2018 02:53:00 -0700 Subject: [PATCH 024/210] Add ACCESS_NETWORK_STATE permission for MH tests ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218058185 --- playbacktests/src/androidTest/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/playbacktests/src/androidTest/AndroidManifest.xml b/playbacktests/src/androidTest/AndroidManifest.xml index d458df55bb..4165a42568 100644 --- a/playbacktests/src/androidTest/AndroidManifest.xml +++ b/playbacktests/src/androidTest/AndroidManifest.xml @@ -18,6 +18,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.google.android.exoplayer2.playbacktests"> + From 8b1080d5ccc2428d81ff71a9ff4a0cc18b4f672b Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 22 Oct 2018 03:31:38 -0700 Subject: [PATCH 025/210] Check if source has been prepared before releasing it. In ConcatenatingMediaSource, the source may be removed before it started preparing (this may happen if lazyPreparation=true). In this case, we shouldn't call releaseSource as the preparation didn't start. Issue:#4986 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218141658 --- RELEASENOTES.md | 3 +++ .../source/ConcatenatingMediaSource.java | 18 ++++++++++++------ .../source/ConcatenatingMediaSourceTest.java | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cd8caa0c8a..ba1734bc30 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -23,6 +23,9 @@ ([#4532](https://github.com/google/ExoPlayer/issues/4532)). * Suppress spurious "references unknown class member" shrinking warning ([#4890](https://github.com/google/ExoPlayer/issues/4890)). +* Fix issue where a `NullPointerException` is thrown when removing an unprepared + media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` + option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)). ### 2.9.0 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 7418e84449..1b614e18a9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -502,9 +502,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource Date: Tue, 23 Oct 2018 05:41:48 -0700 Subject: [PATCH 026/210] Swap google() and jcenter() in docs and gradle config. This seems to be more stable in case Bintray has issues updating the ExoPlayer sources. Issue:#4997 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218327350 --- README.md | 2 +- RELEASENOTES.md | 2 ++ build.gradle | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 13dfaddab3..b69be03ae4 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ included in the `build.gradle` file in the root of your project: ```gradle repositories { - jcenter() google() + jcenter() } ``` diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ba1734bc30..051a8451e9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -26,6 +26,8 @@ * Fix issue where a `NullPointerException` is thrown when removing an unprepared media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)). +* Swap recommended order for google() and jcenter() in gradle config + ([#4997](https://github.com/google/ExoPlayer/issues/4997)). ### 2.9.0 ### diff --git a/build.gradle b/build.gradle index a013f4fb84..96eade1aa3 100644 --- a/build.gradle +++ b/build.gradle @@ -13,8 +13,8 @@ // limitations under the License. buildscript { repositories { - jcenter() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.1.4' @@ -32,8 +32,8 @@ buildscript { } allprojects { repositories { - jcenter() google() + jcenter() } project.ext { exoplayerPublishEnabled = true From 13e0513ea3efd6385d4cddbaca47a43807d486aa Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 23 Oct 2018 06:23:50 -0700 Subject: [PATCH 027/210] Give EventDispatcher more predictable behavior If EventDispatcher.removeListener is called to remove a listener, and if the call is made from the same thread that said listener handles events on, then it should be guaranteed that the listener will not be subsequently invoked on that thread. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218331427 --- .../exoplayer2/util/EventDispatcher.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventDispatcher.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventDispatcher.java index 26c02d8ae9..07f278c808 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventDispatcher.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventDispatcher.java @@ -39,22 +39,23 @@ public final class EventDispatcher { /** The list of listeners and handlers. */ private final CopyOnWriteArrayList> listeners; - /** Creates event dispatcher. */ + /** Creates an event dispatcher. */ public EventDispatcher() { listeners = new CopyOnWriteArrayList<>(); } - /** Adds listener to event dispatcher. */ + /** Adds a listener to the event dispatcher. */ public void addListener(Handler handler, T eventListener) { Assertions.checkArgument(handler != null && eventListener != null); removeListener(eventListener); listeners.add(new HandlerAndListener<>(handler, eventListener)); } - /** Removes listener from event dispatcher. */ + /** Removes a listener from the event dispatcher. */ public void removeListener(T eventListener) { for (HandlerAndListener handlerAndListener : listeners) { if (handlerAndListener.listener == eventListener) { + handlerAndListener.release(); listeners.remove(handlerAndListener); } } @@ -67,19 +68,33 @@ public final class EventDispatcher { */ public void dispatch(Event event) { for (HandlerAndListener handlerAndListener : listeners) { - T eventListener = handlerAndListener.listener; - handlerAndListener.handler.post(() -> event.sendTo(eventListener)); + handlerAndListener.dispatch(event); } } private static final class HandlerAndListener { - public final Handler handler; - public final T listener; + private final Handler handler; + private final T listener; + + private boolean released; public HandlerAndListener(Handler handler, T eventListener) { this.handler = handler; this.listener = eventListener; } + + public void release() { + released = true; + } + + public void dispatch(Event event) { + handler.post( + () -> { + if (!released) { + event.sendTo(listener); + } + }); + } } } From e6b49a5410f42fa44c0ec3e76137390b609afaf6 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 23 Oct 2018 09:49:45 -0700 Subject: [PATCH 028/210] Fix handling of MP3s with appended data Issue: #4954 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=218357113 --- RELEASENOTES.md | 7 ++-- .../extractor/mp3/ConstantBitrateSeeker.java | 5 +++ .../exoplayer2/extractor/mp3/MlltSeeker.java | 4 +++ .../extractor/mp3/Mp3Extractor.java | 32 +++++++++++++++---- .../exoplayer2/extractor/mp3/VbriSeeker.java | 10 ++++-- .../exoplayer2/extractor/mp3/XingSeeker.java | 25 +++++++++++---- 6 files changed, 66 insertions(+), 17 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 051a8451e9..bf734489b9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,8 +6,11 @@ network type. * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). -* MP3: Support seeking based on MLLT metadata - ([#3241](https://github.com/google/ExoPlayer/issues/3241)). +* MP3: + * Support seeking based on MLLT metadata + ([#3241](https://github.com/google/ExoPlayer/issues/3241)). + * Fix handling of streams with appending data + ([#4954](https://github.com/google/ExoPlayer/issues/4954)). * IMA extension: For preroll to live stream transitions, project forward the loading position to avoid being behind the live window. * Fix issue with blind seeking to windows with non-zero offset in a diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java index bffc43a540..f400720772 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java @@ -39,4 +39,9 @@ import com.google.android.exoplayer2.extractor.MpegAudioHeader; public long getTimeUs(long position) { return getTimeUsAtPosition(position); } + + @Override + public long getDataEndPosition() { + return C.POSITION_UNSET; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java index ff607b9482..868c1d9fbf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/MlltSeeker.java @@ -118,4 +118,8 @@ import com.google.android.exoplayer2.util.Util; } } + @Override + public long getDataEndPosition() { + return C.POSITION_UNSET; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 84f620734b..e8848bf983 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -223,7 +223,7 @@ public final class Mp3Extractor implements Extractor { private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { if (sampleBytesRemaining == 0) { extractorInput.resetPeekPosition(); - if (!extractorInput.peekFully(scratch.data, 0, 4, true)) { + if (peekEndOfStreamOrHeader(extractorInput)) { return RESULT_END_OF_INPUT; } scratch.setPosition(0); @@ -285,9 +285,12 @@ public final class Mp3Extractor implements Extractor { } } while (true) { - if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) { - // We reached the end of the stream but found at least one valid frame. - break; + if (peekEndOfStreamOrHeader(input)) { + if (validFrameCount > 0) { + // We reached the end of the stream but found at least one valid frame. + break; + } + throw new EOFException(); } scratch.setPosition(0); int headerData = scratch.readInt(); @@ -332,6 +335,17 @@ public final class Mp3Extractor implements Extractor { return true; } + /** + * Returns whether the extractor input is peeking the end of the stream. If {@code false}, + * populates the scratch buffer with the next four bytes. + */ + private boolean peekEndOfStreamOrHeader(ExtractorInput extractorInput) + throws IOException, InterruptedException { + return (seeker != null && extractorInput.getPeekPosition() == seeker.getDataEndPosition()) + || !extractorInput.peekFully( + scratch.data, /* offset= */ 0, /* length= */ 4, /* allowEndOfInput= */ true); + } + /** * Consumes the next frame from the {@code input} if it contains VBRI or Xing seeking metadata, * returning a {@link Seeker} if the metadata was present and valid, or {@code null} otherwise. @@ -433,8 +447,9 @@ public final class Mp3Extractor implements Extractor { } /** - * {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be - * used to work out the new sample basis timestamp after seeking and resynchronization. + * {@link SeekMap} that provides the end position of audio data and also allows mapping from + * position (byte offset) back to time, which can be used to work out the new sample basis + * timestamp after seeking and resynchronization. */ /* package */ interface Seeker extends SeekMap { @@ -446,6 +461,11 @@ public final class Mp3Extractor implements Extractor { */ long getTimeUs(long position); + /** + * Returns the position (byte offset) in the stream that is immediately after audio data, or + * {@link C#POSITION_UNSET} if not known. + */ + long getDataEndPosition(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java index 774505f622..15e778115d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java @@ -89,17 +89,19 @@ import com.google.android.exoplayer2.util.Util; if (inputLength != C.LENGTH_UNSET && inputLength != position) { Log.w(TAG, "VBRI data size mismatch: " + inputLength + ", " + position); } - return new VbriSeeker(timesUs, positions, durationUs); + return new VbriSeeker(timesUs, positions, durationUs, /* dataEndPosition= */ position); } private final long[] timesUs; private final long[] positions; private final long durationUs; + private final long dataEndPosition; - private VbriSeeker(long[] timesUs, long[] positions, long durationUs) { + private VbriSeeker(long[] timesUs, long[] positions, long durationUs, long dataEndPosition) { this.timesUs = timesUs; this.positions = positions; this.durationUs = durationUs; + this.dataEndPosition = dataEndPosition; } @Override @@ -129,4 +131,8 @@ import com.google.android.exoplayer2.util.Util; return durationUs; } + @Override + public long getDataEndPosition() { + return dataEndPosition; + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java index c2971e47ce..42752e55fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java @@ -75,17 +75,17 @@ import com.google.android.exoplayer2.util.Util; if (inputLength != C.LENGTH_UNSET && inputLength != position + dataSize) { Log.w(TAG, "XING data size mismatch: " + inputLength + ", " + (position + dataSize)); } - return new XingSeeker(position, mpegAudioHeader.frameSize, durationUs, dataSize, - tableOfContents); + return new XingSeeker( + position, mpegAudioHeader.frameSize, durationUs, dataSize, tableOfContents); } private final long dataStartPosition; private final int xingFrameSize; private final long durationUs; - /** - * Data size, including the XING frame. - */ + /** Data size, including the XING frame. */ private final long dataSize; + + private final long dataEndPosition; /** * Entries are in the range [0, 255], but are stored as long integers for convenience. Null if the * table of contents was missing from the header, in which case seeking is not be supported. @@ -93,7 +93,12 @@ import com.google.android.exoplayer2.util.Util; private final @Nullable long[] tableOfContents; private XingSeeker(long dataStartPosition, int xingFrameSize, long durationUs) { - this(dataStartPosition, xingFrameSize, durationUs, C.LENGTH_UNSET, null); + this( + dataStartPosition, + xingFrameSize, + durationUs, + /* dataSize= */ C.LENGTH_UNSET, + /* tableOfContents= */ null); } private XingSeeker( @@ -105,8 +110,9 @@ import com.google.android.exoplayer2.util.Util; this.dataStartPosition = dataStartPosition; this.xingFrameSize = xingFrameSize; this.durationUs = durationUs; - this.dataSize = dataSize; this.tableOfContents = tableOfContents; + this.dataSize = dataSize; + dataEndPosition = dataSize == C.LENGTH_UNSET ? C.POSITION_UNSET : dataStartPosition + dataSize; } @Override @@ -166,6 +172,11 @@ import com.google.android.exoplayer2.util.Util; return durationUs; } + @Override + public long getDataEndPosition() { + return dataEndPosition; + } + /** * Returns the time in microseconds for a given table index. * From b007cbf2b488bd851f2af0e9d7642a60453ed606 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 30 Oct 2018 02:19:08 -0700 Subject: [PATCH 029/210] Let apps specify whether to focus skip button on ATV Issue: #5019 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219267048 --- RELEASENOTES.md | 3 +++ .../exoplayer2/ext/ima/ImaAdsLoader.java | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bf734489b9..16b65aa09a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -26,6 +26,9 @@ ([#4532](https://github.com/google/ExoPlayer/issues/4532)). * Suppress spurious "references unknown class member" shrinking warning ([#4890](https://github.com/google/ExoPlayer/issues/4890)). +* IMA extension: + * Let apps specify whether to focus the skip button on ATV + ([#5019](https://github.com/google/ExoPlayer/issues/5019)). * Fix issue where a `NullPointerException` is thrown when removing an unprepared media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)). 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 95a3a588b4..cc621c6218 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 @@ -93,6 +93,7 @@ public final class ImaAdsLoader private @Nullable AdEventListener adEventListener; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; + private boolean focusSkipButtonWhenAvailable; private ImaFactory imaFactory; /** @@ -104,6 +105,7 @@ public final class ImaAdsLoader this.context = Assertions.checkNotNull(context); vastLoadTimeoutMs = TIMEOUT_UNSET; mediaLoadTimeoutMs = TIMEOUT_UNSET; + focusSkipButtonWhenAvailable = true; imaFactory = new DefaultImaFactory(); } @@ -159,6 +161,20 @@ public final class ImaAdsLoader return this; } + /** + * Sets whether to focus the skip button (when available) on Android TV devices. The default + * setting is {@code true}. + * + * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on + * Android TV devices. + * @return This builder, for convenience. + * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) + */ + public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvailable) { + this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable; + return this; + } + // @VisibleForTesting /* package */ Builder setImaFactory(ImaFactory imaFactory) { this.imaFactory = Assertions.checkNotNull(imaFactory); @@ -181,6 +197,7 @@ public final class ImaAdsLoader null, vastLoadTimeoutMs, mediaLoadTimeoutMs, + focusSkipButtonWhenAvailable, adEventListener, imaFactory); } @@ -200,6 +217,7 @@ public final class ImaAdsLoader adsResponse, vastLoadTimeoutMs, mediaLoadTimeoutMs, + focusSkipButtonWhenAvailable, adEventListener, imaFactory); } @@ -252,6 +270,7 @@ public final class ImaAdsLoader private final @Nullable String adsResponse; private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; + private final boolean focusSkipButtonWhenAvailable; private final @Nullable AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; @@ -338,6 +357,7 @@ public final class ImaAdsLoader /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* focusSkipButtonWhenAvailable= */ true, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -362,6 +382,7 @@ public final class ImaAdsLoader /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* focusSkipButtonWhenAvailable= */ true, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -373,6 +394,7 @@ public final class ImaAdsLoader @Nullable String adsResponse, int vastLoadTimeoutMs, int mediaLoadTimeoutMs, + boolean focusSkipButtonWhenAvailable, @Nullable AdEventListener adEventListener, ImaFactory imaFactory) { Assertions.checkArgument(adTagUri != null || adsResponse != null); @@ -380,6 +402,7 @@ public final class ImaAdsLoader this.adsResponse = adsResponse; this.vastLoadTimeoutMs = vastLoadTimeoutMs; this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; + this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable; this.adEventListener = adEventListener; this.imaFactory = imaFactory; if (imaSdkSettings == null) { @@ -926,6 +949,7 @@ public final class ImaAdsLoader if (mediaLoadTimeoutMs != TIMEOUT_UNSET) { adsRenderingSettings.setLoadVideoTimeout(mediaLoadTimeoutMs); } + adsRenderingSettings.setFocusSkipButtonWhenAvailable(focusSkipButtonWhenAvailable); // Set up the ad playback state, skipping ads based on the start position as required. long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints()); From b1d5966ea567111fbdaa087c6b971a65d195c87c Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 31 Oct 2018 03:37:24 -0700 Subject: [PATCH 030/210] Allow MP4s with truncated stco to be played ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219448836 --- .../exoplayer2/extractor/mp4/AtomParsers.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 7104630a23..0cda3fafa8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -221,11 +221,22 @@ import java.util.List; for (int i = 0; i < sampleCount; i++) { // Advance to the next chunk if necessary. - while (remainingSamplesInChunk == 0) { - Assertions.checkState(chunkIterator.moveNext()); + boolean chunkDataComplete = true; + while (remainingSamplesInChunk == 0 && (chunkDataComplete = chunkIterator.moveNext())) { offset = chunkIterator.offset; remainingSamplesInChunk = chunkIterator.numSamples; } + if (!chunkDataComplete) { + Log.w(TAG, "Unexpected end of chunk data"); + sampleCount = i; + offsets = Arrays.copyOf(offsets, sampleCount); + sizes = Arrays.copyOf(sizes, sampleCount); + timestamps = Arrays.copyOf(timestamps, sampleCount); + flags = Arrays.copyOf(flags, sampleCount); + remainingSamplesAtTimestampOffset = 0; + remainingTimestampOffsetChanges = 0; + break; + } // Add on the timestamp offset if ctts is present. if (ctts != null) { From 1fb2d83ba5fc469d49179ce65701e7eee3487dad Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 31 Oct 2018 04:59:04 -0700 Subject: [PATCH 031/210] Clarify Java 8 gradle requirement in developer guide. Issue:#5026 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219454985 --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b69be03ae4..2b6a508aaa 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,15 @@ following will add a dependency to the full library: implementation 'com.google.android.exoplayer:exoplayer:2.X.X' ``` -where `2.X.X` is your preferred version. Alternatively, you can depend on only -the library modules that you actually need. For example the following will add -dependencies on the Core, DASH and UI library modules, as might be required for -an app that plays DASH content: +where `2.X.X` is your preferred version. If not enabled already, you also need +to turn on Java 8 support in all `build.gradle` files depending on ExoPlayer, by +adding `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to the +`android` section. + +As an alternative to the full library, you can depend on only the library +modules that you actually need. For example the following will add dependencies +on the Core, DASH and UI library modules, as might be required for an app that +plays DASH content: ```gradle implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X' From bb09f5cb12f491d647bacfa7291fe4c58d3dd2c5 Mon Sep 17 00:00:00 2001 From: ojw28 Date: Wed, 31 Oct 2018 19:17:14 +0000 Subject: [PATCH 032/210] Merge pull request #4911 from Comcast/feature/hls-audio-text-id-uniqueness Create unique id for HLS audio and text tracks --- .../hls/playlist/HlsPlaylistParser.java | 7 ++-- .../playlist/HlsMasterPlaylistParserTest.java | 37 ++++++++++++++++++- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 49826902cd..bd2bea0197 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -341,6 +341,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser Date: Wed, 31 Oct 2018 19:17:25 +0000 Subject: [PATCH 033/210] Merge pull request #4996 from YukiMatsumura/fix-idle-requirements fix checkIdleRequirement --- .../com/google/android/exoplayer2/scheduler/Requirements.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java index 4d6dbd83be..5acd31ee0d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/scheduler/Requirements.java @@ -187,7 +187,7 @@ public final class Requirements { } PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); return Util.SDK_INT >= 23 - ? !powerManager.isDeviceIdleMode() + ? powerManager.isDeviceIdleMode() : Util.SDK_INT >= 20 ? !powerManager.isInteractive() : !powerManager.isScreenOn(); } From 22ee67617e1137da8ea528750b4194910d818271 Mon Sep 17 00:00:00 2001 From: ojw28 Date: Wed, 31 Oct 2018 20:01:56 +0000 Subject: [PATCH 034/210] Merge pull request #5004 from pakerfeldt/status-message-invalidresponsecodeexception Provide http status message to InvalidResponseCodeException --- .../exoplayer2/ext/cronet/CronetDataSource.java | 14 +++++++++++--- .../exoplayer2/ext/okhttp/OkHttpDataSource.java | 4 ++-- .../exoplayer2/upstream/DefaultHttpDataSource.java | 4 +++- .../exoplayer2/upstream/HttpDataSource.java | 12 +++++++++++- .../DefaultLoadErrorHandlingPolicyTest.java | 7 ++++--- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 9525983491..9f381d0053 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -326,8 +326,12 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { // Check for a valid response code. int responseCode = responseInfo.getHttpStatusCode(); if (responseCode < 200 || responseCode > 299) { - InvalidResponseCodeException exception = new InvalidResponseCodeException(responseCode, - responseInfo.getAllHeaders(), currentDataSpec); + InvalidResponseCodeException exception = + new InvalidResponseCodeException( + responseCode, + responseInfo.getHttpStatusText(), + responseInfo.getAllHeaders(), + currentDataSpec); if (responseCode == 416) { exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); } @@ -611,7 +615,11 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { // The industry standard is to disregard POST redirects when the status code is 307 or 308. if (responseCode == 307 || responseCode == 308) { exception = - new InvalidResponseCodeException(responseCode, info.getAllHeaders(), currentDataSpec); + new InvalidResponseCodeException( + responseCode, + info.getHttpStatusText(), + info.getAllHeaders(), + currentDataSpec); operation.open(); return; } diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 2707f539bc..778277fdbc 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -172,8 +172,8 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { if (!response.isSuccessful()) { Map> headers = response.headers().toMultimap(); closeConnectionQuietly(); - InvalidResponseCodeException exception = new InvalidResponseCodeException( - responseCode, headers, dataSpec); + InvalidResponseCodeException exception = + new InvalidResponseCodeException(responseCode, response.message(), headers, dataSpec); if (responseCode == 416) { exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index 3673af5540..c6749e6c8f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -283,8 +283,10 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou } int responseCode; + String responseMessage; try { responseCode = connection.getResponseCode(); + responseMessage = connection.getResponseMessage(); } catch (IOException e) { closeConnectionQuietly(); throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e, @@ -296,7 +298,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou Map> headers = connection.getHeaderFields(); closeConnectionQuietly(); InvalidResponseCodeException exception = - new InvalidResponseCodeException(responseCode, headers, dataSpec); + new InvalidResponseCodeException(responseCode, responseMessage, headers, dataSpec); if (responseCode == 416) { exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index 0be7b857df..e73901eccb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.upstream; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Util; @@ -294,15 +295,24 @@ public interface HttpDataSource extends DataSource { */ public final int responseCode; + /** + * The HTTP status message. + */ + @Nullable public final String responseMessage; + /** * An unmodifiable map of the response header fields and values. */ public final Map> headerFields; - public InvalidResponseCodeException(int responseCode, Map> headerFields, + public InvalidResponseCodeException( + int responseCode, + @Nullable String responseMessage, + Map> headerFields, DataSpec dataSpec) { super("Response code: " + responseCode, dataSpec, TYPE_OPEN); this.responseCode = responseCode; + this.responseMessage = responseMessage; this.headerFields = headerFields; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java index e1700e3b20..2a35a31e74 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java @@ -35,7 +35,7 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_blacklist404() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(404, Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException(404, "Not Found", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)) .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } @@ -43,7 +43,7 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_blacklist410() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(410, Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException(410, "Gone", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)) .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } @@ -51,7 +51,8 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_dontBlacklistUnexpectedHttpCodes() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(500, Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException( + 500, "Internal Server Error", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)).isEqualTo(C.TIME_UNSET); } From 8200fe5ae6da02cf29ec090364ce2c6ea51a8b76 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 31 Oct 2018 20:16:23 +0000 Subject: [PATCH 035/210] Merge branch 'customize-ads-rendering-settings-more' of https://github.com/ogaclejapan/ExoPlayer into ogaclejapan-customize-ads-rendering-settings-more --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index cc621c6218..6ca3bfd881 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 @@ -40,6 +40,7 @@ import com.google.ads.interactivemedia.v3.api.AdsRequest; import com.google.ads.interactivemedia.v3.api.CompanionAdSlot; import com.google.ads.interactivemedia.v3.api.ImaSdkFactory; import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; +import com.google.ads.interactivemedia.v3.api.UiElement; import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider; import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate; @@ -67,8 +68,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** Loads ads using the IMA SDK. All methods are called on the main thread. */ public final class ImaAdsLoader @@ -91,8 +94,10 @@ public final class ImaAdsLoader private @Nullable ImaSdkSettings imaSdkSettings; private @Nullable AdEventListener adEventListener; + private @Nullable Set adUiElements; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; + private int mediaBitrate; private boolean focusSkipButtonWhenAvailable; private ImaFactory imaFactory; @@ -105,6 +110,7 @@ public final class ImaAdsLoader this.context = Assertions.checkNotNull(context); vastLoadTimeoutMs = TIMEOUT_UNSET; mediaLoadTimeoutMs = TIMEOUT_UNSET; + mediaBitrate = BITRATE_UNSET; focusSkipButtonWhenAvailable = true; imaFactory = new DefaultImaFactory(); } @@ -135,6 +141,18 @@ public final class ImaAdsLoader return this; } + /** + * Sets the ad UI elements to be rendered by the IMA SDK. + * + * @param adUiElements The ad UI elements to be rendered by the IMA SDK. + * @return This builder, for convenience. + * @see AdsRenderingSettings#setUiElements(Set) + */ + public Builder setAdUiElements(Set adUiElements) { + this.adUiElements = new HashSet<>(Assertions.checkNotNull(adUiElements)); + return this; + } + /** * Sets the VAST load timeout, in milliseconds. * @@ -143,7 +161,7 @@ public final class ImaAdsLoader * @see AdsRequest#setVastLoadTimeout(float) */ public Builder setVastLoadTimeoutMs(int vastLoadTimeoutMs) { - Assertions.checkArgument(vastLoadTimeoutMs >= 0); + Assertions.checkArgument(vastLoadTimeoutMs > 0); this.vastLoadTimeoutMs = vastLoadTimeoutMs; return this; } @@ -156,11 +174,24 @@ public final class ImaAdsLoader * @see AdsRenderingSettings#setLoadVideoTimeout(int) */ public Builder setMediaLoadTimeoutMs(int mediaLoadTimeoutMs) { - Assertions.checkArgument(mediaLoadTimeoutMs >= 0); + Assertions.checkArgument(mediaLoadTimeoutMs > 0); this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; return this; } + /** + * Sets the media maximum recommended bitrate for ads, in bps. + * + * @param bitrate The media maximum recommended bitrate for ads, in bps. + * @return This builder, for convenience. + * @see AdsRenderingSettings#setBitrateKbps(int) + */ + public Builder setMaxMediaBitrate(int bitrate) { + Assertions.checkArgument(bitrate > 0); + this.mediaBitrate = bitrate; + return this; + } + /** * Sets whether to focus the skip button (when available) on Android TV devices. The default * setting is {@code true}. @@ -197,7 +228,9 @@ public final class ImaAdsLoader null, vastLoadTimeoutMs, mediaLoadTimeoutMs, + mediaBitrate, focusSkipButtonWhenAvailable, + adUiElements, adEventListener, imaFactory); } @@ -217,7 +250,9 @@ public final class ImaAdsLoader adsResponse, vastLoadTimeoutMs, mediaLoadTimeoutMs, + mediaBitrate, focusSkipButtonWhenAvailable, + adUiElements, adEventListener, imaFactory); } @@ -247,6 +282,7 @@ public final class ImaAdsLoader private static final long MAXIMUM_PRELOAD_DURATION_MS = 8000; private static final int TIMEOUT_UNSET = -1; + private static final int BITRATE_UNSET = -1; /** The state of ad playback. */ @Documented @@ -271,6 +307,8 @@ public final class ImaAdsLoader private final int vastLoadTimeoutMs; private final int mediaLoadTimeoutMs; private final boolean focusSkipButtonWhenAvailable; + private final int mediaBitrate; + private final @Nullable Set adUiElements; private final @Nullable AdEventListener adEventListener; private final ImaFactory imaFactory; private final Timeline.Period period; @@ -357,7 +395,9 @@ public final class ImaAdsLoader /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* mediaBitrate= */ BITRATE_UNSET, /* focusSkipButtonWhenAvailable= */ true, + /* adUiElements= */ null, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -382,7 +422,9 @@ public final class ImaAdsLoader /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* mediaBitrate= */ BITRATE_UNSET, /* focusSkipButtonWhenAvailable= */ true, + /* adUiElements= */ null, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -394,7 +436,9 @@ public final class ImaAdsLoader @Nullable String adsResponse, int vastLoadTimeoutMs, int mediaLoadTimeoutMs, + int mediaBitrate, boolean focusSkipButtonWhenAvailable, + @Nullable Set adUiElements, @Nullable AdEventListener adEventListener, ImaFactory imaFactory) { Assertions.checkArgument(adTagUri != null || adsResponse != null); @@ -402,7 +446,9 @@ public final class ImaAdsLoader this.adsResponse = adsResponse; this.vastLoadTimeoutMs = vastLoadTimeoutMs; this.mediaLoadTimeoutMs = mediaLoadTimeoutMs; + this.mediaBitrate = mediaBitrate; this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable; + this.adUiElements = adUiElements; this.adEventListener = adEventListener; this.imaFactory = imaFactory; if (imaSdkSettings == null) { @@ -949,7 +995,13 @@ public final class ImaAdsLoader if (mediaLoadTimeoutMs != TIMEOUT_UNSET) { adsRenderingSettings.setLoadVideoTimeout(mediaLoadTimeoutMs); } + if (mediaBitrate != BITRATE_UNSET) { + adsRenderingSettings.setBitrateKbps(mediaBitrate / 1000); + } adsRenderingSettings.setFocusSkipButtonWhenAvailable(focusSkipButtonWhenAvailable); + if (adUiElements != null) { + adsRenderingSettings.setUiElements(adUiElements); + } // Set up the ad playback state, skipping ads based on the start position as required. long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints()); From b88c88e21ea4cea82c0bb68bdf4b387541e7b9d7 Mon Sep 17 00:00:00 2001 From: ojw28 Date: Wed, 31 Oct 2018 20:21:44 +0000 Subject: [PATCH 036/210] Merge pull request #4930 from Comcast/program_information Add Support for Parsing ProgramInformation --- .../source/dash/manifest/DashManifest.java | 10 ++- .../dash/manifest/DashManifestParser.java | 30 ++++++- .../dash/manifest/ProgramInformation.java | 80 +++++++++++++++++++ library/dash/src/test/assets/sample_mpd_1 | 6 ++ .../dash/manifest/DashManifestParserTest.java | 11 +++ .../dash/manifest/DashManifestTest.java | 2 +- 6 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java index 1fdb137be9..6446909808 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java @@ -86,12 +86,17 @@ public class DashManifest implements FilterableManifest { */ public final Uri location; + /** + * The ProgramInformation of this manifest. + */ + public final ProgramInformation programInformation; + private final List periods; public DashManifest(long availabilityStartTimeMs, long durationMs, long minBufferTimeMs, boolean dynamic, long minUpdatePeriodMs, long timeShiftBufferDepthMs, long suggestedPresentationDelayMs, long publishTimeMs, UtcTimingElement utcTiming, - Uri location, List periods) { + Uri location, ProgramInformation programInformation, List periods) { this.availabilityStartTimeMs = availabilityStartTimeMs; this.durationMs = durationMs; this.minBufferTimeMs = minBufferTimeMs; @@ -102,6 +107,7 @@ public class DashManifest implements FilterableManifest { this.publishTimeMs = publishTimeMs; this.utcTiming = utcTiming; this.location = location; + this.programInformation = programInformation; this.periods = periods == null ? Collections.emptyList() : periods; } @@ -150,7 +156,7 @@ public class DashManifest implements FilterableManifest { long newDuration = durationMs != C.TIME_UNSET ? durationMs - shiftMs : C.TIME_UNSET; return new DashManifest(availabilityStartTimeMs, newDuration, minBufferTimeMs, dynamic, minUpdatePeriodMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, publishTimeMs, - utcTiming, location, copyPeriods); + utcTiming, location, programInformation, copyPeriods); } private static ArrayList copyAdaptationSets( diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index 31ff7d283e..d048e22b33 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -122,6 +122,7 @@ public class DashManifestParser extends DefaultHandler long publishTimeMs = parseDateTime(xpp, "publishTime", C.TIME_UNSET); UtcTimingElement utcTiming = null; Uri location = null; + ProgramInformation programInformation = null; List periods = new ArrayList<>(); long nextPeriodStartMs = dynamic ? C.TIME_UNSET : 0; @@ -138,6 +139,8 @@ public class DashManifestParser extends DefaultHandler utcTiming = parseUtcTiming(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "Location")) { location = Uri.parse(xpp.nextText()); + } else if (XmlPullParserUtil.isStartTag(xpp, "ProgramInformation")) { + programInformation = parseProgramInformation(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) { Pair periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs); Period period = periodWithDurationMs.first; @@ -175,16 +178,16 @@ public class DashManifestParser extends DefaultHandler return buildMediaPresentationDescription(availabilityStartTime, durationMs, minBufferTimeMs, dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, - publishTimeMs, utcTiming, location, periods); + publishTimeMs, utcTiming, location, programInformation, periods); } protected DashManifest buildMediaPresentationDescription(long availabilityStartTime, long durationMs, long minBufferTimeMs, boolean dynamic, long minUpdateTimeMs, long timeShiftBufferDepthMs, long suggestedPresentationDelayMs, long publishTimeMs, - UtcTimingElement utcTiming, Uri location, List periods) { + UtcTimingElement utcTiming, Uri location, ProgramInformation programInformation, List periods) { return new DashManifest(availabilityStartTime, durationMs, minBufferTimeMs, dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, - publishTimeMs, utcTiming, location, periods); + publishTimeMs, utcTiming, location, programInformation, periods); } protected UtcTimingElement parseUtcTiming(XmlPullParser xpp) { @@ -998,6 +1001,27 @@ public class DashManifestParser extends DefaultHandler return new RangedUri(urlText, rangeStart, rangeLength); } + protected ProgramInformation parseProgramInformation(XmlPullParser xpp) throws IOException, XmlPullParserException { + String title = null; + String source = null; + String copyright = null; + String moreInformationURL = parseString(xpp, "moreInformationURL", null); + String lang = parseString(xpp, "lang", null); + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "Title")) { + title = xpp.nextText(); + } else if (XmlPullParserUtil.isStartTag(xpp, "Source")) { + source = xpp.nextText(); + } else if (XmlPullParserUtil.isStartTag(xpp, "Copyright")) { + copyright = xpp.nextText(); + } else { + maybeSkipTag(xpp); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "ProgramInformation")); + return new ProgramInformation(title, source, copyright, moreInformationURL, lang); + } + // AudioChannelConfiguration parsing. protected int parseAudioChannelConfiguration(XmlPullParser xpp) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java new file mode 100644 index 0000000000..71b4af3a21 --- /dev/null +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source.dash.manifest; + +import com.google.android.exoplayer2.util.Util; + +/** + * A parsed ProgramInformation element. + */ +public class ProgramInformation { + /** + * The title for the media presentation. + */ + public final String title; + + /** + * Information about the original source of the media presentation. + */ + public final String source; + + /** + * A copyright statement for the media presentation. + */ + public final String copyright; + + /** + * A URL that provides more information about the media presentation. + */ + public final String moreInformationURL; + + /** + * Declares the language code(s) for this ProgramInformation. + */ + public final String lang; + + public ProgramInformation(String title, String source, String copyright, String moreInformationURL, String lang) { + this.title = title; + this.source = source; + this.copyright = copyright; + this.moreInformationURL = moreInformationURL; + this.lang = lang; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ProgramInformation)) { + return false; + } + ProgramInformation other = (ProgramInformation) obj; + return Util.areEqual(this.title, other.title) + && Util.areEqual(this.source, other.source) + && Util.areEqual(this.copyright, other.copyright) + && Util.areEqual(this.moreInformationURL, other.moreInformationURL) + && Util.areEqual(this.lang, other.lang); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (title != null ? title.hashCode() : 0); + result = 31 * result + (source != null ? source.hashCode() : 0); + result = 31 * result + (copyright != null ? copyright.hashCode() : 0); + result = 31 * result + (moreInformationURL != null ? moreInformationURL.hashCode() : 0); + result = 31 * result + (lang != null ? lang.hashCode() : 0); + return result; + } +} diff --git a/library/dash/src/test/assets/sample_mpd_1 b/library/dash/src/test/assets/sample_mpd_1 index 07bcdd4f50..ccd3ab4dd6 100644 --- a/library/dash/src/test/assets/sample_mpd_1 +++ b/library/dash/src/test/assets/sample_mpd_1 @@ -9,6 +9,12 @@ xmlns="urn:mpeg:DASH:schema:MPD:2011" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" yt:earliestMediaSequence="1266404" > + + MediaTitle + MediaSource + MediaCopyright + Date: Wed, 31 Oct 2018 20:37:59 +0000 Subject: [PATCH 037/210] Misc fixes / stylistic consistency changes for merged pull requests --- .../ext/cronet/CronetDataSource.java | 5 +- .../exoplayer2/upstream/HttpDataSource.java | 11 ++- .../DefaultLoadErrorHandlingPolicyTest.java | 6 +- .../source/dash/manifest/DashManifest.java | 74 ++++++++++++++++--- .../dash/manifest/DashManifestParser.java | 58 +++++++++++---- .../dash/manifest/ProgramInformation.java | 40 ++++------ .../dash/manifest/DashManifestParserTest.java | 12 +-- .../dash/manifest/DashManifestTest.java | 13 +++- .../hls/playlist/HlsPlaylistParser.java | 2 +- 9 files changed, 155 insertions(+), 66 deletions(-) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 9f381d0053..af85401100 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -616,10 +616,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { if (responseCode == 307 || responseCode == 308) { exception = new InvalidResponseCodeException( - responseCode, - info.getHttpStatusText(), - info.getAllHeaders(), - currentDataSpec); + responseCode, info.getHttpStatusText(), info.getAllHeaders(), currentDataSpec); operation.open(); return; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index e73901eccb..e3e93bd6fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -295,9 +295,7 @@ public interface HttpDataSource extends DataSource { */ public final int responseCode; - /** - * The HTTP status message. - */ + /** The http status message. */ @Nullable public final String responseMessage; /** @@ -305,6 +303,13 @@ public interface HttpDataSource extends DataSource { */ public final Map> headerFields; + /** @deprecated Use {@link #InvalidResponseCodeException(int, String, Map, DataSpec)}. */ + @Deprecated + public InvalidResponseCodeException( + int responseCode, Map> headerFields, DataSpec dataSpec) { + this(responseCode, /* responseMessage= */ null, headerFields, dataSpec); + } + public InvalidResponseCodeException( int responseCode, @Nullable String responseMessage, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java index 2a35a31e74..5576588857 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicyTest.java @@ -35,7 +35,8 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_blacklist404() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(404, "Not Found", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException( + 404, "Not Found", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)) .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } @@ -43,7 +44,8 @@ public final class DefaultLoadErrorHandlingPolicyTest { @Test public void getBlacklistDurationMsFor_blacklist410() { InvalidResponseCodeException exception = - new InvalidResponseCodeException(410, "Gone", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); + new InvalidResponseCodeException( + 410, "Gone", Collections.emptyMap(), new DataSpec(Uri.EMPTY)); assertThat(getDefaultPolicyBlacklistOutputFor(exception)) .isEqualTo(DefaultLoadErrorHandlingPolicy.DEFAULT_TRACK_BLACKLIST_MS); } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java index 6446909808..3637b80ecb 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.source.dash.manifest; import android.net.Uri; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.FilterableManifest; import com.google.android.exoplayer2.offline.StreamKey; @@ -86,17 +87,56 @@ public class DashManifest implements FilterableManifest { */ public final Uri location; - /** - * The ProgramInformation of this manifest. - */ - public final ProgramInformation programInformation; + /** The {@link ProgramInformation}, or null if not present. */ + @Nullable public final ProgramInformation programInformation; private final List periods; - public DashManifest(long availabilityStartTimeMs, long durationMs, long minBufferTimeMs, - boolean dynamic, long minUpdatePeriodMs, long timeShiftBufferDepthMs, - long suggestedPresentationDelayMs, long publishTimeMs, UtcTimingElement utcTiming, - Uri location, ProgramInformation programInformation, List periods) { + /** + * @deprecated Use {@link #DashManifest(long, long, long, boolean, long, long, long, long, + * ProgramInformation, UtcTimingElement, Uri, List)}. + */ + @Deprecated + public DashManifest( + long availabilityStartTimeMs, + long durationMs, + long minBufferTimeMs, + boolean dynamic, + long minUpdatePeriodMs, + long timeShiftBufferDepthMs, + long suggestedPresentationDelayMs, + long publishTimeMs, + UtcTimingElement utcTiming, + Uri location, + List periods) { + this( + availabilityStartTimeMs, + durationMs, + minBufferTimeMs, + dynamic, + minUpdatePeriodMs, + timeShiftBufferDepthMs, + suggestedPresentationDelayMs, + publishTimeMs, + /* programInformation= */ null, + utcTiming, + location, + periods); + } + + public DashManifest( + long availabilityStartTimeMs, + long durationMs, + long minBufferTimeMs, + boolean dynamic, + long minUpdatePeriodMs, + long timeShiftBufferDepthMs, + long suggestedPresentationDelayMs, + long publishTimeMs, + @Nullable ProgramInformation programInformation, + UtcTimingElement utcTiming, + Uri location, + List periods) { this.availabilityStartTimeMs = availabilityStartTimeMs; this.durationMs = durationMs; this.minBufferTimeMs = minBufferTimeMs; @@ -105,9 +145,9 @@ public class DashManifest implements FilterableManifest { this.timeShiftBufferDepthMs = timeShiftBufferDepthMs; this.suggestedPresentationDelayMs = suggestedPresentationDelayMs; this.publishTimeMs = publishTimeMs; + this.programInformation = programInformation; this.utcTiming = utcTiming; this.location = location; - this.programInformation = programInformation; this.periods = periods == null ? Collections.emptyList() : periods; } @@ -154,9 +194,19 @@ public class DashManifest implements FilterableManifest { } } long newDuration = durationMs != C.TIME_UNSET ? durationMs - shiftMs : C.TIME_UNSET; - return new DashManifest(availabilityStartTimeMs, newDuration, minBufferTimeMs, dynamic, - minUpdatePeriodMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, publishTimeMs, - utcTiming, location, programInformation, copyPeriods); + return new DashManifest( + availabilityStartTimeMs, + newDuration, + minBufferTimeMs, + dynamic, + minUpdatePeriodMs, + timeShiftBufferDepthMs, + suggestedPresentationDelayMs, + publishTimeMs, + programInformation, + utcTiming, + location, + copyPeriods); } private static ArrayList copyAdaptationSets( diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index d048e22b33..f017ae64ad 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -120,9 +120,9 @@ public class DashManifestParser extends DefaultHandler long suggestedPresentationDelayMs = dynamic ? parseDuration(xpp, "suggestedPresentationDelay", C.TIME_UNSET) : C.TIME_UNSET; long publishTimeMs = parseDateTime(xpp, "publishTime", C.TIME_UNSET); + ProgramInformation programInformation = null; UtcTimingElement utcTiming = null; Uri location = null; - ProgramInformation programInformation = null; List periods = new ArrayList<>(); long nextPeriodStartMs = dynamic ? C.TIME_UNSET : 0; @@ -135,12 +135,12 @@ public class DashManifestParser extends DefaultHandler baseUrl = parseBaseUrl(xpp, baseUrl); seenFirstBaseUrl = true; } + } else if (XmlPullParserUtil.isStartTag(xpp, "ProgramInformation")) { + programInformation = parseProgramInformation(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "UTCTiming")) { utcTiming = parseUtcTiming(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "Location")) { location = Uri.parse(xpp.nextText()); - } else if (XmlPullParserUtil.isStartTag(xpp, "ProgramInformation")) { - programInformation = parseProgramInformation(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) { Pair periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs); Period period = periodWithDurationMs.first; @@ -176,18 +176,47 @@ public class DashManifestParser extends DefaultHandler throw new ParserException("No periods found."); } - return buildMediaPresentationDescription(availabilityStartTime, durationMs, minBufferTimeMs, - dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, - publishTimeMs, utcTiming, location, programInformation, periods); + return buildMediaPresentationDescription( + availabilityStartTime, + durationMs, + minBufferTimeMs, + dynamic, + minUpdateTimeMs, + timeShiftBufferDepthMs, + suggestedPresentationDelayMs, + publishTimeMs, + programInformation, + utcTiming, + location, + periods); } - protected DashManifest buildMediaPresentationDescription(long availabilityStartTime, - long durationMs, long minBufferTimeMs, boolean dynamic, long minUpdateTimeMs, - long timeShiftBufferDepthMs, long suggestedPresentationDelayMs, long publishTimeMs, - UtcTimingElement utcTiming, Uri location, ProgramInformation programInformation, List periods) { - return new DashManifest(availabilityStartTime, durationMs, minBufferTimeMs, - dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, - publishTimeMs, utcTiming, location, programInformation, periods); + protected DashManifest buildMediaPresentationDescription( + long availabilityStartTime, + long durationMs, + long minBufferTimeMs, + boolean dynamic, + long minUpdateTimeMs, + long timeShiftBufferDepthMs, + long suggestedPresentationDelayMs, + long publishTimeMs, + ProgramInformation programInformation, + UtcTimingElement utcTiming, + Uri location, + List periods) { + return new DashManifest( + availabilityStartTime, + durationMs, + minBufferTimeMs, + dynamic, + minUpdateTimeMs, + timeShiftBufferDepthMs, + suggestedPresentationDelayMs, + publishTimeMs, + programInformation, + utcTiming, + location, + periods); } protected UtcTimingElement parseUtcTiming(XmlPullParser xpp) { @@ -1001,7 +1030,8 @@ public class DashManifestParser extends DefaultHandler return new RangedUri(urlText, rangeStart, rangeLength); } - protected ProgramInformation parseProgramInformation(XmlPullParser xpp) throws IOException, XmlPullParserException { + protected ProgramInformation parseProgramInformation(XmlPullParser xpp) + throws IOException, XmlPullParserException { String title = null; String source = null; String copyright = null; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java index 71b4af3a21..73e8ef986e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java @@ -17,36 +17,25 @@ package com.google.android.exoplayer2.source.dash.manifest; import com.google.android.exoplayer2.util.Util; -/** - * A parsed ProgramInformation element. - */ +/** A parsed program information element. */ public class ProgramInformation { - /** - * The title for the media presentation. - */ + /** The title for the media presentation. */ public final String title; - /** - * Information about the original source of the media presentation. - */ + /** Information about the original source of the media presentation. */ public final String source; - /** - * A copyright statement for the media presentation. - */ + /** A copyright statement for the media presentation. */ public final String copyright; - /** - * A URL that provides more information about the media presentation. - */ + /** A URL that provides more information about the media presentation. */ public final String moreInformationURL; - /** - * Declares the language code(s) for this ProgramInformation. - */ + /** Declares the language code(s) for this ProgramInformation. */ public final String lang; - public ProgramInformation(String title, String source, String copyright, String moreInformationURL, String lang) { + public ProgramInformation( + String title, String source, String copyright, String moreInformationURL, String lang) { this.title = title; this.source = source; this.copyright = copyright; @@ -56,15 +45,18 @@ public class ProgramInformation { @Override public boolean equals(Object obj) { - if (!(obj instanceof ProgramInformation)) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { return false; } ProgramInformation other = (ProgramInformation) obj; return Util.areEqual(this.title, other.title) - && Util.areEqual(this.source, other.source) - && Util.areEqual(this.copyright, other.copyright) - && Util.areEqual(this.moreInformationURL, other.moreInformationURL) - && Util.areEqual(this.lang, other.lang); + && Util.areEqual(this.source, other.source) + && Util.areEqual(this.copyright, other.copyright) + && Util.areEqual(this.moreInformationURL, other.moreInformationURL) + && Util.areEqual(this.lang, other.lang); } @Override diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java index 3183e3c672..a1693f6985 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java @@ -25,7 +25,6 @@ import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.junit.Test; @@ -156,11 +155,14 @@ public class DashManifestParserTest { @Test public void testParseMediaPresentationDescriptionCanParseProgramInformation() throws IOException { DashManifestParser parser = new DashManifestParser(); - DashManifest mpd = parser.parse(Uri.parse("Https://example.com/test.mpd"), + DashManifest mpd = + parser.parse( + Uri.parse("Https://example.com/test.mpd"), TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_MPD_1)); - ProgramInformation programInformation = new ProgramInformation("MediaTitle", "MediaSource", - "MediaCopyright", "www.example.com", "enUs"); - assertThat(programInformation).isEqualTo(mpd.programInformation); + ProgramInformation expectedProgramInformation = + new ProgramInformation( + "MediaTitle", "MediaSource", "MediaCopyright", "www.example.com", "enUs"); + assertThat(mpd.programInformation).isEqualTo(expectedProgramInformation); } @Test diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java index 17a96fded3..0d08df42e9 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java @@ -219,7 +219,18 @@ public class DashManifestTest { private static DashManifest newDashManifest(int duration, Period... periods) { return new DashManifest( - 0, duration, 1, false, 2, 3, 4, 12345, DUMMY_UTC_TIMING, Uri.EMPTY, null, Arrays.asList(periods)); + /* availabilityStartTimeMs= */ 0, + duration, + /* minBufferTimeMs= */ 1, + /* dynamic= */ false, + /* minUpdatePeriodMs= */ 2, + /* timeShiftBufferDepthMs= */ 3, + /* suggestedPresentationDelayMs= */ 4, + /* publishTimeMs= */ 12345, + /* programInformation= */ null, + DUMMY_UTC_TIMING, + Uri.EMPTY, + Arrays.asList(periods)); } private static Period newPeriod(String id, int startMs, AdaptationSet... adaptationSets) { diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index bd2bea0197..65f4796187 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -341,7 +341,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser Date: Wed, 31 Oct 2018 20:40:57 +0000 Subject: [PATCH 038/210] Fix nullability --- .../exoplayer2/source/dash/manifest/ProgramInformation.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java index 73e8ef986e..e3072c86bd 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/ProgramInformation.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.dash.manifest; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.util.Util; /** A parsed program information element. */ @@ -44,7 +45,7 @@ public class ProgramInformation { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } From 7a3447fe9c4f2b3e31b7f720fb4305cfac54bb14 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 02:06:32 -0700 Subject: [PATCH 039/210] Simplify some buffered position related code. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215704344 --- .../exoplayer2/ExoPlayerImplInternal.java | 27 ++++++++++++------- .../android/exoplayer2/MediaPeriodHolder.java | 13 +++------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index fbf1536f57..7f41719d1d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -504,10 +504,8 @@ import java.util.Collections; // Update the buffered position and total buffered duration. MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod(); - playbackInfo.bufferedPositionUs = - loadingPeriod.getBufferedPositionUs(/* convertEosToDuration= */ true); - playbackInfo.totalBufferedDurationUs = - playbackInfo.bufferedPositionUs - loadingPeriod.toPeriodTime(rendererPositionUs); + playbackInfo.bufferedPositionUs = loadingPeriod.getBufferedPositionUs(); + playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs(); } private void doSomeWork() throws ExoPlaybackException, IOException { @@ -1103,12 +1101,10 @@ import java.util.Collections; } // Renderers are ready and we're loading. Ask the LoadControl whether to transition. MediaPeriodHolder loadingHolder = queue.getLoadingPeriod(); - long bufferedPositionUs = loadingHolder.getBufferedPositionUs(!loadingHolder.info.isFinal); - return bufferedPositionUs == C.TIME_END_OF_SOURCE + boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal; + return bufferedToEnd || loadControl.shouldStartPlayback( - bufferedPositionUs - loadingHolder.toPeriodTime(rendererPositionUs), - mediaClock.getPlaybackParameters().speed, - rebuffering); + getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering); } private boolean isTimelineReady() { @@ -1590,7 +1586,7 @@ import java.util.Collections; return; } long bufferedDurationUs = - nextLoadPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs); + getTotalBufferedDurationUs(/* bufferedPositionInLoadingPeriodUs= */ nextLoadPositionUs); boolean continueLoading = loadControl.shouldContinueLoading( bufferedDurationUs, mediaClock.getPlaybackParameters().speed); @@ -1699,6 +1695,17 @@ import java.util.Collections; } } + private long getTotalBufferedDurationUs() { + return getTotalBufferedDurationUs(playbackInfo.bufferedPositionUs); + } + + private long getTotalBufferedDurationUs(long bufferedPositionInLoadingPeriodUs) { + MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod(); + return loadingPeriodHolder == null + ? 0 + : bufferedPositionInLoadingPeriodUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs); + } + private void updateLoadControlTrackSelection( TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index 4941b4efc6..5925c8f383 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -117,23 +117,18 @@ import com.google.android.exoplayer2.util.Log; } /** - * Returns the buffered position in microseconds. If the period is buffered to the end then - * {@link C#TIME_END_OF_SOURCE} is returned unless {@code convertEosToDuration} is true, in which - * case the period duration is returned. + * Returns the buffered position in microseconds. If the period is buffered to the end, then the + * period duration is returned. * - * @param convertEosToDuration Whether to return the period duration rather than - * {@link C#TIME_END_OF_SOURCE} if the period is fully buffered. * @return The buffered position in microseconds. */ - public long getBufferedPositionUs(boolean convertEosToDuration) { + public long getBufferedPositionUs() { if (!prepared) { return info.startPositionUs; } long bufferedPositionUs = hasEnabledTracks ? mediaPeriod.getBufferedPositionUs() : C.TIME_END_OF_SOURCE; - return bufferedPositionUs == C.TIME_END_OF_SOURCE && convertEosToDuration - ? info.durationUs - : bufferedPositionUs; + return bufferedPositionUs == C.TIME_END_OF_SOURCE ? info.durationUs : bufferedPositionUs; } public long getNextLoadPositionUs() { From eef7e28ab2b42c6168dd378f24d4857b46a57244 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 02:07:17 -0700 Subject: [PATCH 040/210] Add test action to wait for an isLoading change. This allows to wait until loading started or finished. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215704424 --- .../android/exoplayer2/testutil/Action.java | 50 +++++++++++++++++++ .../exoplayer2/testutil/ActionSchedule.java | 11 ++++ 2 files changed, 61 insertions(+) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java index 14d62e85c8..c988c0c172 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java @@ -686,6 +686,56 @@ public abstract class Action { } } + /** + * Waits for a specified loading state, returning either immediately or after a call to {@link + * Player.EventListener#onLoadingChanged(boolean)}. + */ + public static final class WaitForIsLoading extends Action { + + private final boolean targetIsLoading; + + /** + * @param tag A tag to use for logging. + * @param targetIsLoading The loading state to wait for. + */ + public WaitForIsLoading(String tag, boolean targetIsLoading) { + super(tag, "WaitForIsLoading"); + this.targetIsLoading = targetIsLoading; + } + + @Override + protected void doActionAndScheduleNextImpl( + final SimpleExoPlayer player, + final DefaultTrackSelector trackSelector, + final Surface surface, + final HandlerWrapper handler, + final ActionNode nextAction) { + if (nextAction == null) { + return; + } + if (targetIsLoading == player.isLoading()) { + nextAction.schedule(player, trackSelector, surface, handler); + } else { + player.addListener( + new Player.EventListener() { + @Override + public void onLoadingChanged(boolean isLoading) { + if (targetIsLoading == isLoading) { + player.removeListener(this); + nextAction.schedule(player, trackSelector, surface, handler); + } + } + }); + } + } + + @Override + protected void doActionImpl( + SimpleExoPlayer player, DefaultTrackSelector trackSelector, Surface surface) { + // Not triggered. + } + } + /** * Waits for {@link Player.EventListener#onSeekProcessed()}. */ diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index 54d97fb905..71f5fdeae1 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -41,6 +41,7 @@ import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled; import com.google.android.exoplayer2.testutil.Action.SetVideoSurface; import com.google.android.exoplayer2.testutil.Action.Stop; import com.google.android.exoplayer2.testutil.Action.ThrowPlaybackException; +import com.google.android.exoplayer2.testutil.Action.WaitForIsLoading; import com.google.android.exoplayer2.testutil.Action.WaitForPlaybackState; import com.google.android.exoplayer2.testutil.Action.WaitForPositionDiscontinuity; import com.google.android.exoplayer2.testutil.Action.WaitForSeekProcessed; @@ -414,6 +415,16 @@ public final class ActionSchedule { return apply(new WaitForPlaybackState(tag, targetPlaybackState)); } + /** + * Schedules a delay until {@code player.isLoading()} changes to the specified value. + * + * @param targetIsLoading The target value of {@code player.isLoading()}. + * @return The builder, for convenience. + */ + public Builder waitForIsLoading(boolean targetIsLoading) { + return apply(new WaitForIsLoading(tag, targetIsLoading)); + } + /** * Schedules a {@link Runnable} to be executed. * From c04cf3096090447ba5ebf75cae643374b2efe84d Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 31 Oct 2018 22:04:37 +0000 Subject: [PATCH 041/210] Update release notes --- RELEASENOTES.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 16b65aa09a..b0fc16b86d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,15 +4,19 @@ * Improve initial bandwidth meter estimates using the current country and network type. -* SubRip: Add support for alignment tags, and remove tags from the displayed - captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). +* IMA extension: + * For preroll to live stream transitions, project forward the + loading position to avoid being behind the live window. + * Let apps specify whether to focus the skip button on ATV + ([#5019](https://github.com/google/ExoPlayer/issues/5019)). * MP3: * Support seeking based on MLLT metadata ([#3241](https://github.com/google/ExoPlayer/issues/3241)). * Fix handling of streams with appending data ([#4954](https://github.com/google/ExoPlayer/issues/4954)). -* IMA extension: For preroll to live stream transitions, project forward the - loading position to avoid being behind the live window. +* DASH: Parse ProgramInformation element if present in the manifest. +* SubRip: Add support for alignment tags, and remove tags from the displayed + captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). * Fix issue with blind seeking to windows with non-zero offset in a `ConcatenatingMediaSource` ([#4873](https://github.com/google/ExoPlayer/issues/4873)). @@ -26,9 +30,6 @@ ([#4532](https://github.com/google/ExoPlayer/issues/4532)). * Suppress spurious "references unknown class member" shrinking warning ([#4890](https://github.com/google/ExoPlayer/issues/4890)). -* IMA extension: - * Let apps specify whether to focus the skip button on ATV - ([#5019](https://github.com/google/ExoPlayer/issues/5019)). * Fix issue where a `NullPointerException` is thrown when removing an unprepared media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)). From 4d9044d69019296acaca499bdba9d9e30df310cf Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 31 Oct 2018 22:18:31 +0000 Subject: [PATCH 042/210] Fix release notes typo --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b0fc16b86d..a67de32fea 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -12,7 +12,7 @@ * MP3: * Support seeking based on MLLT metadata ([#3241](https://github.com/google/ExoPlayer/issues/3241)). - * Fix handling of streams with appending data + * Fix handling of streams with appended data ([#4954](https://github.com/google/ExoPlayer/issues/4954)). * DASH: Parse ProgramInformation element if present in the manifest. * SubRip: Add support for alignment tags, and remove tags from the displayed From bbd82cf5da8072a0c67f5a422c426ef2d71e315e Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 2 Oct 2018 05:52:56 -0700 Subject: [PATCH 043/210] Add BasePlayer to avoid code duplication for common convenience methods. A lot of methods just forward to other methods and there is no conceivable way a player should implement it another way. Moving these methods to a base player class allows to remove duplicated code across our player implementations. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215374192 --- .../exoplayer2/ext/cast/CastPlayer.java | 75 +--------- .../exoplayer2/ext/ima/FakePlayer.java | 12 -- .../google/android/exoplayer2/BasePlayer.java | 132 ++++++++++++++++++ .../android/exoplayer2/ExoPlayerImpl.java | 78 +---------- .../android/exoplayer2/SimpleExoPlayer.java | 70 +--------- .../exoplayer2/testutil/StubExoPlayer.java | 59 +------- 6 files changed, 140 insertions(+), 286 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 97b05e3f0a..6cf6309796 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ext.cast; import android.os.Looper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.google.android.exoplayer2.BasePlayer; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.PlaybackParameters; @@ -31,7 +32,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; -import com.google.android.exoplayer2.util.Util; import com.google.android.gms.cast.CastStatusCodes; import com.google.android.gms.cast.MediaInfo; import com.google.android.gms.cast.MediaQueueItem; @@ -62,7 +62,7 @@ import java.util.concurrent.CopyOnWriteArraySet; * *

Methods should be called on the application's main thread.

*/ -public final class CastPlayer implements Player { +public final class CastPlayer extends BasePlayer { /** * Listener of changes in the cast session availability. @@ -95,7 +95,6 @@ public final class CastPlayer implements Player { private final CastContext castContext; // TODO: Allow custom implementations of CastTimelineTracker. private final CastTimelineTracker timelineTracker; - private final Timeline.Window window; private final Timeline.Period period; private RemoteMediaClient remoteMediaClient; @@ -128,7 +127,6 @@ public final class CastPlayer implements Player { public CastPlayer(CastContext castContext) { this.castContext = castContext; timelineTracker = new CastTimelineTracker(); - window = new Timeline.Window(); period = new Timeline.Period(); statusListener = new StatusListener(); seekResultCallback = new SeekResultCallback(); @@ -341,21 +339,6 @@ public final class CastPlayer implements Player { return playWhenReady; } - @Override - public void seekToDefaultPosition() { - seekTo(0); - } - - @Override - public void seekToDefaultPosition(int windowIndex) { - seekTo(windowIndex, 0); - } - - @Override - public void seekTo(long positionMs) { - seekTo(getCurrentWindowIndex(), positionMs); - } - @Override public void seekTo(int windowIndex, long positionMs) { MediaStatus mediaStatus = getMediaStatus(); @@ -392,11 +375,6 @@ public final class CastPlayer implements Player { return PlaybackParameters.DEFAULT; } - @Override - public void stop() { - stop(/* reset= */ false); - } - @Override public void stop(boolean reset) { playbackState = STATE_IDLE; @@ -486,32 +464,11 @@ public final class CastPlayer implements Player { return pendingSeekWindowIndex != C.INDEX_UNSET ? pendingSeekWindowIndex : currentWindowIndex; } - @Override - public int getNextWindowIndex() { - return currentTimeline.isEmpty() ? C.INDEX_UNSET - : currentTimeline.getNextWindowIndex(getCurrentWindowIndex(), repeatMode, false); - } - - @Override - public int getPreviousWindowIndex() { - return currentTimeline.isEmpty() ? C.INDEX_UNSET - : currentTimeline.getPreviousWindowIndex(getCurrentWindowIndex(), repeatMode, false); - } - - @Override - public @Nullable Object getCurrentTag() { - int windowIndex = getCurrentWindowIndex(); - return windowIndex >= currentTimeline.getWindowCount() - ? null - : currentTimeline.getWindow(windowIndex, window, /* setTag= */ true).tag; - } - // TODO: Fill the cast timeline information with ProgressListener's duration updates. // See [Internal: b/65152553]. @Override public long getDuration() { - return currentTimeline.isEmpty() ? C.TIME_UNSET - : currentTimeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + return getContentDuration(); } @Override @@ -528,15 +485,6 @@ public final class CastPlayer implements Player { return getCurrentPosition(); } - @Override - public int getBufferedPercentage() { - long position = getBufferedPosition(); - long duration = getDuration(); - return position == C.TIME_UNSET || duration == C.TIME_UNSET - ? 0 - : duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100); - } - @Override public long getTotalBufferedDuration() { long bufferedPosition = getBufferedPosition(); @@ -546,18 +494,6 @@ public final class CastPlayer implements Player { : bufferedPosition - currentPosition; } - @Override - public boolean isCurrentWindowDynamic() { - return !currentTimeline.isEmpty() - && currentTimeline.getWindow(getCurrentWindowIndex(), window).isDynamic; - } - - @Override - public boolean isCurrentWindowSeekable() { - return !currentTimeline.isEmpty() - && currentTimeline.getWindow(getCurrentWindowIndex(), window).isSeekable; - } - @Override public boolean isPlayingAd() { return false; @@ -573,11 +509,6 @@ public final class CastPlayer implements Player { return C.INDEX_UNSET; } - @Override - public long getContentDuration() { - return getDuration(); - } - @Override public boolean isLoading() { return false; diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java index 0c35c9b66d..b8024d6534 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java @@ -26,7 +26,6 @@ import java.util.ArrayList; /* package */ final class FakePlayer extends StubExoPlayer { private final ArrayList listeners; - private final Timeline.Window window; private final Timeline.Period period; private final Timeline timeline; @@ -41,7 +40,6 @@ import java.util.ArrayList; public FakePlayer() { listeners = new ArrayList<>(); - window = new Timeline.Window(); period = new Timeline.Period(); state = Player.STATE_IDLE; playWhenReady = true; @@ -151,16 +149,6 @@ import java.util.ArrayList; return 0; } - @Override - public int getNextWindowIndex() { - return C.INDEX_UNSET; - } - - @Override - public int getPreviousWindowIndex() { - return C.INDEX_UNSET; - } - @Override public long getDuration() { if (timeline.isEmpty()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java new file mode 100644 index 0000000000..6ff0853d9b --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import android.support.annotation.Nullable; +import com.google.android.exoplayer2.util.Util; + +/** Abstract base {@link Player} which implements common implementation independent methods. */ +public abstract class BasePlayer implements Player { + + protected final Timeline.Window window; + + public BasePlayer() { + window = new Timeline.Window(); + } + + @Override + public final void seekToDefaultPosition() { + seekToDefaultPosition(getCurrentWindowIndex()); + } + + @Override + public final void seekToDefaultPosition(int windowIndex) { + seekTo(windowIndex, /* positionMs= */ C.TIME_UNSET); + } + + @Override + public final void seekTo(long positionMs) { + seekTo(getCurrentWindowIndex(), positionMs); + } + + @Override + public final boolean hasPrevious() { + return getPreviousWindowIndex() != C.INDEX_UNSET; + } + + @Override + public final void previous() { + int previousWindowIndex = getPreviousWindowIndex(); + if (previousWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(previousWindowIndex); + } + } + + @Override + public final boolean hasNext() { + return getNextWindowIndex() != C.INDEX_UNSET; + } + + @Override + public final void next() { + int nextWindowIndex = getPreviousWindowIndex(); + if (nextWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(nextWindowIndex); + } + } + + @Override + public final void stop() { + stop(/* reset= */ false); + } + + @Override + public final int getNextWindowIndex() { + Timeline timeline = getCurrentTimeline(); + return timeline.isEmpty() + ? C.INDEX_UNSET + : timeline.getNextWindowIndex( + getCurrentWindowIndex(), getRepeatMode(), getShuffleModeEnabled()); + } + + @Override + public final int getPreviousWindowIndex() { + Timeline timeline = getCurrentTimeline(); + return timeline.isEmpty() + ? C.INDEX_UNSET + : timeline.getPreviousWindowIndex( + getCurrentWindowIndex(), getRepeatMode(), getShuffleModeEnabled()); + } + + @Override + @Nullable + public final Object getCurrentTag() { + int windowIndex = getCurrentWindowIndex(); + Timeline timeline = getCurrentTimeline(); + return windowIndex >= timeline.getWindowCount() + ? null + : timeline.getWindow(windowIndex, window, /* setTag= */ true).tag; + } + + @Override + public final int getBufferedPercentage() { + long position = getBufferedPosition(); + long duration = getDuration(); + return position == C.TIME_UNSET || duration == C.TIME_UNSET + ? 0 + : duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100); + } + + @Override + public final boolean isCurrentWindowDynamic() { + Timeline timeline = getCurrentTimeline(); + return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic; + } + + @Override + public final boolean isCurrentWindowSeekable() { + Timeline timeline = getCurrentTimeline(); + return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; + } + + @Override + public final long getContentDuration() { + Timeline timeline = getCurrentTimeline(); + return timeline.isEmpty() + ? C.TIME_UNSET + : timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 04bc1c611d..ffdadb78f7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -40,10 +40,8 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -/** - * An {@link ExoPlayer} implementation. Instances can be obtained from {@link ExoPlayerFactory}. - */ -/* package */ final class ExoPlayerImpl implements ExoPlayer { +/** An {@link ExoPlayer} implementation. Instances can be obtained from {@link ExoPlayerFactory}. */ +/* package */ final class ExoPlayerImpl extends BasePlayer implements ExoPlayer { private static final String TAG = "ExoPlayerImpl"; @@ -61,7 +59,6 @@ import java.util.concurrent.CopyOnWriteArraySet; private final ExoPlayerImplInternal internalPlayer; private final Handler internalPlayerHandler; private final CopyOnWriteArraySet listeners; - private final Timeline.Window window; private final Timeline.Period period; private final ArrayDeque pendingPlaybackInfoUpdates; @@ -118,7 +115,6 @@ import java.util.concurrent.CopyOnWriteArraySet; new RendererConfiguration[renderers.length], new TrackSelection[renderers.length], null); - window = new Timeline.Window(); period = new Timeline.Period(); playbackParameters = PlaybackParameters.DEFAULT; seekParameters = SeekParameters.DEFAULT; @@ -293,21 +289,6 @@ import java.util.concurrent.CopyOnWriteArraySet; return playbackInfo.isLoading; } - @Override - public void seekToDefaultPosition() { - seekToDefaultPosition(getCurrentWindowIndex()); - } - - @Override - public void seekToDefaultPosition(int windowIndex) { - seekTo(windowIndex, C.TIME_UNSET); - } - - @Override - public void seekTo(long positionMs) { - seekTo(getCurrentWindowIndex(), positionMs); - } - @Override public void seekTo(int windowIndex, long positionMs) { Timeline timeline = playbackInfo.timeline; @@ -377,19 +358,6 @@ import java.util.concurrent.CopyOnWriteArraySet; return seekParameters; } - @Override - public @Nullable Object getCurrentTag() { - int windowIndex = getCurrentWindowIndex(); - return windowIndex >= playbackInfo.timeline.getWindowCount() - ? null - : playbackInfo.timeline.getWindow(windowIndex, window, /* setTag= */ true).tag; - } - - @Override - public void stop() { - stop(/* reset= */ false); - } - @Override public void stop(boolean reset) { if (reset) { @@ -494,20 +462,6 @@ import java.util.concurrent.CopyOnWriteArraySet; } } - @Override - public int getNextWindowIndex() { - Timeline timeline = playbackInfo.timeline; - return timeline.isEmpty() ? C.INDEX_UNSET - : timeline.getNextWindowIndex(getCurrentWindowIndex(), repeatMode, shuffleModeEnabled); - } - - @Override - public int getPreviousWindowIndex() { - Timeline timeline = playbackInfo.timeline; - return timeline.isEmpty() ? C.INDEX_UNSET - : timeline.getPreviousWindowIndex(getCurrentWindowIndex(), repeatMode, shuffleModeEnabled); - } - @Override public long getDuration() { if (isPlayingAd()) { @@ -540,32 +494,11 @@ import java.util.concurrent.CopyOnWriteArraySet; return getContentBufferedPosition(); } - @Override - public int getBufferedPercentage() { - long position = getBufferedPosition(); - long duration = getDuration(); - return position == C.TIME_UNSET || duration == C.TIME_UNSET - ? 0 - : (duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100)); - } - @Override public long getTotalBufferedDuration() { return Math.max(0, C.usToMs(playbackInfo.totalBufferedDurationUs)); } - @Override - public boolean isCurrentWindowDynamic() { - Timeline timeline = playbackInfo.timeline; - return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic; - } - - @Override - public boolean isCurrentWindowSeekable() { - Timeline timeline = playbackInfo.timeline; - return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; - } - @Override public boolean isPlayingAd() { return !shouldMaskPosition() && playbackInfo.periodId.isAd(); @@ -581,13 +514,6 @@ import java.util.concurrent.CopyOnWriteArraySet; return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; } - @Override - public long getContentDuration() { - return playbackInfo.timeline.isEmpty() - ? C.TIME_UNSET - : playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); - } - @Override public long getContentPosition() { if (isPlayingAd()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 39f1655ab5..8517556887 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -64,7 +64,7 @@ import java.util.concurrent.CopyOnWriteArraySet; * be obtained from {@link ExoPlayerFactory}. */ @TargetApi(16) -public class SimpleExoPlayer +public class SimpleExoPlayer extends BasePlayer implements ExoPlayer, Player.AudioComponent, Player.VideoComponent, Player.TextComponent { /** @deprecated Use {@link com.google.android.exoplayer2.video.VideoListener}. */ @@ -927,27 +927,6 @@ public class SimpleExoPlayer return player.isLoading(); } - @Override - public void seekToDefaultPosition() { - verifyApplicationThread(); - analyticsCollector.notifySeekStarted(); - player.seekToDefaultPosition(); - } - - @Override - public void seekToDefaultPosition(int windowIndex) { - verifyApplicationThread(); - analyticsCollector.notifySeekStarted(); - player.seekToDefaultPosition(windowIndex); - } - - @Override - public void seekTo(long positionMs) { - verifyApplicationThread(); - analyticsCollector.notifySeekStarted(); - player.seekTo(positionMs); - } - @Override public void seekTo(int windowIndex, long positionMs) { verifyApplicationThread(); @@ -979,17 +958,6 @@ public class SimpleExoPlayer return player.getSeekParameters(); } - @Override - public @Nullable Object getCurrentTag() { - verifyApplicationThread(); - return player.getCurrentTag(); - } - - @Override - public void stop() { - stop(/* reset= */ false); - } - @Override public void stop(boolean reset) { verifyApplicationThread(); @@ -1092,18 +1060,6 @@ public class SimpleExoPlayer return player.getCurrentWindowIndex(); } - @Override - public int getNextWindowIndex() { - verifyApplicationThread(); - return player.getNextWindowIndex(); - } - - @Override - public int getPreviousWindowIndex() { - verifyApplicationThread(); - return player.getPreviousWindowIndex(); - } - @Override public long getDuration() { verifyApplicationThread(); @@ -1122,30 +1078,12 @@ public class SimpleExoPlayer return player.getBufferedPosition(); } - @Override - public int getBufferedPercentage() { - verifyApplicationThread(); - return player.getBufferedPercentage(); - } - @Override public long getTotalBufferedDuration() { verifyApplicationThread(); return player.getTotalBufferedDuration(); } - @Override - public boolean isCurrentWindowDynamic() { - verifyApplicationThread(); - return player.isCurrentWindowDynamic(); - } - - @Override - public boolean isCurrentWindowSeekable() { - verifyApplicationThread(); - return player.isCurrentWindowSeekable(); - } - @Override public boolean isPlayingAd() { verifyApplicationThread(); @@ -1164,12 +1102,6 @@ public class SimpleExoPlayer return player.getCurrentAdIndexInAdGroup(); } - @Override - public long getContentDuration() { - verifyApplicationThread(); - return player.getContentDuration(); - } - @Override public long getContentPosition() { verifyApplicationThread(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index f1349c1158..156b573df8 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -16,7 +16,7 @@ package com.google.android.exoplayer2.testutil; import android.os.Looper; -import android.support.annotation.Nullable; +import com.google.android.exoplayer2.BasePlayer; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.PlaybackParameters; @@ -32,7 +32,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; * An abstract {@link ExoPlayer} implementation that throws {@link UnsupportedOperationException} * from every method. */ -public abstract class StubExoPlayer implements ExoPlayer { +public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { @Override public AudioComponent getAudioComponent() { @@ -129,21 +129,6 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public void seekToDefaultPosition() { - throw new UnsupportedOperationException(); - } - - @Override - public void seekToDefaultPosition(int windowIndex) { - throw new UnsupportedOperationException(); - } - - @Override - public void seekTo(long positionMs) { - throw new UnsupportedOperationException(); - } - @Override public void seekTo(int windowIndex, long positionMs) { throw new UnsupportedOperationException(); @@ -169,16 +154,6 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public @Nullable Object getCurrentTag() { - throw new UnsupportedOperationException(); - } - - @Override - public void stop() { - throw new UnsupportedOperationException(); - } - @Override public void stop(boolean resetStateAndPosition) { throw new UnsupportedOperationException(); @@ -248,16 +223,6 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public int getNextWindowIndex() { - throw new UnsupportedOperationException(); - } - - @Override - public int getPreviousWindowIndex() { - throw new UnsupportedOperationException(); - } - @Override public long getDuration() { throw new UnsupportedOperationException(); @@ -273,26 +238,11 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public int getBufferedPercentage() { - throw new UnsupportedOperationException(); - } - @Override public long getTotalBufferedDuration() { throw new UnsupportedOperationException(); } - @Override - public boolean isCurrentWindowDynamic() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isCurrentWindowSeekable() { - throw new UnsupportedOperationException(); - } - @Override public boolean isPlayingAd() { throw new UnsupportedOperationException(); @@ -308,11 +258,6 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public long getContentDuration() { - throw new UnsupportedOperationException(); - } - @Override public long getContentPosition() { throw new UnsupportedOperationException(); From e4c20aa3de4878110cf1f2b87cf8e8bd64da6684 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 26 Sep 2018 03:27:52 -0700 Subject: [PATCH 044/210] Add convenience methods player.next() and player.previous() This simplifies code skipping items in a playlist programatically. Issue:#4863 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214580742 --- RELEASENOTES.md | 7 +++-- .../exoplayer2/ext/cast/CastPlayer.java | 26 ++++++++++++++++ .../android/exoplayer2/ExoPlayerImpl.java | 26 ++++++++++++++++ .../com/google/android/exoplayer2/Player.java | 26 ++++++++++++++++ .../android/exoplayer2/SimpleExoPlayer.java | 30 +++++++++++++++++++ .../exoplayer2/testutil/StubExoPlayer.java | 20 +++++++++++++ 6 files changed, 133 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a67de32fea..194a49db88 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,11 +2,14 @@ ### 2.9.1 ### +* Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext` + and `Player.hasPrevious` + ([#4863](https://github.com/google/ExoPlayer/issues/4863)). * Improve initial bandwidth meter estimates using the current country and network type. * IMA extension: - * For preroll to live stream transitions, project forward the - loading position to avoid being behind the live window. + * For preroll to live stream transitions, project forward the loading position + to avoid being behind the live window. * Let apps specify whether to focus the skip button on ATV ([#5019](https://github.com/google/ExoPlayer/issues/5019)). * MP3: diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 6cf6309796..1573401521 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -365,6 +365,32 @@ public final class CastPlayer extends BasePlayer { } } + @Override + public boolean hasPrevious() { + return getPreviousWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void previous() { + int previousWindowIndex = getPreviousWindowIndex(); + if (previousWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(previousWindowIndex); + } + } + + @Override + public boolean hasNext() { + return getNextWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void next() { + int nextWindowIndex = getPreviousWindowIndex(); + if (nextWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(nextWindowIndex); + } + } + @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { // Unsupported by the RemoteMediaClient API. Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index ffdadb78f7..fb27452f89 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -329,6 +329,32 @@ import java.util.concurrent.CopyOnWriteArraySet; } } + @Override + public boolean hasPrevious() { + return getPreviousWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void previous() { + int previousWindowIndex = getPreviousWindowIndex(); + if (previousWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(previousWindowIndex); + } + } + + @Override + public boolean hasNext() { + return getNextWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void next() { + int nextWindowIndex = getPreviousWindowIndex(); + if (nextWindowIndex != C.INDEX_UNSET) { + seekToDefaultPosition(nextWindowIndex); + } + } + @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { if (playbackParameters == null) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 83014c6b10..16f8aa2878 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -659,6 +659,32 @@ public interface Player { */ void seekTo(int windowIndex, long positionMs); + /** + * Returns whether a previous window exists, which may depend on the current repeat mode and + * whether shuffle mode is enabled. + */ + boolean hasPrevious(); + + /** + * Seeks to the default position of the previous window in the timeline, which may depend on the + * current repeat mode and whether shuffle mode is enabled. Does nothing if {@link #hasPrevious()} + * is {@code false}. + */ + void previous(); + + /** + * Returns whether a next window exists, which may depend on the current repeat mode and whether + * shuffle mode is enabled. + */ + boolean hasNext(); + + /** + * Seeks to the default position of the next window in the timeline, which may depend on the + * current repeat mode and whether shuffle mode is enabled. Does nothing if {@link #hasNext()} is + * {@code false}. + */ + void next(); + /** * Attempts to set the playback parameters. Passing {@code null} sets the parameters to the * default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 8517556887..36fd0868f9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -934,6 +934,36 @@ public class SimpleExoPlayer extends BasePlayer player.seekTo(windowIndex, positionMs); } + @Override + public boolean hasPrevious() { + verifyApplicationThread(); + return getPreviousWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void previous() { + verifyApplicationThread(); + if (hasPrevious()) { + analyticsCollector.notifySeekStarted(); + player.previous(); + } + } + + @Override + public boolean hasNext() { + verifyApplicationThread(); + return getNextWindowIndex() != C.INDEX_UNSET; + } + + @Override + public void next() { + verifyApplicationThread(); + if (hasNext()) { + analyticsCollector.notifySeekStarted(); + player.next(); + } + } + @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { verifyApplicationThread(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 156b573df8..d1236a7a2e 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -134,6 +134,26 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } + @Override + public boolean hasPrevious() { + throw new UnsupportedOperationException(); + } + + @Override + public void previous() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasNext() { + throw new UnsupportedOperationException(); + } + + @Override + public void next() { + throw new UnsupportedOperationException(); + } + @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { throw new UnsupportedOperationException(); From 7876999ae7a2b723edde2adc0317e94c2e8e65c0 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 1 Nov 2018 00:58:32 -0700 Subject: [PATCH 045/210] Fix extended service number calculation ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219597894 --- .../google/android/exoplayer2/text/cea/Cea708Decoder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java index a95f1de738..b3be88b851 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java @@ -272,7 +272,10 @@ public final class Cea708Decoder extends CeaDecoder { if (serviceNumber == 7) { // extended service numbers serviceBlockPacket.skipBits(2); - serviceNumber += serviceBlockPacket.readBits(6); + serviceNumber = serviceBlockPacket.readBits(6); + if (serviceNumber < 7) { + Log.w(TAG, "Invalid extended service number: " + serviceNumber); + } } // Ignore packets in which blockSize is 0 From f9a805070a21fb9dd19db9cb0eb3858ae2c32f33 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 1 Nov 2018 03:08:01 -0700 Subject: [PATCH 046/210] Bump version to 2.9.1 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219609471 --- RELEASENOTES.md | 3 +++ constants.gradle | 4 ++-- .../com/google/android/exoplayer2/ExoPlayerLibraryInfo.java | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 194a49db88..850b0089b8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -18,6 +18,9 @@ * Fix handling of streams with appended data ([#4954](https://github.com/google/ExoPlayer/issues/4954)). * DASH: Parse ProgramInformation element if present in the manifest. +* HLS: Add constructor to `DefaultHlsExtractorFactory` for adding TS payload + reader factory flags + ([#4861](https://github.com/google/ExoPlayer/issues/4861)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). * Fix issue with blind seeking to windows with non-zero offset in a diff --git a/constants.gradle b/constants.gradle index 6db6d6310b..dd277c722c 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.9.0' - releaseVersionCode = 2009000 + releaseVersion = '2.9.1' + releaseVersionCode = 2009001 // Important: ExoPlayer specifies a minSdkVersion of 14 because various // components provided by the library may be of use on older devices. // However, please note that the core media playback functionality provided diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index f5ad677d77..c4dda9e957 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo { /** The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - public static final String VERSION = "2.9.0"; + public static final String VERSION = "2.9.1"; /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.0"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.1"; /** * The version of the library expressed as an integer, for example 1002003. @@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final int VERSION_INT = 2009000; + public static final int VERSION_INT = 2009001; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} From 10511e56cf13fde9340cd5b4621b7b6532dd7e6c Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 27 Sep 2018 07:37:39 -0700 Subject: [PATCH 047/210] Add constructor for adding payload reader factory flags Issue:#4861 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=214772527 --- .../ts/DefaultTsPayloadReaderFactory.java | 26 ++++++++++++ .../hls/DefaultHlsExtractorFactory.java | 42 +++++++++++++++---- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 94fd7ceb13..a5506e2cfb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -54,11 +54,37 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact }) public @interface Flags {} + /** + * When extracting H.264 samples, whether to treat samples consisting of non-IDR I slices as + * synchronization samples (key-frames). + */ public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1; + /** + * Prevents the creation of {@link AdtsReader} and {@link LatmReader} instances. This flag should + * be enabled if the transport stream contains no packets for an AAC elementary stream that is + * declared in the PMT. + */ public static final int FLAG_IGNORE_AAC_STREAM = 1 << 1; + /** + * Prevents the creation of {@link H264Reader} instances. This flag should be enabled if the + * transport stream contains no packets for an H.264 elementary stream that is declared in the + * PMT. + */ public static final int FLAG_IGNORE_H264_STREAM = 1 << 2; + /** + * When extracting H.264 samples, whether to split the input stream into access units (samples) + * based on slice headers. This flag should be disabled if the stream contains access unit + * delimiters (AUDs). + */ public static final int FLAG_DETECT_ACCESS_UNITS = 1 << 3; + /** Prevents the creation of {@link SpliceInfoSectionReader} instances. */ public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 1 << 4; + /** + * Whether the list of {@code closedCaptionFormats} passed to {@link + * DefaultTsPayloadReaderFactory#DefaultTsPayloadReaderFactory(int, List)} should be used in spite + * of any closed captions service descriptors. If this flag is disabled, {@code + * closedCaptionFormats} will be ignored if the PMT contains closed captions service descriptors. + */ public static final int FLAG_OVERRIDE_CAPTION_DESCRIPTORS = 1 << 5; private static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86; 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 951e6c95e0..3d75923553 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 @@ -51,6 +51,24 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { public static final String VTT_FILE_EXTENSION = ".vtt"; public static final String WEBVTT_FILE_EXTENSION = ".webvtt"; + @DefaultTsPayloadReaderFactory.Flags private final int payloadReaderFactoryFlags; + + /** Creates a factory for HLS segment extractors. */ + public DefaultHlsExtractorFactory() { + this(/* payloadReaderFactoryFlags= */ 0); + } + + /** + * Creates a factory for HLS segment extractors. + * + * @param payloadReaderFactoryFlags Flags to add when constructing any {@link + * DefaultTsPayloadReaderFactory} instances. Other flags may be added on top of {@code + * payloadReaderFactoryFlags} when creating {@link DefaultTsPayloadReaderFactory}. + */ + public DefaultHlsExtractorFactory(int payloadReaderFactoryFlags) { + this.payloadReaderFactoryFlags = payloadReaderFactoryFlags; + } + @Override public Pair createExtractor( Extractor previousExtractor, @@ -138,7 +156,9 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { } if (!(extractorByFileExtension instanceof TsExtractor)) { - TsExtractor tsExtractor = createTsExtractor(format, muxedCaptionFormats, timestampAdjuster); + TsExtractor tsExtractor = + createTsExtractor( + payloadReaderFactoryFlags, format, muxedCaptionFormats, timestampAdjuster); if (sniffQuietly(tsExtractor, extractorInput)) { return buildResult(tsExtractor); } @@ -180,17 +200,23 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { muxedCaptionFormats != null ? muxedCaptionFormats : Collections.emptyList()); } else { // For any other file extension, we assume TS format. - return createTsExtractor(format, muxedCaptionFormats, timestampAdjuster); + return createTsExtractor( + payloadReaderFactoryFlags, format, muxedCaptionFormats, timestampAdjuster); } } private static TsExtractor createTsExtractor( - Format format, List muxedCaptionFormats, TimestampAdjuster timestampAdjuster) { + @DefaultTsPayloadReaderFactory.Flags int userProvidedPayloadReaderFactoryFlags, + Format format, + List muxedCaptionFormats, + TimestampAdjuster timestampAdjuster) { @DefaultTsPayloadReaderFactory.Flags - int esReaderFactoryFlags = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM; + int payloadReaderFactoryFlags = + DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM + | userProvidedPayloadReaderFactoryFlags; if (muxedCaptionFormats != null) { // The playlist declares closed caption renditions, we should ignore descriptors. - esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS; + payloadReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS; } else { // The playlist does not provide any closed caption information. We preemptively declare a // closed caption track on channel 0. @@ -208,17 +234,17 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { // 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))) { - esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; + payloadReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; } if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { - esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; + payloadReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; } } return new TsExtractor( TsExtractor.MODE_HLS, timestampAdjuster, - new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, muxedCaptionFormats)); + new DefaultTsPayloadReaderFactory(payloadReaderFactoryFlags, muxedCaptionFormats)); } private static Pair buildResult(Extractor extractor) { From 57042adec74a7e02cd69d6ef7ad6300ef1a36635 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 1 Nov 2018 11:55:40 +0000 Subject: [PATCH 048/210] Remove methods now in BasePlayer --- .../exoplayer2/ext/cast/CastPlayer.java | 26 ---------------- .../android/exoplayer2/ExoPlayerImpl.java | 26 ---------------- .../android/exoplayer2/SimpleExoPlayer.java | 30 ------------------- .../exoplayer2/testutil/StubExoPlayer.java | 20 ------------- 4 files changed, 102 deletions(-) diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 1573401521..6cf6309796 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -365,32 +365,6 @@ public final class CastPlayer extends BasePlayer { } } - @Override - public boolean hasPrevious() { - return getPreviousWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void previous() { - int previousWindowIndex = getPreviousWindowIndex(); - if (previousWindowIndex != C.INDEX_UNSET) { - seekToDefaultPosition(previousWindowIndex); - } - } - - @Override - public boolean hasNext() { - return getNextWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void next() { - int nextWindowIndex = getPreviousWindowIndex(); - if (nextWindowIndex != C.INDEX_UNSET) { - seekToDefaultPosition(nextWindowIndex); - } - } - @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { // Unsupported by the RemoteMediaClient API. Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index fb27452f89..ffdadb78f7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -329,32 +329,6 @@ import java.util.concurrent.CopyOnWriteArraySet; } } - @Override - public boolean hasPrevious() { - return getPreviousWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void previous() { - int previousWindowIndex = getPreviousWindowIndex(); - if (previousWindowIndex != C.INDEX_UNSET) { - seekToDefaultPosition(previousWindowIndex); - } - } - - @Override - public boolean hasNext() { - return getNextWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void next() { - int nextWindowIndex = getPreviousWindowIndex(); - if (nextWindowIndex != C.INDEX_UNSET) { - seekToDefaultPosition(nextWindowIndex); - } - } - @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { if (playbackParameters == null) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 36fd0868f9..8517556887 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -934,36 +934,6 @@ public class SimpleExoPlayer extends BasePlayer player.seekTo(windowIndex, positionMs); } - @Override - public boolean hasPrevious() { - verifyApplicationThread(); - return getPreviousWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void previous() { - verifyApplicationThread(); - if (hasPrevious()) { - analyticsCollector.notifySeekStarted(); - player.previous(); - } - } - - @Override - public boolean hasNext() { - verifyApplicationThread(); - return getNextWindowIndex() != C.INDEX_UNSET; - } - - @Override - public void next() { - verifyApplicationThread(); - if (hasNext()) { - analyticsCollector.notifySeekStarted(); - player.next(); - } - } - @Override public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { verifyApplicationThread(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index d1236a7a2e..156b573df8 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -134,26 +134,6 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public boolean hasPrevious() { - throw new UnsupportedOperationException(); - } - - @Override - public void previous() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasNext() { - throw new UnsupportedOperationException(); - } - - @Override - public void next() { - throw new UnsupportedOperationException(); - } - @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { throw new UnsupportedOperationException(); From 1866e6bfbaf40c36996276c5feebd21669dc6743 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 2 Nov 2018 01:36:41 -0700 Subject: [PATCH 049/210] Double the buffer duration for AC3 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219765107 --- .../google/android/exoplayer2/audio/DefaultAudioSink.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 4ce34ad41a..83f5b7dc52 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -172,6 +172,9 @@ public final class DefaultAudioSink implements AudioSink { */ private static final int BUFFER_MULTIPLICATION_FACTOR = 4; + /** To avoid underruns on some devices (e.g., Broadcom 7271), scale up the AC3 buffer duration. */ + private static final int AC3_BUFFER_MULTIPLICATION_FACTOR = 2; + /** * @see AudioTrack#ERROR_BAD_VALUE */ @@ -484,6 +487,9 @@ public final class DefaultAudioSink implements AudioSink { return Util.constrainValue(multipliedBufferSize, minAppBufferSize, maxAppBufferSize); } else { int rate = getMaximumEncodedRateBytesPerSecond(outputEncoding); + if (outputEncoding == C.ENCODING_AC3) { + rate *= AC3_BUFFER_MULTIPLICATION_FACTOR; + } return (int) (PASSTHROUGH_BUFFER_DURATION_US * rate / C.MICROS_PER_SECOND); } } From 6d84b2a496c5168322d39eb2d7856f0675a97183 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 2 Nov 2018 05:28:24 -0700 Subject: [PATCH 050/210] Update the DefaultExtractorInput's peek buffer length on each write This prevents leaving an inconsistent state after a EOF exception. Issue:#5039 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219785275 --- RELEASENOTES.md | 7 +++++-- .../extractor/DefaultExtractorInput.java | 4 ++-- .../extractor/DefaultExtractorInputTest.java | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 850b0089b8..3d4c3cd63e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -18,8 +18,11 @@ * Fix handling of streams with appended data ([#4954](https://github.com/google/ExoPlayer/issues/4954)). * DASH: Parse ProgramInformation element if present in the manifest. -* HLS: Add constructor to `DefaultHlsExtractorFactory` for adding TS payload - reader factory flags +* HLS: + * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload + reader factory flags + * Fix bug in segment sniffing + ([#5039](https://github.com/google/ExoPlayer/issues/5039)). ([#4861](https://github.com/google/ExoPlayer/issues/4861)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java index c3f6304091..450cca42b0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java @@ -130,16 +130,16 @@ public final class DefaultExtractorInput implements ExtractorInput { public boolean advancePeekPosition(int length, boolean allowEndOfInput) throws IOException, InterruptedException { ensureSpaceForPeek(length); - int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length); + int bytesPeeked = peekBufferLength - peekBufferPosition; while (bytesPeeked < length) { bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked, allowEndOfInput); if (bytesPeeked == C.RESULT_END_OF_INPUT) { return false; } + peekBufferLength = peekBufferPosition + bytesPeeked; } peekBufferPosition += length; - peekBufferLength = Math.max(peekBufferLength, peekBufferPosition); return true; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java index a96dfaf2f8..8b26361578 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java @@ -338,6 +338,23 @@ public class DefaultExtractorInputTest { } } + @Test + public void testPeekFullyAfterEofExceptionPeeksAsExpected() throws Exception { + DefaultExtractorInput input = createDefaultExtractorInput(); + byte[] target = new byte[TEST_DATA.length + 10]; + + try { + input.peekFully(target, /* offset= */ 0, target.length); + fail(); + } catch (EOFException expected) { + // Do nothing. Expected. + } + input.peekFully(target, /* offset= */ 0, /* length= */ TEST_DATA.length); + + assertThat(input.getPeekPosition()).isEqualTo(TEST_DATA.length); + assertThat(Arrays.equals(TEST_DATA, Arrays.copyOf(target, TEST_DATA.length))).isTrue(); + } + @Test public void testResetPeekPosition() throws Exception { DefaultExtractorInput input = createDefaultExtractorInput(); From 8f57d85881a0fd5e713b04bc5d6b6055f9bbfc70 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Fri, 2 Nov 2018 07:26:30 -0700 Subject: [PATCH 051/210] Add support for .cmf* extension in DefaultHlsExtractorFactory This makes extractor selection a bit more efficient for some CMAF files. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219795105 --- .../exoplayer2/source/hls/DefaultHlsExtractorFactory.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 3d75923553..8a403c3759 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 @@ -48,6 +48,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { public static final String MP4_FILE_EXTENSION = ".mp4"; public static final String M4_FILE_EXTENSION_PREFIX = ".m4"; public static final String MP4_FILE_EXTENSION_PREFIX = ".mp4"; + public static final String CMF_FILE_EXTENSION_PREFIX = ".cmf"; public static final String VTT_FILE_EXTENSION = ".vtt"; public static final String WEBVTT_FILE_EXTENSION = ".webvtt"; @@ -191,7 +192,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { return new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0); } else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION) || lastPathSegment.startsWith(M4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 4) - || lastPathSegment.startsWith(MP4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)) { + || lastPathSegment.startsWith(MP4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5) + || lastPathSegment.startsWith(CMF_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)) { return new FragmentedMp4Extractor( /* flags= */ 0, timestampAdjuster, From af2b3f578f75016f6105faa9d03af84ddd5a0f5c Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 5 Nov 2018 01:23:47 -0800 Subject: [PATCH 052/210] Tweak dev guide / readme ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220059244 --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2b6a508aaa..37967dd527 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ repository and depend on the modules locally. ### From JCenter ### The easiest way to get started using ExoPlayer is to add it as a gradle -dependency. You need to make sure you have the JCenter and Google repositories +dependency. You need to make sure you have the Google and JCenter repositories included in the `build.gradle` file in the root of your project: ```gradle @@ -47,8 +47,13 @@ implementation 'com.google.android.exoplayer:exoplayer:2.X.X' where `2.X.X` is your preferred version. If not enabled already, you also need to turn on Java 8 support in all `build.gradle` files depending on ExoPlayer, by -adding `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to the -`android` section. +adding the following to the `android` section: + +```gradle +compileOptions { + targetCompatibility JavaVersion.VERSION_1_8 +} +``` As an alternative to the full library, you can depend on only the library modules that you actually need. For example the following will add dependencies From e347239ac557c7c12d0f4ddb8eb81fefccd7b272 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 5 Nov 2018 10:54:24 -0800 Subject: [PATCH 053/210] Document error case for generateAudioSessionIdV21 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220132865 --- .../core/src/main/java/com/google/android/exoplayer2/C.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 6c72dd8d0a..fac9818d9e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -978,7 +978,10 @@ public final class C { } /** - * Returns a newly generated {@link android.media.AudioTrack} session identifier. + * Returns a newly generated audio session identifier, or {@link AudioManager#ERROR} if an error + * occurred in which case audio playback may fail. + * + * @see AudioManager#generateAudioSessionId() */ @TargetApi(21) public static int generateAudioSessionIdV21(Context context) { From 3e35b6d0161530d49c74a7804b97aea274189755 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 6 Nov 2018 00:35:42 -0800 Subject: [PATCH 054/210] Work around non-empty EoS buffers with timestamp 0 Issue: #5045 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220237752 --- RELEASENOTES.md | 9 ++++-- .../audio/MediaCodecAudioRenderer.java | 30 ++++++++++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3d4c3cd63e..18d42c0c9b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -35,13 +35,16 @@ * Fix issue where the buffered position was not updated correctly when transitioning between periods ([#4899](https://github.com/google/ExoPlayer/issues/4899)). +* Fix issue where a `NullPointerException` is thrown when removing an unprepared + media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` + option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)). +* Work around an issue where a non-empty end-of-stream audio buffer would be + output with timestamp zero, causing the player position to jump backwards + ([#5045](https://github.com/google/ExoPlayer/issues/5045)). * Suppress a spurious assertion failure on some Samsung devices ([#4532](https://github.com/google/ExoPlayer/issues/4532)). * Suppress spurious "references unknown class member" shrinking warning ([#4890](https://github.com/google/ExoPlayer/issues/4890)). -* Fix issue where a `NullPointerException` is thrown when removing an unprepared - media source from a `ConcatenatingMediaSource` with the `useLazyPreparation` - option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)). * Swap recommended order for google() and jcenter() in gradle config ([#4997](https://github.com/google/ExoPlayer/issues/4997)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 624e698ad6..7fdce35098 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -24,6 +24,7 @@ import android.media.MediaCrypto; import android.media.MediaFormat; import android.media.audiofx.Virtualizer; import android.os.Handler; +import android.support.annotation.CallSuper; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -86,6 +87,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media private int codecMaxInputSize; private boolean passthroughEnabled; private boolean codecNeedsDiscardChannelsWorkaround; + private boolean codecNeedsEosBufferTimestampWorkaround; private android.media.MediaFormat passthroughMediaFormat; private @C.Encoding int pcmEncoding; private int channelCount; @@ -345,6 +347,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media float codecOperatingRate) { codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats()); codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); + codecNeedsEosBufferTimestampWorkaround = codecNeedsEosBufferTimestampWorkaround(codecInfo.name); passthroughEnabled = codecInfo.passthrough; String codecMimeType = codecInfo.mimeType == null ? MimeTypes.AUDIO_RAW : codecInfo.mimeType; MediaFormat mediaFormat = @@ -583,9 +586,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media lastInputTimeUs = Math.max(buffer.timeUs, lastInputTimeUs); } + @CallSuper @Override protected void onProcessedOutputBuffer(long presentationTimeUs) { - super.onProcessedOutputBuffer(presentationTimeUs); while (pendingStreamChangeCount != 0 && presentationTimeUs >= pendingStreamChangeTimesUs[0]) { audioSink.handleDiscontinuity(); pendingStreamChangeCount--; @@ -610,6 +613,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media boolean shouldSkip, Format format) throws ExoPlaybackException { + if (codecNeedsEosBufferTimestampWorkaround + && bufferPresentationTimeUs == 0 + && (bufferFlags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0 + && lastInputTimeUs != C.TIME_UNSET) { + bufferPresentationTimeUs = lastInputTimeUs; + } + if (passthroughEnabled && (bufferFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // Discard output buffers from the passthrough (raw) decoder containing codec specific data. codec.releaseOutputBuffer(bufferIndex, false); @@ -777,6 +787,24 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media || Util.DEVICE.startsWith("heroqlte")); } + /** + * Returns whether the decoder may output a non-empty buffer with timestamp 0 as the end of stream + * buffer. + * + *

See GitHub issue #5045. + */ + private static boolean codecNeedsEosBufferTimestampWorkaround(String codecName) { + return Util.SDK_INT < 21 + && "OMX.SEC.mp3.dec".equals(codecName) + && "samsung".equals(Util.MANUFACTURER) + && (Util.DEVICE.startsWith("baffin") + || Util.DEVICE.startsWith("grand") + || Util.DEVICE.startsWith("fortuna") + || Util.DEVICE.startsWith("gprimelte") + || Util.DEVICE.startsWith("j2y18lte") + || Util.DEVICE.startsWith("ms01")); + } + private final class AudioSinkListener implements AudioSink.Listener { @Override From 54075ed166f64089d645a4e58a7f26ff70f85da2 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 6 Nov 2018 06:03:59 -0800 Subject: [PATCH 055/210] Remove executable bit from some resources Copybara propagates this bit on the files, so removing it avoids some unnecessary changes in the first migrated commit. Also losslessly optimize two PNG files. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220268951 --- .../main/res/drawable-xxhdpi/ic_download.png | Bin 303 -> 261 bytes .../main/res/drawable-xxxhdpi/ic_download.png | Bin 304 -> 263 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/demos/main/src/main/res/drawable-xxhdpi/ic_download.png b/demos/main/src/main/res/drawable-xxhdpi/ic_download.png index f02715177ad64ccd13ae3b10298f099f692ae725..4e04a30198d72eedd3a749158ce4fc091a2b0f8b 100644 GIT binary patch delta 233 zcmZ3_)XFqLrCun&C&ZP3f#E+YunSsi1r%i~3GxeOaA^3ia3H(HS_UXG$J50zq=GR? z;#h)650j#*$^wrqW~s?42__1++_v^+NnbTvl^Zg9vXBUia<*}6=c)%6j-~Kql=t~E zurNq)IH?J4YJ9hUHgm^y?VWZtFOm-I47ZRz9yitSi9oXwo1;&c1dE|&lE9HA9gYH% zmDmD#x+Gi-HIf95F6eL+0jj_ds63Wdb#doS_w@hf#^!%2o==z_Tf7eFPzFy|KbLh* G2~7ZayIVp4 delta 276 zcmZo=TF*2=rCv0^C&ZP3f#E*}@Hn5#0+e7c3GxeOaA;uouW%rGQBx#P=#Zz2V@L(# z+bbJ$j|2!XUtE1qHe!*qg0%pPc!BAT|59@_0v05uN&JX-#xMMKU)tu@?;G!ao7uki zlfm5WvWMGvrOn>N->BaGtvLOD{q3##33ji`5(0zWJ9ZxLaJ6+iW9z2#$}s7jVNzhf zU~#`-cU8}f>Yj)z$w%%aA6X=){7g<+?5l)f@f-cRPIU3B5=Pf0Hmx{1W9QKst}@wY zWU_T0MI_&|e#{#^DjWv+R;i913rU`GJA70J0Yf7HdZ(Wfyo{Xm^Fh6({ z_nPtA>pxZT^|vw>_nO_(IRD_a*ip%vc=Os1g?FzWTps**Qn22_*r1(DN<1H4T7bql vcxeelge1npOP|+Xm)v7s>gTe~DWM4f1o3xI literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^6F``W8A#63;L-+CJOMr-u0Z-f3|JhnUIFB@lmz(& zGyG?0c(D0mIFNtd)5S5Qg7NL8g}Kd%Jgyff^!->F`o2}}_}t%%${nijyLSKM`^zAAsKnfb Sjb}E{YYd*QelF{r5}E)O=6ezV From ac0b11edc0606dbf82e2623828bbbc416e069f7e Mon Sep 17 00:00:00 2001 From: borrelli Date: Tue, 6 Nov 2018 11:18:28 -0800 Subject: [PATCH 056/210] Fix for #5055 - Cannot disable audio focus after enabled. This fixes an issue where disabling audio focus handling while audio focus is held would not release audio focus. A new test was added for this situation. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220316866 --- RELEASENOTES.md | 2 ++ .../exoplayer2/audio/AudioFocusManager.java | 4 +-- .../audio/AudioFocusManagerTest.java | 33 ++++++++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 18d42c0c9b..fef5e3ddb7 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -29,6 +29,8 @@ * Fix issue with blind seeking to windows with non-zero offset in a `ConcatenatingMediaSource` ([#4873](https://github.com/google/ExoPlayer/issues/4873)). +* Fix issue where audio focus handling could not be disabled after enabling it + ([#5055](https://github.com/google/ExoPlayer/issues/5055)). * Fix issue where subtitles were positioned incorrectly if `SubtitleView` had a non-zero position offset to its parent ([#4788](https://github.com/google/ExoPlayer/issues/4788)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java index ca4e0c299e..4740e5d6e7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java @@ -144,8 +144,8 @@ public final class AudioFocusManager { */ public @PlayerCommand int setAudioAttributes( @Nullable AudioAttributes audioAttributes, boolean playWhenReady, int playerState) { - if (audioAttributes == null) { - return PLAYER_COMMAND_PLAY_WHEN_READY; + if (this.audioAttributes == null && audioAttributes == null) { + return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY; } Assertions.checkNotNull( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java index 1cf812559c..b175828d44 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java @@ -58,7 +58,38 @@ public class AudioFocusManagerTest { assertThat( audioFocusManager.setAudioAttributes( /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_IDLE)) + .isEqualTo(PLAYER_COMMAND_WAIT_FOR_CALLBACK); + assertThat( + audioFocusManager.setAudioAttributes( + /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(request).isNull(); + } + + @Test + public void setAudioAttributes_withNullUsage_releasesAudioFocus() { + // Create attributes and request audio focus. + AudioAttributes media = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).build(); + Shadows.shadowOf(audioManager) + .setNextFocusRequestResponse(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + assertThat( + audioFocusManager.setAudioAttributes( + media, /* playWhenReady= */ true, Player.STATE_READY)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + ShadowAudioManager.AudioFocusRequest request = + Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); + assertThat(request.durationHint).isEqualTo(AudioManager.AUDIOFOCUS_GAIN); + + // Ensure that setting null audio attributes with audio focus releases audio focus. + assertThat( + audioFocusManager.setAudioAttributes( + /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY)) + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + AudioManager.OnAudioFocusChangeListener lastRequest = + Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener(); + assertThat(lastRequest).isNotNull(); } @Test @@ -296,7 +327,7 @@ public class AudioFocusManagerTest { assertThat( audioFocusManager.setAudioAttributes( /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_READY)) - .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); + .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); assertThat(Shadows.shadowOf(audioManager).getLastAbandonedAudioFocusListener()).isNull(); ShadowAudioManager.AudioFocusRequest request = Shadows.shadowOf(audioManager).getLastAudioFocusRequest(); From b8b8844083ff7589836485db013907b908683d60 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 7 Nov 2018 07:47:51 -0800 Subject: [PATCH 057/210] Add missing update on repeat toggle mode change ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220461315 --- .../java/com/google/android/exoplayer2/ui/PlayerControlView.java | 1 + 1 file changed, 1 insertion(+) 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 5f4864d783..f0e824afed 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 @@ -529,6 +529,7 @@ public class PlayerControlView extends FrameLayout { controlDispatcher.dispatchSetRepeatMode(player, Player.REPEAT_MODE_ALL); } } + updateRepeatModeButton(); } /** Returns whether the shuffle button is shown. */ From fd98d70a113baf67d173714ae781bc0315e2f2f6 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 7 Nov 2018 08:39:05 -0800 Subject: [PATCH 058/210] Make TimelineQueueNavigator shuffle aware Issue: #5065 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220468285 --- RELEASENOTES.md | 2 + .../mediasession/TimelineQueueNavigator.java | 51 +++++++++++-------- .../source/ClippingMediaSource.java | 2 +- .../exoplayer2/ui/PlayerControlView.java | 11 ++-- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fef5e3ddb7..9eae825c61 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -29,6 +29,8 @@ * Fix issue with blind seeking to windows with non-zero offset in a `ConcatenatingMediaSource` ([#4873](https://github.com/google/ExoPlayer/issues/4873)). +* Fix logic for enabling next and previous actions in `TimelineQueueNavigator` + ([#5065](https://github.com/google/ExoPlayer/issues/5065)). * Fix issue where audio focus handling could not be disabled after enabling it ([#5055](https://github.com/google/ExoPlayer/issues/5055)). * Fix issue where subtitles were positioned incorrectly if `SubtitleView` had a diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java index 6671add7e5..d55f8e04f0 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java @@ -39,6 +39,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu public static final int DEFAULT_MAX_QUEUE_SIZE = 10; private final MediaSessionCompat mediaSession; + private final Timeline.Window window; protected final int maxQueueSize; private long activeQueueItemId; @@ -68,6 +69,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu this.mediaSession = mediaSession; this.maxQueueSize = maxQueueSize; activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; + window = new Timeline.Window(); } /** @@ -81,25 +83,24 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu @Override public long getSupportedQueueNavigatorActions(Player player) { - if (player == null || player.getCurrentTimeline().getWindowCount() < 2) { + if (player == null) { return 0; } - if (player.getRepeatMode() != Player.REPEAT_MODE_OFF) { - return PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + Timeline timeline = player.getCurrentTimeline(); + if (timeline.isEmpty() || player.isPlayingAd()) { + return 0; } - - int currentWindowIndex = player.getCurrentWindowIndex(); - long actions; - if (currentWindowIndex == 0) { - actions = PlaybackStateCompat.ACTION_SKIP_TO_NEXT; - } else if (currentWindowIndex == player.getCurrentTimeline().getWindowCount() - 1) { - actions = PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; - } else { - actions = PlaybackStateCompat.ACTION_SKIP_TO_NEXT - | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; + long actions = 0; + if (timeline.getWindowCount() > 1) { + actions |= PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; } - return actions | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + if (window.isSeekable || !window.isDynamic || player.hasPrevious()) { + actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; + } + if (window.isDynamic || player.hasNext()) { + actions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; + } + return actions; } @Override @@ -125,22 +126,25 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu @Override public void onSkipToPrevious(Player player) { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } + int windowIndex = player.getCurrentWindowIndex(); + timeline.getWindow(windowIndex, window); int previousWindowIndex = player.getPreviousWindowIndex(); - if (player.getCurrentPosition() > MAX_POSITION_FOR_SEEK_TO_PREVIOUS - || previousWindowIndex == C.INDEX_UNSET) { - player.seekTo(0); - } else { + if (previousWindowIndex != C.INDEX_UNSET + && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS + || (window.isDynamic && !window.isSeekable))) { player.seekTo(previousWindowIndex, C.TIME_UNSET); + } else { + player.seekTo(0); } } @Override public void onSkipToQueueItem(Player player, long id) { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } int windowIndex = (int) id; @@ -152,12 +156,15 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu @Override public void onSkipToNext(Player player) { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } + int windowIndex = player.getCurrentWindowIndex(); int nextWindowIndex = player.getNextWindowIndex(); if (nextWindowIndex != C.INDEX_UNSET) { player.seekTo(nextWindowIndex, C.TIME_UNSET); + } else if (timeline.getWindow(windowIndex, window).isDynamic) { + player.seekTo(windowIndex, C.TIME_UNSET); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index 5f80725805..78e37c1869 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -348,7 +348,7 @@ public final class ClippingMediaSource extends CompositeMediaSource { if (timeline.getPeriodCount() != 1) { throw new IllegalClippingException(IllegalClippingException.REASON_INVALID_PERIOD_COUNT); } - Window window = timeline.getWindow(0, new Window(), false); + Window window = timeline.getWindow(0, new Window()); startUs = Math.max(0, startUs); long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : Math.max(0, endUs); if (window.durationUs != C.TIME_UNSET) { 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 f0e824afed..8ab4210465 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 @@ -634,9 +634,8 @@ public class PlayerControlView extends FrameLayout { int windowIndex = player.getCurrentWindowIndex(); timeline.getWindow(windowIndex, window); isSeekable = window.isSeekable; - enablePrevious = - isSeekable || !window.isDynamic || player.getPreviousWindowIndex() != C.INDEX_UNSET; - enableNext = window.isDynamic || player.getNextWindowIndex() != C.INDEX_UNSET; + enablePrevious = isSeekable || !window.isDynamic || player.hasPrevious(); + enableNext = window.isDynamic || player.hasNext(); } setButtonEnabled(enablePrevious, previousButton); setButtonEnabled(enableNext, nextButton); @@ -831,7 +830,7 @@ public class PlayerControlView extends FrameLayout { private void previous() { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } int windowIndex = player.getCurrentWindowIndex(); @@ -848,14 +847,14 @@ public class PlayerControlView extends FrameLayout { private void next() { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } int windowIndex = player.getCurrentWindowIndex(); int nextWindowIndex = player.getNextWindowIndex(); if (nextWindowIndex != C.INDEX_UNSET) { seekTo(nextWindowIndex, C.TIME_UNSET); - } else if (timeline.getWindow(windowIndex, window, false).isDynamic) { + } else if (timeline.getWindow(windowIndex, window).isDynamic) { seekTo(windowIndex, C.TIME_UNSET); } } From 6bc04082224daae7440e555ecc357abc9aecc99f Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 7 Nov 2018 08:49:10 -0800 Subject: [PATCH 059/210] Make BasePlayer.get[Next/Previous]WindowIndex more useful When in REPEAT_MODE_ONE, it's unlikely apps want next/previous methods on the player to keep them in the same window. Music apps in particular tend to implement next/previous functionality as though repeat mode were off when in this mode (i.e. current song loops forever during playback, but next/previous navigation still navigates to next/previous items). ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220469655 --- .../java/com/google/android/exoplayer2/BasePlayer.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java index 6ff0853d9b..f1b54153a1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -79,7 +79,7 @@ public abstract class BasePlayer implements Player { return timeline.isEmpty() ? C.INDEX_UNSET : timeline.getNextWindowIndex( - getCurrentWindowIndex(), getRepeatMode(), getShuffleModeEnabled()); + getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled()); } @Override @@ -88,7 +88,7 @@ public abstract class BasePlayer implements Player { return timeline.isEmpty() ? C.INDEX_UNSET : timeline.getPreviousWindowIndex( - getCurrentWindowIndex(), getRepeatMode(), getShuffleModeEnabled()); + getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled()); } @Override @@ -129,4 +129,10 @@ public abstract class BasePlayer implements Player { ? C.TIME_UNSET : timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); } + + @RepeatMode + private int getRepeatModeForNavigation() { + @RepeatMode int repeatMode = getRepeatMode(); + return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode; + } } From f5c3b30290692f2385ad43e7e8ca26f742508476 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 7 Nov 2018 08:53:09 -0800 Subject: [PATCH 060/210] Fix BasePlayer.next() ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220470213 --- .../src/main/java/com/google/android/exoplayer2/BasePlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java index f1b54153a1..eb3bd4f91a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -62,7 +62,7 @@ public abstract class BasePlayer implements Player { @Override public final void next() { - int nextWindowIndex = getPreviousWindowIndex(); + int nextWindowIndex = getNextWindowIndex(); if (nextWindowIndex != C.INDEX_UNSET) { seekToDefaultPosition(nextWindowIndex); } From a02a75ba57c7e8486b0a22a9df8bb69f0cd7d495 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 7 Nov 2018 19:24:02 +0000 Subject: [PATCH 061/210] Fix audio focus --- .../exoplayer2/audio/AudioFocusManager.java | 20 +++++++++---------- .../audio/AudioFocusManagerTest.java | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java index 4740e5d6e7..7146426a4a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioFocusManager.java @@ -163,11 +163,9 @@ public final class AudioFocusManager { } } - if (playerState == Player.STATE_IDLE) { - return PLAYER_COMMAND_WAIT_FOR_CALLBACK; - } else { - return handlePrepare(playWhenReady); - } + return playerState == Player.STATE_IDLE + ? handleIdle(playWhenReady) + : handlePrepare(playWhenReady); } /** @@ -199,12 +197,9 @@ public final class AudioFocusManager { if (!playWhenReady) { abandonAudioFocus(); return PLAYER_COMMAND_DO_NOT_PLAY; - } else if (playerState != Player.STATE_IDLE) { - return requestAudioFocus(); } - return focusGain != C.AUDIOFOCUS_NONE - ? PLAYER_COMMAND_WAIT_FOR_CALLBACK - : PLAYER_COMMAND_PLAY_WHEN_READY; + + return playerState == Player.STATE_IDLE ? handleIdle(playWhenReady) : requestAudioFocus(); } /** Called by the player as part of {@link ExoPlayer#stop(boolean)}. */ @@ -218,6 +213,11 @@ public final class AudioFocusManager { // Internal methods. + @PlayerCommand + private int handleIdle(boolean playWhenReady) { + return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY; + } + private @PlayerCommand int requestAudioFocus() { int focusRequestResult; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java index b175828d44..086c4ebc7f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/AudioFocusManagerTest.java @@ -58,7 +58,7 @@ public class AudioFocusManagerTest { assertThat( audioFocusManager.setAudioAttributes( /* audioAttributes= */ null, /* playWhenReady= */ false, Player.STATE_IDLE)) - .isEqualTo(PLAYER_COMMAND_WAIT_FOR_CALLBACK); + .isEqualTo(PLAYER_COMMAND_DO_NOT_PLAY); assertThat( audioFocusManager.setAudioAttributes( /* audioAttributes= */ null, /* playWhenReady= */ true, Player.STATE_READY)) @@ -148,7 +148,7 @@ public class AudioFocusManagerTest { assertThat( audioFocusManager.setAudioAttributes( media, /* playWhenReady= */ true, Player.STATE_IDLE)) - .isEqualTo(PLAYER_COMMAND_WAIT_FOR_CALLBACK); + .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); assertThat(Shadows.shadowOf(audioManager).getLastAudioFocusRequest()).isNull(); assertThat(audioFocusManager.handlePrepare(/* playWhenReady= */ true)) .isEqualTo(PLAYER_COMMAND_PLAY_WHEN_READY); From d9462b9d2b987e5280e9d9a9717ef9d70665f2b3 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 8 Nov 2018 08:18:44 -0800 Subject: [PATCH 062/210] Include channel count in capabilities check Issue: #4690 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220640737 --- RELEASENOTES.md | 5 +++++ .../ext/ffmpeg/FfmpegAudioRenderer.java | 5 +++-- .../ext/flac/LibflacAudioRenderer.java | 2 +- .../ext/opus/LibopusAudioRenderer.java | 2 +- .../exoplayer2/audio/AudioCapabilities.java | 14 ++++++++------ .../android/exoplayer2/audio/AudioSink.java | 10 ++++++---- .../exoplayer2/audio/DefaultAudioSink.java | 12 ++++++++---- .../audio/MediaCodecAudioRenderer.java | 17 ++++++++++------- .../audio/SimpleDecoderAudioRenderer.java | 9 ++++----- 9 files changed, 46 insertions(+), 30 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9eae825c61..ecc967f016 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,10 @@ # Release notes # +### 2.9.2 ### + +* Include channel count in audio capabilities check + ([#4690](https://github.com/google/ExoPlayer/issues/4690)). + ### 2.9.1 ### * Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext` diff --git a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java index 13e3964c71..f0b30baa8a 100644 --- a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java +++ b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioRenderer.java @@ -145,12 +145,13 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { } private boolean isOutputSupported(Format inputFormat) { - return shouldUseFloatOutput(inputFormat) || supportsOutputEncoding(C.ENCODING_PCM_16BIT); + return shouldUseFloatOutput(inputFormat) + || supportsOutput(inputFormat.channelCount, C.ENCODING_PCM_16BIT); } private boolean shouldUseFloatOutput(Format inputFormat) { Assertions.checkNotNull(inputFormat.sampleMimeType); - if (!enableFloatOutput || !supportsOutputEncoding(C.ENCODING_PCM_FLOAT)) { + if (!enableFloatOutput || !supportsOutput(inputFormat.channelCount, C.ENCODING_PCM_FLOAT)) { return false; } switch (inputFormat.sampleMimeType) { diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java index fa66abbdc6..424fcbb285 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java @@ -53,7 +53,7 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer { if (!FlacLibrary.isAvailable() || !MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)) { return FORMAT_UNSUPPORTED_TYPE; - } else if (!supportsOutputEncoding(C.ENCODING_PCM_16BIT)) { + } else if (!supportsOutput(format.channelCount, C.ENCODING_PCM_16BIT)) { return FORMAT_UNSUPPORTED_SUBTYPE; } else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) { return FORMAT_UNSUPPORTED_DRM; diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java index 57937b4282..e288339058 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java @@ -78,7 +78,7 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer { if (!OpusLibrary.isAvailable() || !MimeTypes.AUDIO_OPUS.equalsIgnoreCase(format.sampleMimeType)) { return FORMAT_UNSUPPORTED_TYPE; - } else if (!supportsOutputEncoding(C.ENCODING_PCM_16BIT)) { + } else if (!supportsOutput(format.channelCount, C.ENCODING_PCM_16BIT)) { return FORMAT_UNSUPPORTED_SUBTYPE; } else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) { return FORMAT_UNSUPPORTED_DRM; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java index 92d39dec65..6a5e58ef2a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java @@ -29,11 +29,11 @@ import java.util.Arrays; @TargetApi(21) public final class AudioCapabilities { - /** - * The minimum audio capabilities supported by all devices. - */ + private static final int DEFAULT_MAX_CHANNEL_COUNT = 8; + + /** The minimum audio capabilities supported by all devices. */ public static final AudioCapabilities DEFAULT_AUDIO_CAPABILITIES = - new AudioCapabilities(new int[] {AudioFormat.ENCODING_PCM_16BIT}, 2); + new AudioCapabilities(new int[] {AudioFormat.ENCODING_PCM_16BIT}, DEFAULT_MAX_CHANNEL_COUNT); /** * Returns the current audio capabilities for the device. @@ -52,8 +52,10 @@ public final class AudioCapabilities { if (intent == null || intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, 0) == 0) { return DEFAULT_AUDIO_CAPABILITIES; } - return new AudioCapabilities(intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS), - intent.getIntExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, 0)); + return new AudioCapabilities( + intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS), + intent.getIntExtra( + AudioManager.EXTRA_MAX_CHANNEL_COUNT, /* defaultValue= */ DEFAULT_MAX_CHANNEL_COUNT)); } private final int[] supportedEncodings; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java index bb7ef22ef4..24d218bf3c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.audio; import android.media.AudioTrack; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackParameters; import java.nio.ByteBuffer; @@ -165,12 +166,13 @@ public interface AudioSink { void setListener(Listener listener); /** - * Returns whether it's possible to play audio in the specified encoding. + * Returns whether the sink supports the audio format. * - * @param encoding The audio encoding. - * @return Whether it's possible to play audio in the specified encoding. + * @param channelCount The number of channels, or {@link Format#NO_VALUE} if not known. + * @param encoding The audio encoding, or {@link Format#NO_VALUE} if not known. + * @return Whether the sink supports the audio format. */ - boolean isEncodingSupported(@C.Encoding int encoding); + boolean supportsOutput(int channelCount, @C.Encoding int encoding); /** * Returns the playback position in the stream starting at zero, in microseconds, or diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 83f5b7dc52..429510bcaf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -377,14 +377,18 @@ public final class DefaultAudioSink implements AudioSink { } @Override - public boolean isEncodingSupported(@C.Encoding int encoding) { + public boolean supportsOutput(int channelCount, @C.Encoding int encoding) { if (Util.isEncodingLinearPcm(encoding)) { // AudioTrack supports 16-bit integer PCM output in all platform API versions, and float // output from platform API version 21 only. Other integer PCM encodings are resampled by this - // sink to 16-bit PCM. + // sink to 16-bit PCM. We assume that the audio framework will downsample any number of + // channels to the output device's required number of channels. return encoding != C.ENCODING_PCM_FLOAT || Util.SDK_INT >= 21; } else { - return audioCapabilities != null && audioCapabilities.supportsEncoding(encoding); + return audioCapabilities != null + && audioCapabilities.supportsEncoding(encoding) + && (channelCount == Format.NO_VALUE + || channelCount <= audioCapabilities.getMaxChannelCount()); } } @@ -415,7 +419,7 @@ public final class DefaultAudioSink implements AudioSink { isInputPcm = Util.isEncodingLinearPcm(inputEncoding); shouldConvertHighResIntPcmToFloat = enableConvertHighResIntPcmToFloat - && isEncodingSupported(C.ENCODING_PCM_32BIT) + && supportsOutput(channelCount, C.ENCODING_PCM_32BIT) && Util.isEncodingHighResolutionIntegerPcm(inputEncoding); if (isInputPcm) { pcmFrameSize = Util.getPcmFrameSize(inputEncoding, channelCount); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 7fdce35098..daa0447905 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -272,12 +272,14 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED; boolean supportsFormatDrm = supportsFormatDrm(drmSessionManager, format.drmInitData); - if (supportsFormatDrm && allowPassthrough(mimeType) + if (supportsFormatDrm + && allowPassthrough(format.channelCount, mimeType) && mediaCodecSelector.getPassthroughDecoderInfo() != null) { return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | FORMAT_HANDLED; } - if ((MimeTypes.AUDIO_RAW.equals(mimeType) && !audioSink.isEncodingSupported(format.pcmEncoding)) - || !audioSink.isEncodingSupported(C.ENCODING_PCM_16BIT)) { + if ((MimeTypes.AUDIO_RAW.equals(mimeType) + && !audioSink.supportsOutput(format.channelCount, format.pcmEncoding)) + || !audioSink.supportsOutput(format.channelCount, C.ENCODING_PCM_16BIT)) { // Assume the decoder outputs 16-bit PCM, unless the input is raw. return FORMAT_UNSUPPORTED_SUBTYPE; } @@ -316,7 +318,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media protected List getDecoderInfos( MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder) throws DecoderQueryException { - if (allowPassthrough(format.sampleMimeType)) { + if (allowPassthrough(format.channelCount, format.sampleMimeType)) { MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo(); if (passthroughDecoderInfo != null) { return Collections.singletonList(passthroughDecoderInfo); @@ -330,12 +332,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media * This implementation returns true if the {@link AudioSink} indicates that encoded audio output * is supported. * + * @param channelCount The number of channels in the input media, or {@link Format#NO_VALUE} if + * not known. * @param mimeType The type of input media. * @return Whether passthrough playback is supported. */ - protected boolean allowPassthrough(String mimeType) { - @C.Encoding int encoding = MimeTypes.getEncoding(mimeType); - return encoding != C.ENCODING_INVALID && audioSink.isEncodingSupported(encoding); + protected boolean allowPassthrough(int channelCount, String mimeType) { + return audioSink.supportsOutput(channelCount, MimeTypes.getEncoding(mimeType)); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index cecb17d96c..9b6be57e4c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -249,13 +249,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements DrmSessionManager drmSessionManager, Format format); /** - * Returns whether the audio sink can accept audio in the specified encoding. + * Returns whether the sink supports the audio format. * - * @param encoding The audio encoding. - * @return Whether the audio sink can accept audio in the specified encoding. + * @see AudioSink#supportsOutput(int, int) */ - protected final boolean supportsOutputEncoding(@C.Encoding int encoding) { - return audioSink.isEncodingSupported(encoding); + protected final boolean supportsOutput(int channelCount, @C.Encoding int encoding) { + return audioSink.supportsOutput(channelCount, encoding); } @Override From 2426a047f1aa54cfcbf105faeda7cbff5ee9cc5a Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 12 Nov 2018 04:43:43 -0800 Subject: [PATCH 063/210] Make support-media-compat an API dependency The extension cannot be used without also using support-media-compat (e.g. to instantiate a MediaSessionCompat). So it may as well be an API dependency. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=221072128 --- extensions/mediasession/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/mediasession/build.gradle b/extensions/mediasession/build.gradle index da04b0aec3..5fb25c6382 100644 --- a/extensions/mediasession/build.gradle +++ b/extensions/mediasession/build.gradle @@ -31,7 +31,7 @@ android { dependencies { implementation project(modulePrefix + 'library-core') - implementation 'com.android.support:support-media-compat:' + supportLibraryVersion + api 'com.android.support:support-media-compat:' + supportLibraryVersion } ext { From f7ed39ee173175f1154e6ad0be30fb03d2211d0e Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 12 Nov 2018 05:11:37 -0800 Subject: [PATCH 064/210] Add constructor to DefaultShuffleOrder to support sideloaded shuffle orders Issue: #4915 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=221075615 --- .../android/exoplayer2/source/ShuffleOrder.java | 11 +++++++++++ .../exoplayer2/source/ShuffleOrderTest.java | 17 +++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java index 750c42bbd0..90e2576d76 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java @@ -55,6 +55,17 @@ public interface ShuffleOrder { this(length, new Random(randomSeed)); } + /** + * Creates an instance with a specified shuffle order and the specified random seed. The random + * seed is used for {@link #cloneAndInsert(int, int)} invocations. + * + * @param shuffledIndices The shuffled indices to use as order. + * @param randomSeed A random seed. + */ + public DefaultShuffleOrder(int[] shuffledIndices, long randomSeed) { + this(Arrays.copyOf(shuffledIndices, shuffledIndices.length), new Random(randomSeed)); + } + private DefaultShuffleOrder(int length, Random random) { this(createShuffledList(length, random), random); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java index e15c8f0aaa..06f39409e7 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java @@ -79,6 +79,23 @@ public final class ShuffleOrderTest { } } + @Test + public void testSideloadedShuffleOrder() { + int[] shuffledIndices = new int[] {2, 1, 0, 4, 3}; + ShuffleOrder shuffleOrder = new DefaultShuffleOrder(shuffledIndices, RANDOM_SEED); + assertThat(shuffleOrder.getFirstIndex()).isEqualTo(2); + assertThat(shuffleOrder.getLastIndex()).isEqualTo(3); + for (int i = 0; i < 4; i++) { + assertThat(shuffleOrder.getNextIndex(shuffledIndices[i])).isEqualTo(shuffledIndices[i + 1]); + } + assertThat(shuffleOrder.getNextIndex(3)).isEqualTo(C.INDEX_UNSET); + for (int i = 4; i > 0; i--) { + assertThat(shuffleOrder.getPreviousIndex(shuffledIndices[i])) + .isEqualTo(shuffledIndices[i - 1]); + } + assertThat(shuffleOrder.getPreviousIndex(2)).isEqualTo(C.INDEX_UNSET); + } + private static void assertShuffleOrderCorrectness(ShuffleOrder shuffleOrder, int length) { assertThat(shuffleOrder.getLength()).isEqualTo(length); if (length == 0) { From 0311e153f367d5a798999721ecbfcdbd26d3725c Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 12 Nov 2018 05:45:14 -0800 Subject: [PATCH 065/210] Document need to call MediaSessionCompat.setActive ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=221078075 --- .../exoplayer2/ext/mediasession/MediaSessionConnector.java | 5 +++++ 1 file changed, 5 insertions(+) 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 d0cde5f693..9323723601 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 @@ -49,6 +49,11 @@ import java.util.Map; /** * Connects a {@link MediaSessionCompat} to a {@link Player}. * + *

This connector does not call {@link MediaSessionCompat#setActive(boolean)}, and so + * application code is responsible for making the session active when desired. A session must be + * active for transport controls to be displayed (e.g. on the lock screen) and for it to receive + * media button events. + * *

The connector listens for actions sent by the media session's controller and implements these * actions by calling appropriate player methods. The playback state of the media session is * automatically synced with the player. The connector can also be optionally extended by providing From 177f3883e9d12d041b19d6d0a490bef024a84bb7 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 14 Nov 2018 06:44:38 -0800 Subject: [PATCH 066/210] Noop cleanup of binary seeking / duration reading. This is a precursor for fixing the ref'd issue. These classes are well tested, so the tests passing should give you reasonable confidence I didn't break anything :). Issue: #5097 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=221435824 --- .../extractor/BinarySearchSeeker.java | 91 ++++++++----------- .../extractor/ts/PsBinarySearchSeeker.java | 15 ++- .../extractor/ts/PsDurationReader.java | 30 +++--- .../extractor/ts/TsBinarySearchSeeker.java | 22 ++--- .../extractor/ts/TsDurationReader.java | 31 +++---- 5 files changed, 87 insertions(+), 102 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java index 3b0b834427..efdeddbdce 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java @@ -42,31 +42,16 @@ public abstract class BinarySearchSeeker { protected interface TimestampSeeker { /** - * Searches for a given timestamp from the input. + * Searches a limited window of the provided input for a target timestamp. The size of the + * window is implementation specific, but should be small enough such that it's reasonable for + * multiple such reads to occur during a seek operation. * - *

Given a target timestamp and an input stream, this seeker will try to read up to a range - * of {@code searchRangeBytes} bytes from that input, look for all available timestamps from all - * frames in that range, compare those with the target timestamp, and return one of the {@link - * TimestampSearchResult}. - * - * @param input The {@link ExtractorInput} from which data should be read. - * @param targetTimestamp The target timestamp that we are looking for. - * @param outputFrameHolder If {@link TimestampSearchResult#RESULT_TARGET_TIMESTAMP_FOUND} is + * @param input The {@link ExtractorInput} from which data should be peeked. + * @param targetTimestamp The target timestamp. + * @param outputFrameHolder If {@link TimestampSearchResult#TYPE_TARGET_TIMESTAMP_FOUND} is * returned, this holder may be updated to hold the extracted frame that contains the target * frame/sample associated with the target timestamp. - * @return A {@link TimestampSearchResult}, that includes a {@link TimestampSearchResult#result} - * value, and other necessary info: - *

    - *
  • {@link TimestampSearchResult#RESULT_NO_TIMESTAMP} is returned if there is no - * timestamp in the reading range. - *
  • {@link TimestampSearchResult#RESULT_POSITION_UNDERESTIMATED} is returned if all - * timestamps in the range are smaller than the target timestamp. - *
  • {@link TimestampSearchResult#RESULT_POSITION_OVERESTIMATED} is returned if all - * timestamps in the range are larger than the target timestamp. - *
  • {@link TimestampSearchResult#RESULT_TARGET_TIMESTAMP_FOUND} is returned if this - * seeker can find a timestamp that it deems close enough to the given target. - *
- * + * @return A {@link TimestampSearchResult} that describes the result of the search. * @throws IOException If an error occurred reading from the input. * @throws InterruptedException If the thread was interrupted. */ @@ -231,22 +216,22 @@ public abstract class BinarySearchSeeker { timestampSeeker.searchForTimestamp( input, seekOperationParams.getTargetTimePosition(), outputFrameHolder); - switch (timestampSearchResult.result) { - case TimestampSearchResult.RESULT_POSITION_OVERESTIMATED: + switch (timestampSearchResult.type) { + case TimestampSearchResult.TYPE_POSITION_OVERESTIMATED: seekOperationParams.updateSeekCeiling( timestampSearchResult.timestampToUpdate, timestampSearchResult.bytePositionToUpdate); break; - case TimestampSearchResult.RESULT_POSITION_UNDERESTIMATED: + case TimestampSearchResult.TYPE_POSITION_UNDERESTIMATED: seekOperationParams.updateSeekFloor( timestampSearchResult.timestampToUpdate, timestampSearchResult.bytePositionToUpdate); break; - case TimestampSearchResult.RESULT_TARGET_TIMESTAMP_FOUND: + case TimestampSearchResult.TYPE_TARGET_TIMESTAMP_FOUND: markSeekOperationFinished( /* foundTargetFrame= */ true, timestampSearchResult.bytePositionToUpdate); skipInputUntilPosition(input, timestampSearchResult.bytePositionToUpdate); return seekToPosition( input, timestampSearchResult.bytePositionToUpdate, seekPositionHolder); - case TimestampSearchResult.RESULT_NO_TIMESTAMP: + case TimestampSearchResult.TYPE_NO_TIMESTAMP: // We can't find any timestamp in the search range from the search position. // Give up, and just continue reading from the last search position in this case. markSeekOperationFinished(/* foundTargetFrame= */ false, searchPosition); @@ -433,45 +418,49 @@ public abstract class BinarySearchSeeker { */ public static final class TimestampSearchResult { - public static final int RESULT_TARGET_TIMESTAMP_FOUND = 0; - public static final int RESULT_POSITION_OVERESTIMATED = -1; - public static final int RESULT_POSITION_UNDERESTIMATED = -2; - public static final int RESULT_NO_TIMESTAMP = -3; + /** The search found a timestamp that it deems close enough to the given target. */ + public static final int TYPE_TARGET_TIMESTAMP_FOUND = 0; + /** The search found only timestamps larger than the target timestamp. */ + public static final int TYPE_POSITION_OVERESTIMATED = -1; + /** The search found only timestamps smaller than the target timestamp. */ + public static final int TYPE_POSITION_UNDERESTIMATED = -2; + /** The search didn't find any timestamps. */ + public static final int TYPE_NO_TIMESTAMP = -3; @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ - RESULT_TARGET_TIMESTAMP_FOUND, - RESULT_POSITION_OVERESTIMATED, - RESULT_POSITION_UNDERESTIMATED, - RESULT_NO_TIMESTAMP + TYPE_TARGET_TIMESTAMP_FOUND, + TYPE_POSITION_OVERESTIMATED, + TYPE_POSITION_UNDERESTIMATED, + TYPE_NO_TIMESTAMP }) - @interface SearchResult {} + @interface Type {} public static final TimestampSearchResult NO_TIMESTAMP_IN_RANGE_RESULT = - new TimestampSearchResult(RESULT_NO_TIMESTAMP, C.TIME_UNSET, C.POSITION_UNSET); + new TimestampSearchResult(TYPE_NO_TIMESTAMP, C.TIME_UNSET, C.POSITION_UNSET); - /** @see TimestampSeeker */ - private final @SearchResult int result; + /** The type of the result. */ + @Type private final int type; /** - * When {@code result} is {@link #RESULT_POSITION_OVERESTIMATED}, the {@link - * SeekOperationParams#ceilingTimePosition} should be updated with this value. When {@code - * result} is {@link #RESULT_POSITION_UNDERESTIMATED}, the {@link + * When {@link #type} is {@link #TYPE_POSITION_OVERESTIMATED}, the {@link + * SeekOperationParams#ceilingTimePosition} should be updated with this value. When {@link + * #type} is {@link #TYPE_POSITION_UNDERESTIMATED}, the {@link * SeekOperationParams#floorTimePosition} should be updated with this value. */ private final long timestampToUpdate; /** - * When {@code result} is {@link #RESULT_POSITION_OVERESTIMATED}, the {@link - * SeekOperationParams#ceilingBytePosition} should be updated with this value. When {@code - * result} is {@link #RESULT_POSITION_UNDERESTIMATED}, the {@link + * When {@link #type} is {@link #TYPE_POSITION_OVERESTIMATED}, the {@link + * SeekOperationParams#ceilingBytePosition} should be updated with this value. When {@link + * #type} is {@link #TYPE_POSITION_UNDERESTIMATED}, the {@link * SeekOperationParams#floorBytePosition} should be updated with this value. */ private final long bytePositionToUpdate; private TimestampSearchResult( - @SearchResult int result, long timestampToUpdate, long bytePositionToUpdate) { - this.result = result; + @Type int type, long timestampToUpdate, long bytePositionToUpdate) { + this.type = type; this.timestampToUpdate = timestampToUpdate; this.bytePositionToUpdate = bytePositionToUpdate; } @@ -484,7 +473,7 @@ public abstract class BinarySearchSeeker { public static TimestampSearchResult overestimatedResult( long newCeilingTimestamp, long newCeilingBytePosition) { return new TimestampSearchResult( - RESULT_POSITION_OVERESTIMATED, newCeilingTimestamp, newCeilingBytePosition); + TYPE_POSITION_OVERESTIMATED, newCeilingTimestamp, newCeilingBytePosition); } /** @@ -495,11 +484,11 @@ public abstract class BinarySearchSeeker { public static TimestampSearchResult underestimatedResult( long newFloorTimestamp, long newCeilingBytePosition) { return new TimestampSearchResult( - RESULT_POSITION_UNDERESTIMATED, newFloorTimestamp, newCeilingBytePosition); + TYPE_POSITION_UNDERESTIMATED, newFloorTimestamp, newCeilingBytePosition); } /** - * Returns a result to signal that the target timestamp has been found at the {@code + * Returns a result to signal that the target timestamp has been found at {@code * resultBytePosition}, and the seek operation can stop. * *

Note that when this value is returned from {@link @@ -508,7 +497,7 @@ public abstract class BinarySearchSeeker { */ public static TimestampSearchResult targetFoundResult(long resultBytePosition) { return new TimestampSearchResult( - RESULT_TARGET_TIMESTAMP_FOUND, C.TIME_UNSET, resultBytePosition); + TYPE_TARGET_TIMESTAMP_FOUND, C.TIME_UNSET, resultBytePosition); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java index e8c207f75d..1180dd486e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java @@ -53,10 +53,9 @@ import java.io.IOException; /** * A seeker that looks for a given SCR timestamp at a given position in a PS stream. * - *

Given a SCR timestamp, and a position within a PS stream, this seeker will try to read a - * range of up to {@link #TIMESTAMP_SEARCH_BYTES} bytes from that stream position, look for all - * packs in that range, and then compare the SCR timestamps (if available) of these packets vs the - * target timestamp. + *

Given a SCR timestamp, and a position within a PS stream, this seeker will peek up to {@link + * #TIMESTAMP_SEARCH_BYTES} bytes from that stream position, look for all packs in that range, and + * then compare the SCR timestamps (if available) of these packets to the target timestamp. */ private static final class PsScrSeeker implements TimestampSeeker { @@ -73,10 +72,10 @@ import java.io.IOException; ExtractorInput input, long targetTimestamp, OutputFrameHolder outputFrameHolder) throws IOException, InterruptedException { long inputPosition = input.getPosition(); - int bytesToRead = - (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - input.getPosition()); - packetBuffer.reset(bytesToRead); - input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToRead); + int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition); + + input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); + packetBuffer.reset(bytesToSearch); return searchForScrValueInBuffer(packetBuffer, targetTimestamp, inputPosition); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java index 3b52206235..077a35f4a7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java @@ -38,7 +38,7 @@ import java.io.IOException; */ /* package */ final class PsDurationReader { - private static final int DURATION_READ_BYTES = 20000; + private static final int TIMESTAMP_SEARCH_BYTES = 20000; private final TimestampAdjuster scrTimestampAdjuster; private final ParsableByteArray packetBuffer; @@ -56,7 +56,7 @@ import java.io.IOException; firstScrValue = C.TIME_UNSET; lastScrValue = C.TIME_UNSET; durationUs = C.TIME_UNSET; - packetBuffer = new ParsableByteArray(DURATION_READ_BYTES); + packetBuffer = new ParsableByteArray(TIMESTAMP_SEARCH_BYTES); } /** Returns true if a PS duration has been read. */ @@ -136,16 +136,16 @@ import java.io.IOException; private int readFirstScrValue(ExtractorInput input, PositionHolder seekPositionHolder) throws IOException, InterruptedException { - if (input.getPosition() != 0) { - seekPositionHolder.position = 0; + int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength()); + int searchStartPosition = 0; + if (input.getPosition() != searchStartPosition) { + seekPositionHolder.position = searchStartPosition; return Extractor.RESULT_SEEK; } - int bytesToRead = (int) Math.min(DURATION_READ_BYTES, input.getLength()); input.resetPeekPosition(); - input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToRead); - packetBuffer.setPosition(0); - packetBuffer.setLimit(bytesToRead); + input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); + packetBuffer.reset(bytesToSearch); firstScrValue = readFirstScrValueFromBuffer(packetBuffer); isFirstScrValueRead = true; @@ -172,17 +172,17 @@ import java.io.IOException; private int readLastScrValue(ExtractorInput input, PositionHolder seekPositionHolder) throws IOException, InterruptedException { - int bytesToRead = (int) Math.min(DURATION_READ_BYTES, input.getLength()); - long bufferStartStreamPosition = input.getLength() - bytesToRead; - if (input.getPosition() != bufferStartStreamPosition) { - seekPositionHolder.position = bufferStartStreamPosition; + long inputLength = input.getLength(); + int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, inputLength); + long searchStartPosition = inputLength - bytesToSearch; + if (input.getPosition() != searchStartPosition) { + seekPositionHolder.position = searchStartPosition; return Extractor.RESULT_SEEK; } input.resetPeekPosition(); - input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToRead); - packetBuffer.setPosition(0); - packetBuffer.setLimit(bytesToRead); + input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); + packetBuffer.reset(bytesToSearch); lastScrValue = readLastScrValueFromBuffer(packetBuffer); isLastScrValueRead = true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java index 29aa0d55d2..e9b051592d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java @@ -33,10 +33,8 @@ import java.io.IOException; /* package */ final class TsBinarySearchSeeker extends BinarySearchSeeker { private static final long SEEK_TOLERANCE_US = 100_000; - private static final int MINIMUM_SEARCH_RANGE_BYTES = TsExtractor.TS_PACKET_SIZE * 5; - private static final int TIMESTAMP_SEARCH_PACKETS = 200; - private static final int TIMESTAMP_SEARCH_BYTES = - TsExtractor.TS_PACKET_SIZE * TIMESTAMP_SEARCH_PACKETS; + private static final int MINIMUM_SEARCH_RANGE_BYTES = 5 * TsExtractor.TS_PACKET_SIZE; + private static final int TIMESTAMP_SEARCH_BYTES = 200 * TsExtractor.TS_PACKET_SIZE; public TsBinarySearchSeeker( TimestampAdjuster pcrTimestampAdjuster, long streamDurationUs, long inputLength, int pcrPid) { @@ -56,10 +54,10 @@ import java.io.IOException; * A {@link TimestampSeeker} implementation that looks for a given PCR timestamp at a given * position in a TS stream. * - *

Given a PCR timestamp, and a position within a TS stream, this seeker will try to read up to - * {@link #TIMESTAMP_SEARCH_PACKETS} TS packets from that stream position, look for all packet - * with PID equals to PCR_PID, and then compare the PCR timestamps (if available) of these packets - * vs the target timestamp. + *

Given a PCR timestamp, and a position within a TS stream, this seeker will peek up to {@link + * #TIMESTAMP_SEARCH_BYTES} from that stream position, look for all packets with PID equal to + * PCR_PID, and then compare the PCR timestamps (if available) of these packets to the target + * timestamp. */ private static final class TsPcrSeeker implements TimestampSeeker { @@ -78,10 +76,10 @@ import java.io.IOException; ExtractorInput input, long targetTimestamp, OutputFrameHolder outputFrameHolder) throws IOException, InterruptedException { long inputPosition = input.getPosition(); - int bytesToRead = - (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - input.getPosition()); - packetBuffer.reset(bytesToRead); - input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToRead); + int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition); + + input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); + packetBuffer.reset(bytesToSearch); return searchForPcrValueInBuffer(packetBuffer, targetTimestamp, inputPosition); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java index 350337cc86..4401083324 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java @@ -35,8 +35,7 @@ import java.io.IOException; */ /* package */ final class TsDurationReader { - private static final int DURATION_READ_PACKETS = 200; - private static final int DURATION_READ_BYTES = TsExtractor.TS_PACKET_SIZE * DURATION_READ_PACKETS; + private static final int TIMESTAMP_SEARCH_BYTES = 200 * TsExtractor.TS_PACKET_SIZE; private final TimestampAdjuster pcrTimestampAdjuster; private final ParsableByteArray packetBuffer; @@ -54,7 +53,7 @@ import java.io.IOException; firstPcrValue = C.TIME_UNSET; lastPcrValue = C.TIME_UNSET; durationUs = C.TIME_UNSET; - packetBuffer = new ParsableByteArray(DURATION_READ_BYTES); + packetBuffer = new ParsableByteArray(TIMESTAMP_SEARCH_BYTES); } /** Returns true if a TS duration has been read. */ @@ -124,16 +123,16 @@ import java.io.IOException; private int readFirstPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid) throws IOException, InterruptedException { - if (input.getPosition() != 0) { - seekPositionHolder.position = 0; + int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength()); + int searchStartPosition = 0; + if (input.getPosition() != searchStartPosition) { + seekPositionHolder.position = searchStartPosition; return Extractor.RESULT_SEEK; } - int bytesToRead = (int) Math.min(DURATION_READ_BYTES, input.getLength()); input.resetPeekPosition(); - input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToRead); - packetBuffer.setPosition(0); - packetBuffer.setLimit(bytesToRead); + input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); + packetBuffer.reset(bytesToSearch); firstPcrValue = readFirstPcrValueFromBuffer(packetBuffer, pcrPid); isFirstPcrValueRead = true; @@ -159,17 +158,17 @@ import java.io.IOException; private int readLastPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid) throws IOException, InterruptedException { - int bytesToRead = (int) Math.min(DURATION_READ_BYTES, input.getLength()); - long bufferStartStreamPosition = input.getLength() - bytesToRead; - if (input.getPosition() != bufferStartStreamPosition) { - seekPositionHolder.position = bufferStartStreamPosition; + long inputLength = input.getLength(); + int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, inputLength); + long searchStartPosition = inputLength - bytesToSearch; + if (input.getPosition() != searchStartPosition) { + seekPositionHolder.position = searchStartPosition; return Extractor.RESULT_SEEK; } input.resetPeekPosition(); - input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToRead); - packetBuffer.setPosition(0); - packetBuffer.setLimit(bytesToRead); + input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); + packetBuffer.reset(bytesToSearch); lastPcrValue = readLastPcrValueFromBuffer(packetBuffer, pcrPid); isLastPcrValueRead = true; From 84fc24492fa20b26393a910bb68f239c0c0d2f57 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 14 Nov 2018 07:13:10 -0800 Subject: [PATCH 067/210] DASH: Fix detection of end of live events The remaining work is to split Window.isDynamic so that it's possible to represent a window that wont be appended to, but may still be removed from. Issue: #4780 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=221439220 --- RELEASENOTES.md | 2 + .../google/android/exoplayer2/Timeline.java | 9 ++- .../source/dash/DashMediaSource.java | 25 +++----- .../source/dash/DefaultDashChunkSource.java | 15 ++--- .../source/dash/PlayerEmsgHandler.java | 60 ++++--------------- 5 files changed, 34 insertions(+), 77 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ecc967f016..27644c609c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### 2.9.2 ### +* DASH: Fix detecting the end of live events + ([#4780](https://github.com/google/ExoPlayer/issues/4780)). * Include channel count in audio capabilities check ([#4690](https://github.com/google/ExoPlayer/issues/4690)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index 1639920aaa..bb7f027726 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -139,9 +139,12 @@ public abstract class Timeline { */ public boolean isSeekable; - /** - * Whether this window may change when the timeline is updated. - */ + // TODO: Split this to better describe which parts of the window might change. For example it + // should be possible to individually determine whether the start and end positions of the + // window may change relative to the underlying periods. For an example of where it's useful to + // know that the end position is fixed whilst the start position may still change, see: + // https://github.com/google/ExoPlayer/issues/4780. + /** Whether this window may change when the timeline is updated. */ public boolean isDynamic; /** diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 0c05be873a..8ee859b8bd 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -376,7 +376,6 @@ public final class DashMediaSource extends BaseMediaSource { private int staleManifestReloadAttempt; private long expiredManifestPublishTimeUs; - private boolean dynamicMediaPresentationEnded; private int firstPeriodId; @@ -679,7 +678,6 @@ public final class DashMediaSource extends BaseMediaSource { elapsedRealtimeOffsetMs = 0; staleManifestReloadAttempt = 0; expiredManifestPublishTimeUs = C.TIME_UNSET; - dynamicMediaPresentationEnded = false; firstPeriodId = 0; periodsById.clear(); } @@ -691,10 +689,6 @@ public final class DashMediaSource extends BaseMediaSource { startLoadingManifest(); } - /* package */ void onDashLiveMediaPresentationEndSignalEncountered() { - this.dynamicMediaPresentationEnded = true; - } - /* package */ void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs) { if (this.expiredManifestPublishTimeUs == C.TIME_UNSET || this.expiredManifestPublishTimeUs < expiredManifestPublishTimeUs) { @@ -734,9 +728,8 @@ public final class DashMediaSource extends BaseMediaSource { // behind. Log.w(TAG, "Loaded out of sync manifest"); isManifestStale = true; - } else if (dynamicMediaPresentationEnded - || (expiredManifestPublishTimeUs != C.TIME_UNSET - && newManifest.publishTimeMs * 1000 <= expiredManifestPublishTimeUs)) { + } else if (expiredManifestPublishTimeUs != C.TIME_UNSET + && newManifest.publishTimeMs * 1000 <= expiredManifestPublishTimeUs) { // If we receive a dynamic manifest that's older than expected (i.e. its publish time has // expired, or it's dynamic and we know the presentation has ended), then this manifest is // stale. @@ -745,8 +738,6 @@ public final class DashMediaSource extends BaseMediaSource { "Loaded stale dynamic manifest: " + newManifest.publishTimeMs + ", " - + dynamicMediaPresentationEnded - + ", " + expiredManifestPublishTimeUs); isManifestStale = true; } @@ -763,7 +754,6 @@ public final class DashMediaSource extends BaseMediaSource { staleManifestReloadAttempt = 0; } - manifest = newManifest; manifestLoadPending &= manifest.dynamic; manifestLoadStartTimestampMs = elapsedRealtimeMs - loadDurationMs; @@ -1170,12 +1160,16 @@ public final class DashMediaSource extends BaseMediaSource { long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs( defaultPositionProjectionUs); Object tag = setTag ? windowTag : null; + boolean isDynamic = + manifest.dynamic + && manifest.minUpdatePeriodMs != C.TIME_UNSET + && manifest.durationMs == C.TIME_UNSET; return window.set( tag, presentationStartTimeMs, windowStartTimeMs, /* isSeekable= */ true, - manifest.dynamic, + isDynamic, windowDefaultStartPositionUs, windowDurationUs, /* firstPeriodIndex= */ 0, @@ -1253,11 +1247,6 @@ public final class DashMediaSource extends BaseMediaSource { public void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs) { DashMediaSource.this.onDashManifestPublishTimeExpired(expiredManifestPublishTimeUs); } - - @Override - public void onDashLiveMediaPresentationEndSignalEncountered() { - DashMediaSource.this.onDashLiveMediaPresentationEndSignalEncountered(); - } } private final class ManifestCallback implements Loader.Callback> { diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 1ea25ecc36..5e20fb769c 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -318,9 +318,12 @@ public class DefaultDashChunkSource implements DashChunkSource { } } + long periodDurationUs = representationHolder.periodDurationUs; + boolean periodEnded = periodDurationUs != C.TIME_UNSET; + if (representationHolder.getSegmentCount() == 0) { // The index doesn't define any segments. - out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1); + out.endOfStream = periodEnded; return; } @@ -343,17 +346,15 @@ public class DefaultDashChunkSource implements DashChunkSource { fatalError = new BehindLiveWindowException(); return; } + if (segmentNum > lastAvailableSegmentNum || (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) { - // The segment is beyond the end of the period. We know the period will not be extended if the - // manifest is static, or if there's a period after this one. - out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1); + // The segment is beyond the end of the period. + out.endOfStream = periodEnded; return; } - long periodDurationUs = representationHolder.periodDurationUs; - if (periodDurationUs != C.TIME_UNSET - && representationHolder.getSegmentStartTimeUs(segmentNum) >= periodDurationUs) { + if (periodEnded && representationHolder.getSegmentStartTimeUs(segmentNum) >= periodDurationUs) { // The period duration clips the period to a position before the segment. out.endOfStream = true; return; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java index e657d126bf..be299308a9 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java @@ -60,8 +60,7 @@ import java.util.TreeMap; */ public final class PlayerEmsgHandler implements Handler.Callback { - private static final int EMSG_MEDIA_PRESENTATION_ENDED = 1; - private static final int EMSG_MANIFEST_EXPIRED = 2; + private static final int EMSG_MANIFEST_EXPIRED = 1; /** Callbacks for player emsg events encountered during DASH live stream. */ public interface PlayerEmsgCallback { @@ -75,9 +74,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { * @param expiredManifestPublishTimeUs The manifest publish time that has been expired. */ void onDashManifestPublishTimeExpired(long expiredManifestPublishTimeUs); - - /** Called when a media presentation end signal is encountered during live stream. * */ - void onDashLiveMediaPresentationEndSignalEncountered(); } private final Allocator allocator; @@ -88,7 +84,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { private DashManifest manifest; - private boolean dynamicMediaPresentationEnded; private long expiredManifestPublishTimeUs; private long lastLoadedChunkEndTimeUs; private long lastLoadedChunkEndTimeBeforeRefreshUs; @@ -134,21 +129,15 @@ public final class PlayerEmsgHandler implements Handler.Callback { return true; } boolean manifestRefreshNeeded = false; - if (dynamicMediaPresentationEnded) { - // The manifest we have is dynamic, but we know a non-dynamic one representing the final state - // should be available. - manifestRefreshNeeded = true; - } else { - // Find the smallest publishTime (greater than or equal to the current manifest's publish - // time) that has a corresponding expiry time. - Map.Entry expiredEntry = ceilingExpiryEntryForPublishTime(manifest.publishTimeMs); - if (expiredEntry != null) { - long expiredPointUs = expiredEntry.getValue(); - if (expiredPointUs < presentationPositionUs) { - expiredManifestPublishTimeUs = expiredEntry.getKey(); - notifyManifestPublishTimeExpired(); - manifestRefreshNeeded = true; - } + // Find the smallest publishTime (greater than or equal to the current manifest's publish time) + // that has a corresponding expiry time. + Map.Entry expiredEntry = ceilingExpiryEntryForPublishTime(manifest.publishTimeMs); + if (expiredEntry != null) { + long expiredPointUs = expiredEntry.getValue(); + if (expiredPointUs < presentationPositionUs) { + expiredManifestPublishTimeUs = expiredEntry.getKey(); + notifyManifestPublishTimeExpired(); + manifestRefreshNeeded = true; } } if (manifestRefreshNeeded) { @@ -221,9 +210,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { return true; } switch (message.what) { - case (EMSG_MEDIA_PRESENTATION_ENDED): - handleMediaPresentationEndedMessageEncountered(); - return true; case (EMSG_MANIFEST_EXPIRED): ManifestExpiryEventInfo messageObj = (ManifestExpiryEventInfo) message.obj; handleManifestExpiredMessage( @@ -248,11 +234,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { } } - private void handleMediaPresentationEndedMessageEncountered() { - dynamicMediaPresentationEnded = true; - notifySourceMediaPresentationEnded(); - } - private @Nullable Map.Entry ceilingExpiryEntryForPublishTime(long publishTimeMs) { return manifestPublishTimeToExpiryTimeUs.ceilingEntry(publishTimeMs); } @@ -273,10 +254,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { playerEmsgCallback.onDashManifestPublishTimeExpired(expiredManifestPublishTimeUs); } - private void notifySourceMediaPresentationEnded() { - playerEmsgCallback.onDashLiveMediaPresentationEndSignalEncountered(); - } - /** Requests DASH media manifest to be refreshed if necessary. */ private void maybeNotifyDashManifestRefreshNeeded() { if (lastLoadedChunkEndTimeBeforeRefreshUs != C.TIME_UNSET @@ -298,12 +275,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { } } - private static boolean isMessageSignalingMediaPresentationEnded(EventMessage eventMessage) { - // According to section 4.5.2.1 DASH-IF IOP, if both presentation time delta and event duration - // are zero, the media presentation is ended. - return eventMessage.presentationTimeUs == 0 && eventMessage.durationMs == 0; - } - /** Handles emsg messages for a specific track for the player. */ public final class PlayerTrackEmsgHandler implements TrackOutput { @@ -413,16 +384,7 @@ public final class PlayerEmsgHandler implements Handler.Callback { if (manifestPublishTimeMsInEmsg == C.TIME_UNSET) { return; } - - if (isMessageSignalingMediaPresentationEnded(eventMessage)) { - onMediaPresentationEndedMessageEncountered(); - } else { - onManifestExpiredMessageEncountered(eventTimeUs, manifestPublishTimeMsInEmsg); - } - } - - private void onMediaPresentationEndedMessageEncountered() { - handler.sendMessage(handler.obtainMessage(EMSG_MEDIA_PRESENTATION_ENDED)); + onManifestExpiredMessageEncountered(eventTimeUs, manifestPublishTimeMsInEmsg); } private void onManifestExpiredMessageEncountered( From c08edc5e0a065790e115d79a465317cc656f39d1 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 14 Nov 2018 08:40:42 -0800 Subject: [PATCH 068/210] Workaround for TS seeking - Increase the search window size to fix TS seeking for problematic media we've had provided to us. - As per my comments on the issue, we should look at doing more here to better fix the problem. This will solve the worst of the immediate problem, however. - The memory usage is non-trivial, particularly with the increased search window size. I've made the allocations only live whilst determining duration and seeking to address this. I've done the same for PS just for consistency. Issue: #5097 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=221449988 --- RELEASENOTES.md | 2 ++ .../extractor/BinarySearchSeeker.java | 4 ++++ .../extractor/ts/PsBinarySearchSeeker.java | 10 ++++++-- .../extractor/ts/PsDurationReader.java | 8 ++++--- .../extractor/ts/TsBinarySearchSeeker.java | 12 +++++++--- .../extractor/ts/TsDurationReader.java | 10 ++++---- .../exoplayer2/util/ParsableByteArray.java | 24 ++++++++++++------- .../core/src/test/assets/ts/sample.ts.1.dump | 18 ++++++++------ .../core/src/test/assets/ts/sample.ts.2.dump | 18 ++++++++------ 9 files changed, 72 insertions(+), 34 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 27644c609c..35e51076e5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### 2.9.2 ### +* Support seeking for a wider range of MPEG-TS streams + ([#5097](https://github.com/google/ExoPlayer/issues/5097)). * DASH: Fix detecting the end of live events ([#4780](https://github.com/google/ExoPlayer/issues/4780)). * Include channel count in audio capabilities check diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java index efdeddbdce..e8b6e736ba 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/BinarySearchSeeker.java @@ -58,6 +58,9 @@ public abstract class BinarySearchSeeker { TimestampSearchResult searchForTimestamp( ExtractorInput input, long targetTimestamp, OutputFrameHolder outputFrameHolder) throws IOException, InterruptedException; + + /** Called when a seek operation finishes. */ + default void onSeekFinished() {} } /** @@ -255,6 +258,7 @@ public abstract class BinarySearchSeeker { protected final void markSeekOperationFinished(boolean foundTargetFrame, long resultPosition) { seekOperationParams = null; + timestampSeeker.onSeekFinished(); onSeekOperationFinished(foundTargetFrame, resultPosition); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java index 1180dd486e..4efd38b7eb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.extractor.BinarySearchSeeker; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; /** @@ -64,7 +65,7 @@ import java.io.IOException; private PsScrSeeker(TimestampAdjuster scrTimestampAdjuster) { this.scrTimestampAdjuster = scrTimestampAdjuster; - packetBuffer = new ParsableByteArray(TIMESTAMP_SEARCH_BYTES); + packetBuffer = new ParsableByteArray(); } @Override @@ -74,12 +75,17 @@ import java.io.IOException; long inputPosition = input.getPosition(); int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition); - input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); packetBuffer.reset(bytesToSearch); + input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); return searchForScrValueInBuffer(packetBuffer, targetTimestamp, inputPosition); } + @Override + public void onSeekFinished() { + packetBuffer.reset(Util.EMPTY_BYTE_ARRAY); + } + private TimestampSearchResult searchForScrValueInBuffer( ParsableByteArray packetBuffer, long targetScrTimeUs, long bufferStartOffset) { int startOfLastPacketPosition = C.POSITION_UNSET; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java index 077a35f4a7..b0cdf7eb79 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; /** @@ -56,7 +57,7 @@ import java.io.IOException; firstScrValue = C.TIME_UNSET; lastScrValue = C.TIME_UNSET; durationUs = C.TIME_UNSET; - packetBuffer = new ParsableByteArray(TIMESTAMP_SEARCH_BYTES); + packetBuffer = new ParsableByteArray(); } /** Returns true if a PS duration has been read. */ @@ -129,6 +130,7 @@ import java.io.IOException; } private int finishReadDuration(ExtractorInput input) { + packetBuffer.reset(Util.EMPTY_BYTE_ARRAY); isDurationRead = true; input.resetPeekPosition(); return Extractor.RESULT_CONTINUE; @@ -143,9 +145,9 @@ import java.io.IOException; return Extractor.RESULT_SEEK; } + packetBuffer.reset(bytesToSearch); input.resetPeekPosition(); input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); - packetBuffer.reset(bytesToSearch); firstScrValue = readFirstScrValueFromBuffer(packetBuffer); isFirstScrValueRead = true; @@ -180,9 +182,9 @@ import java.io.IOException; return Extractor.RESULT_SEEK; } + packetBuffer.reset(bytesToSearch); input.resetPeekPosition(); input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); - packetBuffer.reset(bytesToSearch); lastScrValue = readLastScrValueFromBuffer(packetBuffer); isLastScrValueRead = true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java index e9b051592d..ea2519d2e9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsBinarySearchSeeker.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.extractor.BinarySearchSeeker; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; /** @@ -34,7 +35,7 @@ import java.io.IOException; private static final long SEEK_TOLERANCE_US = 100_000; private static final int MINIMUM_SEARCH_RANGE_BYTES = 5 * TsExtractor.TS_PACKET_SIZE; - private static final int TIMESTAMP_SEARCH_BYTES = 200 * TsExtractor.TS_PACKET_SIZE; + private static final int TIMESTAMP_SEARCH_BYTES = 600 * TsExtractor.TS_PACKET_SIZE; public TsBinarySearchSeeker( TimestampAdjuster pcrTimestampAdjuster, long streamDurationUs, long inputLength, int pcrPid) { @@ -68,7 +69,7 @@ import java.io.IOException; public TsPcrSeeker(int pcrPid, TimestampAdjuster pcrTimestampAdjuster) { this.pcrPid = pcrPid; this.pcrTimestampAdjuster = pcrTimestampAdjuster; - packetBuffer = new ParsableByteArray(TIMESTAMP_SEARCH_BYTES); + packetBuffer = new ParsableByteArray(); } @Override @@ -78,8 +79,8 @@ import java.io.IOException; long inputPosition = input.getPosition(); int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition); - input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); packetBuffer.reset(bytesToSearch); + input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); return searchForPcrValueInBuffer(packetBuffer, targetTimestamp, inputPosition); } @@ -131,5 +132,10 @@ import java.io.IOException; return TimestampSearchResult.NO_TIMESTAMP_IN_RANGE_RESULT; } } + + @Override + public void onSeekFinished() { + packetBuffer.reset(Util.EMPTY_BYTE_ARRAY); + } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java index 4401083324..804a643414 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; /** @@ -35,7 +36,7 @@ import java.io.IOException; */ /* package */ final class TsDurationReader { - private static final int TIMESTAMP_SEARCH_BYTES = 200 * TsExtractor.TS_PACKET_SIZE; + private static final int TIMESTAMP_SEARCH_BYTES = 600 * TsExtractor.TS_PACKET_SIZE; private final TimestampAdjuster pcrTimestampAdjuster; private final ParsableByteArray packetBuffer; @@ -53,7 +54,7 @@ import java.io.IOException; firstPcrValue = C.TIME_UNSET; lastPcrValue = C.TIME_UNSET; durationUs = C.TIME_UNSET; - packetBuffer = new ParsableByteArray(TIMESTAMP_SEARCH_BYTES); + packetBuffer = new ParsableByteArray(); } /** Returns true if a TS duration has been read. */ @@ -116,6 +117,7 @@ import java.io.IOException; } private int finishReadDuration(ExtractorInput input) { + packetBuffer.reset(Util.EMPTY_BYTE_ARRAY); isDurationRead = true; input.resetPeekPosition(); return Extractor.RESULT_CONTINUE; @@ -130,9 +132,9 @@ import java.io.IOException; return Extractor.RESULT_SEEK; } + packetBuffer.reset(bytesToSearch); input.resetPeekPosition(); input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); - packetBuffer.reset(bytesToSearch); firstPcrValue = readFirstPcrValueFromBuffer(packetBuffer, pcrPid); isFirstPcrValueRead = true; @@ -166,9 +168,9 @@ import java.io.IOException; return Extractor.RESULT_SEEK; } + packetBuffer.reset(bytesToSearch); input.resetPeekPosition(); input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); - packetBuffer.reset(bytesToSearch); lastPcrValue = readLastPcrValueFromBuffer(packetBuffer, pcrPid); isLastPcrValueRead = true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java index 464061153f..b928ffc02b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java @@ -67,6 +67,12 @@ public final class ParsableByteArray { this.limit = limit; } + /** Sets the position and limit to zero. */ + public void reset() { + position = 0; + limit = 0; + } + /** * Resets the position to zero and the limit to the specified value. If the limit exceeds the * capacity, {@code data} is replaced with a new array of sufficient size. @@ -77,6 +83,16 @@ public final class ParsableByteArray { reset(capacity() < limit ? new byte[limit] : data, limit); } + /** + * Updates the instance to wrap {@code data}, and resets the position to zero and the limit to + * {@code data.length}. + * + * @param data The array to wrap. + */ + public void reset(byte[] data) { + reset(data, data.length); + } + /** * Updates the instance to wrap {@code data}, and resets the position to zero. * @@ -89,14 +105,6 @@ public final class ParsableByteArray { position = 0; } - /** - * Sets the position and limit to zero. - */ - public void reset() { - position = 0; - limit = 0; - } - /** * Returns the number of bytes yet to be read. */ diff --git a/library/core/src/test/assets/ts/sample.ts.1.dump b/library/core/src/test/assets/ts/sample.ts.1.dump index 7454a02141..5c361e1246 100644 --- a/library/core/src/test/assets/ts/sample.ts.1.dump +++ b/library/core/src/test/assets/ts/sample.ts.1.dump @@ -26,10 +26,14 @@ track 256: drmInitData = - initializationData: data = length 22, hash CE183139 - total output bytes = 24315 - sample count = 1 + total output bytes = 45026 + sample count = 2 sample 0: - time = 55611 + time = 55610 + flags = 1 + data = length 20711, hash 34341E8 + sample 1: + time = 88977 flags = 0 data = length 18112, hash EC44B35B track 257: @@ -57,19 +61,19 @@ track 257: total output bytes = 5015 sample count = 4 sample 0: - time = 11333 + time = 44699 flags = 1 data = length 1253, hash 727FD1C6 sample 1: - time = 37455 + time = 70821 flags = 1 data = length 1254, hash 73FB07B8 sample 2: - time = 63578 + time = 96944 flags = 1 data = length 1254, hash 73FB07B8 sample 3: - time = 89700 + time = 123066 flags = 1 data = length 1254, hash 73FB07B8 track 8448: diff --git a/library/core/src/test/assets/ts/sample.ts.2.dump b/library/core/src/test/assets/ts/sample.ts.2.dump index c7cef05b93..cec91ae2b9 100644 --- a/library/core/src/test/assets/ts/sample.ts.2.dump +++ b/library/core/src/test/assets/ts/sample.ts.2.dump @@ -26,10 +26,14 @@ track 256: drmInitData = - initializationData: data = length 22, hash CE183139 - total output bytes = 24315 - sample count = 1 + total output bytes = 45026 + sample count = 2 sample 0: - time = 77855 + time = 77854 + flags = 1 + data = length 20711, hash 34341E8 + sample 1: + time = 111221 flags = 0 data = length 18112, hash EC44B35B track 257: @@ -57,19 +61,19 @@ track 257: total output bytes = 5015 sample count = 4 sample 0: - time = 33577 + time = 66943 flags = 1 data = length 1253, hash 727FD1C6 sample 1: - time = 59699 + time = 93065 flags = 1 data = length 1254, hash 73FB07B8 sample 2: - time = 85822 + time = 119188 flags = 1 data = length 1254, hash 73FB07B8 sample 3: - time = 111944 + time = 145310 flags = 1 data = length 1254, hash 73FB07B8 track 8448: From 30941c41c3a2ca316eb78d7c4b9185983ae63183 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 15 Nov 2018 09:40:33 -0800 Subject: [PATCH 069/210] Ensure DefaultLoadControl.Builder is single-use. This is needed because the allocator can't be reused for example. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=221638233 --- .../android/exoplayer2/DefaultLoadControl.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java index 97a56b844c..032b932191 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java @@ -79,6 +79,7 @@ public class DefaultLoadControl implements LoadControl { private PriorityTaskManager priorityTaskManager; private int backBufferDurationMs; private boolean retainBackBufferFromKeyframe; + private boolean createDefaultLoadControlCalled; /** Constructs a new instance. */ public Builder() { @@ -99,8 +100,10 @@ public class DefaultLoadControl implements LoadControl { * * @param allocator The {@link DefaultAllocator}. * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. */ public Builder setAllocator(DefaultAllocator allocator) { + Assertions.checkState(!createDefaultLoadControlCalled); this.allocator = allocator; return this; } @@ -118,12 +121,14 @@ public class DefaultLoadControl implements LoadControl { * for playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be * caused by buffer depletion rather than a user action. * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. */ public Builder setBufferDurationsMs( int minBufferMs, int maxBufferMs, int bufferForPlaybackMs, int bufferForPlaybackAfterRebufferMs) { + Assertions.checkState(!createDefaultLoadControlCalled); this.minBufferMs = minBufferMs; this.maxBufferMs = maxBufferMs; this.bufferForPlaybackMs = bufferForPlaybackMs; @@ -137,8 +142,10 @@ public class DefaultLoadControl implements LoadControl { * * @param targetBufferBytes The target buffer size in bytes. * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. */ public Builder setTargetBufferBytes(int targetBufferBytes) { + Assertions.checkState(!createDefaultLoadControlCalled); this.targetBufferBytes = targetBufferBytes; return this; } @@ -150,8 +157,10 @@ public class DefaultLoadControl implements LoadControl { * @param prioritizeTimeOverSizeThresholds Whether the load control prioritizes buffer time * constraints over buffer size constraints. * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. */ public Builder setPrioritizeTimeOverSizeThresholds(boolean prioritizeTimeOverSizeThresholds) { + Assertions.checkState(!createDefaultLoadControlCalled); this.prioritizeTimeOverSizeThresholds = prioritizeTimeOverSizeThresholds; return this; } @@ -161,8 +170,10 @@ public class DefaultLoadControl implements LoadControl { * * @param priorityTaskManager The {@link PriorityTaskManager} to use. * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. */ public Builder setPriorityTaskManager(PriorityTaskManager priorityTaskManager) { + Assertions.checkState(!createDefaultLoadControlCalled); this.priorityTaskManager = priorityTaskManager; return this; } @@ -175,8 +186,10 @@ public class DefaultLoadControl implements LoadControl { * @param retainBackBufferFromKeyframe Whether the back buffer is retained from the previous * keyframe. * @return This builder, for convenience. + * @throws IllegalStateException If {@link #createDefaultLoadControl()} has already been called. */ public Builder setBackBuffer(int backBufferDurationMs, boolean retainBackBufferFromKeyframe) { + Assertions.checkState(!createDefaultLoadControlCalled); this.backBufferDurationMs = backBufferDurationMs; this.retainBackBufferFromKeyframe = retainBackBufferFromKeyframe; return this; @@ -184,6 +197,7 @@ public class DefaultLoadControl implements LoadControl { /** Creates a {@link DefaultLoadControl}. */ public DefaultLoadControl createDefaultLoadControl() { + createDefaultLoadControlCalled = true; if (allocator == null) { allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); } From ec43ed7e59b3b7aaec2c031595cd6517a8f3ac0d Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 15 Nov 2018 09:46:19 -0800 Subject: [PATCH 070/210] Improve DefaultLoadControl.shouldContinueLoading for the minBuffer=maxBuffer case. Currently no path may be chosen if minBufferUs == maxBufferUs == bufferedDurationUs. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=221639199 --- .../android/exoplayer2/DefaultLoadControl.java | 2 +- .../android/exoplayer2/DefaultLoadControlTest.java | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java index 032b932191..c109ed81c1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java @@ -385,7 +385,7 @@ public class DefaultLoadControl implements LoadControl { } if (bufferedDurationUs < minBufferUs) { isBuffering = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached; - } else if (bufferedDurationUs > maxBufferUs || targetBufferSizeReached) { + } else if (bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) { isBuffering = false; } // Else don't change the buffering state if (priorityTaskManager != null && isBuffering != wasBuffering) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java b/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java index b066cc263a..93ec5e0264 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/DefaultLoadControlTest.java @@ -48,15 +48,15 @@ public class DefaultLoadControlTest { createDefaultLoadControl(); assertThat(loadControl.shouldContinueLoading(/* bufferedDurationUs= */ 0, SPEED)).isTrue(); assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isTrue(); - assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isTrue(); - assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US + 1, SPEED)).isFalse(); + assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US - 1, SPEED)).isTrue(); + assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse(); } @Test public void testShouldNotContinueLoadingOnceBufferingStopped_untilBelowMinBuffer() { createDefaultLoadControl(); - assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US + 1, SPEED)).isFalse(); assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse(); + assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US - 1, SPEED)).isFalse(); assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isFalse(); assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US - 1, SPEED)).isTrue(); } @@ -69,7 +69,7 @@ public class DefaultLoadControlTest { assertThat(loadControl.shouldContinueLoading(/* bufferedDurationUs= */ 0, SPEED)).isTrue(); assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US - 1, SPEED)).isTrue(); assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isFalse(); - assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US + 1, SPEED)).isFalse(); + assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse(); } @Test @@ -82,7 +82,7 @@ public class DefaultLoadControlTest { assertThat(loadControl.shouldContinueLoading(/* bufferedDurationUs= */ 0, SPEED)).isFalse(); assertThat(loadControl.shouldContinueLoading(MIN_BUFFER_US, SPEED)).isFalse(); - assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US + 1, SPEED)).isFalse(); + assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, SPEED)).isFalse(); } @Test @@ -100,7 +100,7 @@ public class DefaultLoadControlTest { public void testShouldNotContinueLoadingWithMaxBufferReached_inFastPlayback() { createDefaultLoadControl(); - assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US + 1, /* playbackSpeed= */ 100f)) + assertThat(loadControl.shouldContinueLoading(MAX_BUFFER_US, /* playbackSpeed= */ 100f)) .isFalse(); } From 269615e62f9675ac84728132cfa5562a74945d4b Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 19 Nov 2018 09:23:11 -0800 Subject: [PATCH 071/210] Update IMA SDK and Play Services Ads versions Since version 17.0 play-services-ads requires specifying AD_MANAGER_APP=true in AndroidManifest.xml, so add this in the IMA extension's manifest. See also https://developers.google.com/ad-manager/mobile-ads-sdk/android/quick-start. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=222087771 --- extensions/ima/build.gradle | 6 +++--- extensions/ima/src/main/AndroidManifest.xml | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/extensions/ima/build.gradle b/extensions/ima/build.gradle index 7fc7935cac..22196ff3ab 100644 --- a/extensions/ima/build.gradle +++ b/extensions/ima/build.gradle @@ -31,13 +31,13 @@ android { } dependencies { - api 'com.google.ads.interactivemedia.v3:interactivemedia:3.9.4' + api 'com.google.ads.interactivemedia.v3:interactivemedia:3.10.2' implementation project(modulePrefix + 'library-core') - implementation 'com.google.android.gms:play-services-ads:15.0.1' + implementation 'com.google.android.gms:play-services-ads:17.1.1' // These dependencies are necessary to force the supportLibraryVersion of // com.android.support:support-v4 and com.android.support:customtabs to be // used. Else older versions are used, for example via: - // com.google.android.gms:play-services-ads:15.0.1 + // com.google.android.gms:play-services-ads:17.1.1 // |-- com.android.support:customtabs:26.1.0 implementation 'com.android.support:support-v4:' + supportLibraryVersion implementation 'com.android.support:customtabs:' + supportLibraryVersion diff --git a/extensions/ima/src/main/AndroidManifest.xml b/extensions/ima/src/main/AndroidManifest.xml index 1bb79ff21d..226b15cb34 100644 --- a/extensions/ima/src/main/AndroidManifest.xml +++ b/extensions/ima/src/main/AndroidManifest.xml @@ -15,6 +15,10 @@ --> - + + + + From 233ff0729dae360c6dccafe87ae02f6ab8075666 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 21 Nov 2018 04:06:51 -0800 Subject: [PATCH 072/210] Add test that DefaultDataSource is able to build an RtmpDataSource ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=222387374 --- extensions/rtmp/build.gradle | 2 + extensions/rtmp/src/test/AndroidManifest.xml | 17 +++++++ .../ext/rtmp/DefaultDataSourceTest.java | 45 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 extensions/rtmp/src/test/AndroidManifest.xml create mode 100644 extensions/rtmp/src/test/java/com/google/android/exoplayer2/ext/rtmp/DefaultDataSourceTest.java diff --git a/extensions/rtmp/build.gradle b/extensions/rtmp/build.gradle index 2f2c65980a..af02ee2eaa 100644 --- a/extensions/rtmp/build.gradle +++ b/extensions/rtmp/build.gradle @@ -33,6 +33,8 @@ dependencies { implementation project(modulePrefix + 'library-core') implementation 'net.butterflytv.utils:rtmp-client:3.0.1' implementation 'com.android.support:support-annotations:' + supportLibraryVersion + testImplementation 'junit:junit:' + junitVersion + testImplementation 'org.robolectric:robolectric:' + robolectricVersion } ext { diff --git a/extensions/rtmp/src/test/AndroidManifest.xml b/extensions/rtmp/src/test/AndroidManifest.xml new file mode 100644 index 0000000000..7eab4e2d59 --- /dev/null +++ b/extensions/rtmp/src/test/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + diff --git a/extensions/rtmp/src/test/java/com/google/android/exoplayer2/ext/rtmp/DefaultDataSourceTest.java b/extensions/rtmp/src/test/java/com/google/android/exoplayer2/ext/rtmp/DefaultDataSourceTest.java new file mode 100644 index 0000000000..f4753798b8 --- /dev/null +++ b/extensions/rtmp/src/test/java/com/google/android/exoplayer2/ext/rtmp/DefaultDataSourceTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.rtmp; + +import android.net.Uri; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.DefaultDataSource; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +/** Unit test for {@link DefaultDataSource} with RTMP URIs. */ +@RunWith(RobolectricTestRunner.class) +public final class DefaultDataSourceTest { + + @Test + public void openRtmpDataSpec_instantiatesRtmpDataSourceViaReflection() throws IOException { + DefaultDataSource dataSource = + new DefaultDataSource( + RuntimeEnvironment.application, "userAgent", /* allowCrossProtocolRedirects= */ false); + DataSpec dataSpec = new DataSpec(Uri.parse("rtmp://test.com/stream")); + try { + dataSource.open(dataSpec); + } catch (UnsatisfiedLinkError e) { + // RtmpDataSource was successfully instantiated (test run using Gradle). + } catch (UnsupportedOperationException e) { + // RtmpDataSource was successfully instantiated (test run using Blaze). + } + } +} From 56b0294ba2c6f28cdd7c6bc5d20740758174ce99 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 21 Nov 2018 05:09:46 -0800 Subject: [PATCH 073/210] Support Opus and Flac in MP4/DASH Issue: #4883 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=222392621 --- RELEASENOTES.md | 2 ++ .../exoplayer2/extractor/mp4/Atom.java | 4 +++ .../exoplayer2/extractor/mp4/AtomParsers.java | 26 +++++++++++++++++-- .../android/exoplayer2/util/MimeTypes.java | 4 ++- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 35e51076e5..fe5c832d14 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### 2.9.2 ### +* MP4: Support Opus and FLAC in the MP4 container, and in DASH + ([#4883](https://github.com/google/ExoPlayer/issues/4883)). * Support seeking for a wider range of MPEG-TS streams ([#5097](https://github.com/google/ExoPlayer/issues/5097)). * DASH: Fix detecting the end of live events diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index 3b85c3d2e3..f51c97389b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -145,6 +145,10 @@ import java.util.List; public static final int TYPE_alac = Util.getIntegerCodeForString("alac"); public static final int TYPE_alaw = Util.getIntegerCodeForString("alaw"); public static final int TYPE_ulaw = Util.getIntegerCodeForString("ulaw"); + public static final int TYPE_Opus = Util.getIntegerCodeForString("Opus"); + public static final int TYPE_dOps = Util.getIntegerCodeForString("dOps"); + public static final int TYPE_fLaC = Util.getIntegerCodeForString("fLaC"); + public static final int TYPE_dfLa = Util.getIntegerCodeForString("dfLa"); public final int type; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 0cda3fafa8..e3d56489a6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -58,6 +58,9 @@ import java.util.List; */ private static final int MAX_GAPLESS_TRIM_SIZE_SAMPLES = 3; + /** The magic signature for an Opus Identification header, as defined in RFC-7845. */ + private static final byte[] opusMagic = Util.getUtf8Bytes("OpusHead"); + /** * Parses a trak atom (defined in 14496-12). * @@ -679,7 +682,9 @@ import java.util.List; || childAtomType == Atom.TYPE__mp3 || childAtomType == Atom.TYPE_alac || childAtomType == Atom.TYPE_alaw - || childAtomType == Atom.TYPE_ulaw) { + || childAtomType == Atom.TYPE_ulaw + || childAtomType == Atom.TYPE_Opus + || childAtomType == Atom.TYPE_fLaC) { parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, language, isQuickTime, drmInitData, out, i); } else if (childAtomType == Atom.TYPE_TTML || childAtomType == Atom.TYPE_tx3g @@ -976,6 +981,10 @@ import java.util.List; mimeType = MimeTypes.AUDIO_ALAW; } else if (atomType == Atom.TYPE_ulaw) { mimeType = MimeTypes.AUDIO_MLAW; + } else if (atomType == Atom.TYPE_Opus) { + mimeType = MimeTypes.AUDIO_OPUS; + } else if (atomType == Atom.TYPE_fLaC) { + mimeType = MimeTypes.AUDIO_FLAC; } byte[] initializationData = null; @@ -1016,7 +1025,20 @@ import java.util.List; } else if (childAtomType == Atom.TYPE_alac) { initializationData = new byte[childAtomSize]; parent.setPosition(childPosition); - parent.readBytes(initializationData, 0, childAtomSize); + parent.readBytes(initializationData, /* offset= */ 0, childAtomSize); + } else if (childAtomType == Atom.TYPE_dOps) { + // Build an Opus Identification Header (defined in RFC-7845) by concatenating the Opus Magic + // Signature and the body of the dOps atom. + int childAtomBodySize = childAtomSize - Atom.HEADER_SIZE; + initializationData = new byte[opusMagic.length + childAtomBodySize]; + System.arraycopy(opusMagic, 0, initializationData, 0, opusMagic.length); + parent.setPosition(childPosition + Atom.HEADER_SIZE); + parent.readBytes(initializationData, opusMagic.length, childAtomBodySize); + } else if (childAtomSize == Atom.TYPE_dfLa) { + int childAtomBodySize = childAtomSize - Atom.FULL_HEADER_SIZE; + initializationData = new byte[childAtomBodySize]; + parent.setPosition(childPosition + Atom.FULL_HEADER_SIZE); + parent.readBytes(initializationData, /* offset= */ 0, childAtomBodySize); } childPosition += childAtomSize; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index f56aac7c70..e506ae1b19 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -207,7 +207,7 @@ public final class MimeTypes { if (codec == null) { return null; } - codec = codec.trim(); + codec = Util.toLowerInvariant(codec.trim()); if (codec.startsWith("avc1") || codec.startsWith("avc3")) { return MimeTypes.VIDEO_H264; } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { @@ -245,6 +245,8 @@ public final class MimeTypes { return MimeTypes.AUDIO_OPUS; } else if (codec.startsWith("vorbis")) { return MimeTypes.AUDIO_VORBIS; + } else if (codec.startsWith("flac")) { + return MimeTypes.AUDIO_FLAC; } else { return getCustomMimeTypeForCodec(codec); } From 473ed4cf69c3541cd82cd14e3d81d0087da05260 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 21 Nov 2018 05:58:24 -0800 Subject: [PATCH 074/210] Support removal of ranges from ShuffleOrders This allows more efficient range removals and is consistent with addition, which supports adding multiple elements in a single operation. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=222396310 --- .../source/ConcatenatingMediaSource.java | 6 +- .../exoplayer2/source/ShuffleOrder.java | 29 +++---- .../exoplayer2/source/ShuffleOrderTest.java | 78 +++++++++++-------- .../exoplayer2/testutil/FakeShuffleOrder.java | 4 +- 4 files changed, 65 insertions(+), 52 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 1b614e18a9..52b2f63759 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -570,9 +570,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource= fromIndex; index--) { - shuffleOrder = shuffleOrder.cloneAndRemove(index); - } + shuffleOrder = shuffleOrder.cloneAndRemove(fromIndex, toIndex); } for (int index = toIndex - 1; index >= fromIndex; index--) { removeMediaSourceInternal(index); @@ -581,7 +579,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource moveMessage = (MessageData) Util.castNonNull(message); - shuffleOrder = shuffleOrder.cloneAndRemove(moveMessage.index); + shuffleOrder = shuffleOrder.cloneAndRemove(moveMessage.index, moveMessage.index + 1); shuffleOrder = shuffleOrder.cloneAndInsert(moveMessage.customData, 1); moveMediaSourceInternal(moveMessage.index, moveMessage.customData); scheduleListenerNotification(moveMessage.actionOnCompletion); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java index 90e2576d76..5af9dbd20a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ShuffleOrder.java @@ -135,15 +135,16 @@ public interface ShuffleOrder { } @Override - public ShuffleOrder cloneAndRemove(int removalIndex) { - int[] newShuffled = new int[shuffled.length - 1]; - boolean foundRemovedElement = false; + public ShuffleOrder cloneAndRemove(int indexFrom, int indexToExclusive) { + int numberOfElementsToRemove = indexToExclusive - indexFrom; + int[] newShuffled = new int[shuffled.length - numberOfElementsToRemove]; + int foundElementsCount = 0; for (int i = 0; i < shuffled.length; i++) { - if (shuffled[i] == removalIndex) { - foundRemovedElement = true; + if (shuffled[i] >= indexFrom && shuffled[i] < indexToExclusive) { + foundElementsCount++; } else { - newShuffled[foundRemovedElement ? i - 1 : i] = shuffled[i] > removalIndex - ? shuffled[i] - 1 : shuffled[i]; + newShuffled[i - foundElementsCount] = + shuffled[i] >= indexFrom ? shuffled[i] - numberOfElementsToRemove : shuffled[i]; } } return new DefaultShuffleOrder(newShuffled, new Random(random.nextLong())); @@ -213,8 +214,8 @@ public interface ShuffleOrder { } @Override - public ShuffleOrder cloneAndRemove(int removalIndex) { - return new UnshuffledShuffleOrder(length - 1); + public ShuffleOrder cloneAndRemove(int indexFrom, int indexToExclusive) { + return new UnshuffledShuffleOrder(length - indexToExclusive + indexFrom); } @Override @@ -268,12 +269,14 @@ public interface ShuffleOrder { ShuffleOrder cloneAndInsert(int insertionIndex, int insertionCount); /** - * Returns a copy of the shuffle order with one element removed. + * Returns a copy of the shuffle order with a range of elements removed. * - * @param removalIndex The index of the element in the unshuffled order which is to be removed. - * @return A copy of this {@link ShuffleOrder} without the removed element. + * @param indexFrom The starting index in the unshuffled order of the range to remove. + * @param indexToExclusive The smallest index (must be greater or equal to {@code indexFrom}) that + * will not be removed. + * @return A copy of this {@link ShuffleOrder} without the elements in the removed range. */ - ShuffleOrder cloneAndRemove(int removalIndex); + ShuffleOrder cloneAndRemove(int indexFrom, int indexToExclusive); /** Returns a copy of the shuffle order with all elements removed. */ ShuffleOrder cloneAndClear(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java index 06f39409e7..430ceb87f1 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ShuffleOrderTest.java @@ -45,10 +45,32 @@ public final class ShuffleOrderTest { testCloneAndInsert(new DefaultShuffleOrder(initialLength, RANDOM_SEED), insertionPoint, 5); } } - testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 0); - testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 2); - testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 4); - testCloneAndRemove(new DefaultShuffleOrder(1, RANDOM_SEED), 0); + testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 0, 1); + testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 2, 3); + testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 4, 5); + testCloneAndRemove(new DefaultShuffleOrder(1, RANDOM_SEED), 0, 1); + testCloneAndRemove(new DefaultShuffleOrder(1000, RANDOM_SEED), 0, 1000); + testCloneAndRemove(new DefaultShuffleOrder(1000, RANDOM_SEED), 0, 999); + testCloneAndRemove(new DefaultShuffleOrder(1000, RANDOM_SEED), 0, 500); + testCloneAndRemove(new DefaultShuffleOrder(1000, RANDOM_SEED), 100, 600); + testCloneAndRemove(new DefaultShuffleOrder(1000, RANDOM_SEED), 500, 1000); + } + + @Test + public void testDefaultShuffleOrderSideloaded() { + int[] shuffledIndices = new int[] {2, 1, 0, 4, 3}; + ShuffleOrder shuffleOrder = new DefaultShuffleOrder(shuffledIndices, RANDOM_SEED); + assertThat(shuffleOrder.getFirstIndex()).isEqualTo(2); + assertThat(shuffleOrder.getLastIndex()).isEqualTo(3); + for (int i = 0; i < 4; i++) { + assertThat(shuffleOrder.getNextIndex(shuffledIndices[i])).isEqualTo(shuffledIndices[i + 1]); + } + assertThat(shuffleOrder.getNextIndex(3)).isEqualTo(C.INDEX_UNSET); + for (int i = 4; i > 0; i--) { + assertThat(shuffleOrder.getPreviousIndex(shuffledIndices[i])) + .isEqualTo(shuffledIndices[i - 1]); + } + assertThat(shuffleOrder.getPreviousIndex(2)).isEqualTo(C.INDEX_UNSET); } @Test @@ -63,10 +85,15 @@ public final class ShuffleOrderTest { testCloneAndInsert(new UnshuffledShuffleOrder(initialLength), insertionPoint, 5); } } - testCloneAndRemove(new UnshuffledShuffleOrder(5), 0); - testCloneAndRemove(new UnshuffledShuffleOrder(5), 2); - testCloneAndRemove(new UnshuffledShuffleOrder(5), 4); - testCloneAndRemove(new UnshuffledShuffleOrder(1), 0); + testCloneAndRemove(new UnshuffledShuffleOrder(5), 0, 1); + testCloneAndRemove(new UnshuffledShuffleOrder(5), 2, 3); + testCloneAndRemove(new UnshuffledShuffleOrder(5), 4, 5); + testCloneAndRemove(new UnshuffledShuffleOrder(1), 0, 1); + testCloneAndRemove(new UnshuffledShuffleOrder(1000), 0, 1000); + testCloneAndRemove(new UnshuffledShuffleOrder(1000), 0, 999); + testCloneAndRemove(new UnshuffledShuffleOrder(1000), 0, 500); + testCloneAndRemove(new UnshuffledShuffleOrder(1000), 100, 600); + testCloneAndRemove(new UnshuffledShuffleOrder(1000), 500, 1000); } @Test @@ -79,23 +106,6 @@ public final class ShuffleOrderTest { } } - @Test - public void testSideloadedShuffleOrder() { - int[] shuffledIndices = new int[] {2, 1, 0, 4, 3}; - ShuffleOrder shuffleOrder = new DefaultShuffleOrder(shuffledIndices, RANDOM_SEED); - assertThat(shuffleOrder.getFirstIndex()).isEqualTo(2); - assertThat(shuffleOrder.getLastIndex()).isEqualTo(3); - for (int i = 0; i < 4; i++) { - assertThat(shuffleOrder.getNextIndex(shuffledIndices[i])).isEqualTo(shuffledIndices[i + 1]); - } - assertThat(shuffleOrder.getNextIndex(3)).isEqualTo(C.INDEX_UNSET); - for (int i = 4; i > 0; i--) { - assertThat(shuffleOrder.getPreviousIndex(shuffledIndices[i])) - .isEqualTo(shuffledIndices[i - 1]); - } - assertThat(shuffleOrder.getPreviousIndex(2)).isEqualTo(C.INDEX_UNSET); - } - private static void assertShuffleOrderCorrectness(ShuffleOrder shuffleOrder, int length) { assertThat(shuffleOrder.getLength()).isEqualTo(length); if (length == 0) { @@ -137,22 +147,24 @@ public final class ShuffleOrderTest { } } - private static void testCloneAndRemove(ShuffleOrder shuffleOrder, int position) { - ShuffleOrder newOrder = shuffleOrder.cloneAndRemove(position); - assertShuffleOrderCorrectness(newOrder, shuffleOrder.getLength() - 1); + private static void testCloneAndRemove( + ShuffleOrder shuffleOrder, int indexFrom, int indexToExclusive) { + int numberOfElementsToRemove = indexToExclusive - indexFrom; + ShuffleOrder newOrder = shuffleOrder.cloneAndRemove(indexFrom, indexToExclusive); + assertShuffleOrderCorrectness(newOrder, shuffleOrder.getLength() - numberOfElementsToRemove); // Assert all elements still have the relative same order for (int i = 0; i < shuffleOrder.getLength(); i++) { - if (i == position) { + if (i >= indexFrom && i < indexToExclusive) { continue; } int expectedNextIndex = shuffleOrder.getNextIndex(i); - if (expectedNextIndex == position) { + while (expectedNextIndex >= indexFrom && expectedNextIndex < indexToExclusive) { expectedNextIndex = shuffleOrder.getNextIndex(expectedNextIndex); } - if (expectedNextIndex != C.INDEX_UNSET && expectedNextIndex >= position) { - expectedNextIndex--; + if (expectedNextIndex != C.INDEX_UNSET && expectedNextIndex >= indexFrom) { + expectedNextIndex -= numberOfElementsToRemove; } - int newNextIndex = newOrder.getNextIndex(i < position ? i : i - 1); + int newNextIndex = newOrder.getNextIndex(i < indexFrom ? i : i - numberOfElementsToRemove); assertThat(newNextIndex).isEqualTo(expectedNextIndex); } } diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeShuffleOrder.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeShuffleOrder.java index cb70c75bdb..4b3a0d5051 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeShuffleOrder.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeShuffleOrder.java @@ -61,8 +61,8 @@ public final class FakeShuffleOrder implements ShuffleOrder { } @Override - public ShuffleOrder cloneAndRemove(int removalIndex) { - return new FakeShuffleOrder(length - 1); + public ShuffleOrder cloneAndRemove(int indexFrom, int indexToExclusive) { + return new FakeShuffleOrder(length - indexToExclusive + indexFrom); } @Override From d075e4f91b2597e3608d6ae911a4eb34359e873b Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 21 Nov 2018 07:38:37 -0800 Subject: [PATCH 075/210] Fix HLS ID3 sniffing The input.getLength() check is invalid because the length may be unknown (i.e. if the server doesn't include a Content-Length response header when serving chunks). Issue: #5063 (tangentially related only) ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=222406347 --- .../android/exoplayer2/source/hls/HlsMediaChunk.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 0cfe31f3b9..20134e9efc 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -32,6 +32,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.Util; +import java.io.EOFException; import java.io.IOException; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -313,8 +314,10 @@ import java.util.concurrent.atomic.AtomicInteger; */ private long peekId3PrivTimestamp(ExtractorInput input) throws IOException, InterruptedException { input.resetPeekPosition(); - if (input.getLength() < Id3Decoder.ID3_HEADER_LENGTH - || !input.peekFully(id3Data.data, 0, Id3Decoder.ID3_HEADER_LENGTH, true)) { + try { + input.peekFully(id3Data.data, 0, Id3Decoder.ID3_HEADER_LENGTH); + } catch (EOFException e) { + // The input isn't long enough for there to be any ID3 data. return C.TIME_UNSET; } id3Data.reset(Id3Decoder.ID3_HEADER_LENGTH); @@ -330,9 +333,7 @@ import java.util.concurrent.atomic.AtomicInteger; id3Data.reset(requiredCapacity); System.arraycopy(data, 0, id3Data.data, 0, Id3Decoder.ID3_HEADER_LENGTH); } - if (!input.peekFully(id3Data.data, Id3Decoder.ID3_HEADER_LENGTH, id3Size, true)) { - return C.TIME_UNSET; - } + input.peekFully(id3Data.data, Id3Decoder.ID3_HEADER_LENGTH, id3Size); Metadata metadata = id3Decoder.decode(id3Data.data, id3Size); if (metadata == null) { return C.TIME_UNSET; From 028bd9df8f8071edf375423b1135c6b9195bb030 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 23 Nov 2018 12:28:23 +0000 Subject: [PATCH 076/210] Remove stray proguard files --- extensions/ima/src/main/proguard-rules.txt | 6 ------ library/dash/src/main/proguard-rules.txt | 7 ------- library/hls/src/main/proguard-rules.txt | 7 ------- library/smoothstreaming/src/main/proguard-rules.txt | 7 ------- 4 files changed, 27 deletions(-) delete mode 100644 extensions/ima/src/main/proguard-rules.txt delete mode 100644 library/dash/src/main/proguard-rules.txt delete mode 100644 library/hls/src/main/proguard-rules.txt delete mode 100644 library/smoothstreaming/src/main/proguard-rules.txt diff --git a/extensions/ima/src/main/proguard-rules.txt b/extensions/ima/src/main/proguard-rules.txt deleted file mode 100644 index feef3daf7a..0000000000 --- a/extensions/ima/src/main/proguard-rules.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Proguard rules specific to the IMA extension. - --keep class com.google.ads.interactivemedia.** { *; } --keep interface com.google.ads.interactivemedia.** { *; } --keep class com.google.obf.** { *; } --keep interface com.google.obf.** { *; } diff --git a/library/dash/src/main/proguard-rules.txt b/library/dash/src/main/proguard-rules.txt deleted file mode 100644 index f8725fff4d..0000000000 --- a/library/dash/src/main/proguard-rules.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Proguard rules specific to the dash module. - -# Constructors accessed via reflection in SegmentDownloadAction --dontnote com.google.android.exoplayer2.source.dash.offline.DashDownloadAction --keepclassmembers class com.google.android.exoplayer2.source.dash.offline.DashDownloadAction { - static ** DESERIALIZER; -} diff --git a/library/hls/src/main/proguard-rules.txt b/library/hls/src/main/proguard-rules.txt deleted file mode 100644 index 3b8d1bb4ac..0000000000 --- a/library/hls/src/main/proguard-rules.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Proguard rules specific to the hls module. - -# Constructors accessed via reflection in SegmentDownloadAction --dontnote com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction --keepclassmembers class com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction { - static ** DESERIALIZER; -} diff --git a/library/smoothstreaming/src/main/proguard-rules.txt b/library/smoothstreaming/src/main/proguard-rules.txt deleted file mode 100644 index d14244d783..0000000000 --- a/library/smoothstreaming/src/main/proguard-rules.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Proguard rules specific to the smoothstreaming module. - -# Constructors accessed via reflection in SegmentDownloadAction --dontnote com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction --keepclassmembers class com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction { - static ** DESERIALIZER; -} From 0701fed7088e5f43a412ecf53504bbcf7d356849 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 22 Nov 2018 17:32:04 +0000 Subject: [PATCH 077/210] Use overflow-save add operation for blacklisting duration. This allows to specify open-ended blacklisting with Long.MAX_VALUE. PiperOrigin-RevId: 222550939 --- .../com/google/android/exoplayer2/castdemo/DemoUtil.java | 2 -- .../exoplayer2/trackselection/BaseTrackSelection.java | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java index 26ab5eb0dd..77f6a6fc1a 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java @@ -81,8 +81,6 @@ import java.util.List; + "hls/TearsOfSteel.m3u8", "Tears of Steel (HLS)", MIME_TYPE_HLS)); samples.add(new Sample("https://html5demos.com/assets/dizzy.mp4", "Dizzy (MP4)", MIME_TYPE_VIDEO_MP4)); - - SAMPLES = Collections.unmodifiableList(samples); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java index 9a15cbae14..798b6ce810 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -160,7 +161,10 @@ public abstract class BaseTrackSelection implements TrackSelection { if (!canBlacklist) { return false; } - blacklistUntilTimes[index] = Math.max(blacklistUntilTimes[index], nowMs + blacklistDurationMs); + blacklistUntilTimes[index] = + Math.max( + blacklistUntilTimes[index], + Util.addWithOverflowDefault(nowMs, blacklistDurationMs, Long.MAX_VALUE)); return true; } From 3039c358cc667eeea49d19d89530f94b92fd187f Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 23 Nov 2018 09:13:57 +0000 Subject: [PATCH 078/210] Fix show_buffering attribute values. The corresponding IntDef has changed without updating the attribute values. Issue:#5139 PiperOrigin-RevId: 222598044 --- RELEASENOTES.md | 2 ++ library/ui/src/main/res/values/attrs.xml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fe5c832d14..4ce09dfbb5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -10,6 +10,8 @@ ([#4780](https://github.com/google/ExoPlayer/issues/4780)). * Include channel count in audio capabilities check ([#4690](https://github.com/google/ExoPlayer/issues/4690)). +* Fix issue with applying the `show_buffering` attribute in `PlayerView` + ([#5139](https://github.com/google/ExoPlayer/issues/5139)). ### 2.9.1 ### diff --git a/library/ui/src/main/res/values/attrs.xml b/library/ui/src/main/res/values/attrs.xml index 89f873edf7..c13622f182 100644 --- a/library/ui/src/main/res/values/attrs.xml +++ b/library/ui/src/main/res/values/attrs.xml @@ -53,8 +53,8 @@ - - + + From e99c9041793cbbaddde5958fb6625929cdeab30b Mon Sep 17 00:00:00 2001 From: BrainCrumbz Date: Fri, 23 Nov 2018 12:36:43 +0000 Subject: [PATCH 079/210] Merge #5126: fix(playlist): always call onCompletion when moving media sources Imported from GitHub PR https://github.com/google/ExoPlayer/pull/5126 Closes #5125 Merge 55a4c1e15de7f100f37e38119f1da360910fd1e3 into fe41f17c387b7c18a818c8cf2a1ebcdfbd36836a PiperOrigin-RevId: 222612873 --- .../android/exoplayer2/source/ConcatenatingMediaSource.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 52b2f63759..88cd4a1595 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -365,6 +365,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSource Date: Fri, 23 Nov 2018 16:20:18 +0000 Subject: [PATCH 080/210] Ensure changes are reflected into attrs PiperOrigin-RevId: 222628386 --- .../google/android/exoplayer2/ui/PlayerView.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 e429f5bfa0..310d04a064 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 @@ -241,11 +241,7 @@ import java.util.List; */ public class PlayerView extends FrameLayout { - private static final int SURFACE_TYPE_NONE = 0; - private static final int SURFACE_TYPE_SURFACE_VIEW = 1; - private static final int SURFACE_TYPE_TEXTURE_VIEW = 2; - private static final int SURFACE_TYPE_MONO360_VIEW = 3; - + // LINT.IfChange /** * Determines when the buffering view is shown. One of {@link #SHOW_BUFFERING_NEVER}, {@link * #SHOW_BUFFERING_WHEN_PLAYING} or {@link #SHOW_BUFFERING_ALWAYS}. @@ -266,6 +262,14 @@ public class PlayerView extends FrameLayout { * buffering} state. */ public static final int SHOW_BUFFERING_ALWAYS = 2; + // LINT.ThenChange(../../../../../../res/values/attrs.xml) + + // LINT.IfChange + private static final int SURFACE_TYPE_NONE = 0; + private static final int SURFACE_TYPE_SURFACE_VIEW = 1; + private static final int SURFACE_TYPE_TEXTURE_VIEW = 2; + private static final int SURFACE_TYPE_MONO360_VIEW = 3; + // LINT.ThenChange(../../../../../../res/values/attrs.xml) private final AspectRatioFrameLayout contentFrame; private final View shutterView; From 07053aaa51576320bbfe28ff1c49ef386020fc82 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 23 Nov 2018 16:55:26 +0000 Subject: [PATCH 081/210] Add Lint.IfChange/ThenChange for repeat modes PiperOrigin-RevId: 222630411 --- .../java/com/google/android/exoplayer2/util/RepeatModeUtil.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java index 5816e40623..cc23c9763c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/RepeatModeUtil.java @@ -26,6 +26,7 @@ import java.lang.annotation.RetentionPolicy; */ public final class RepeatModeUtil { + // LINT.IfChange /** * Set of repeat toggle modes. Can be combined using bit-wise operations. Possible flag values are * {@link #REPEAT_TOGGLE_MODE_NONE}, {@link #REPEAT_TOGGLE_MODE_ONE} and {@link @@ -47,6 +48,7 @@ public final class RepeatModeUtil { public static final int REPEAT_TOGGLE_MODE_ONE = 1; /** "Repeat All" button enabled. */ public static final int REPEAT_TOGGLE_MODE_ALL = 1 << 1; // 2 + // LINT.ThenChange(../../../../../../../../../ui/src/main/res/values/attrs.xml) private RepeatModeUtil() { // Prevent instantiation. From 327d23c711d90e9a2c7c372bf3d34630fc355bd9 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 23 Nov 2018 17:37:46 +0000 Subject: [PATCH 082/210] Clarify contribution branch PiperOrigin-RevId: 222632883 --- CONTRIBUTING.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43c4809480..94b349b217 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,9 +16,8 @@ all of the information requested in the issue template. ## Pull requests ## We will also consider high quality pull requests. These should normally merge -into the `dev-vX` branch with the highest major version number. Bug fixes may -be suitable for merging into older `dev-vX` branches. Before a pull request can -be accepted you must submit a Contributor License Agreement, as described below. +into the `dev-v2` branch. Before a pull request can be accepted you must submit +a Contributor License Agreement, as described below. [dev]: https://github.com/google/ExoPlayer/tree/dev From 9c821777a02af8db736202d5ba599d7a0287d494 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 26 Nov 2018 10:50:49 +0000 Subject: [PATCH 083/210] Noop fix to WebvttDecoder We already have tests for comment blocks, and they already pass because we discard the comment when we fail to parse it as a cue. We should just skip it directly, however. --- .../google/android/exoplayer2/text/webvtt/WebvttDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java index 327428af36..06d3c14970 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java @@ -110,7 +110,7 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder { foundEvent = EVENT_END_OF_FILE; } else if (STYLE_START.equals(line)) { foundEvent = EVENT_STYLE_BLOCK; - } else if (COMMENT_START.startsWith(line)) { + } else if (line.startsWith(COMMENT_START)) { foundEvent = EVENT_COMMENT; } else { foundEvent = EVENT_CUE; From 3a0b12707475a0eb7c310a8e81540a4c8592dca0 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 26 Nov 2018 10:38:14 +0000 Subject: [PATCH 084/210] Update content url for IMA demo app The existing one seems to be dead, and isn't https. PiperOrigin-RevId: 222795996 --- demos/ima/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/ima/src/main/res/values/strings.xml b/demos/ima/src/main/res/values/strings.xml index 67a7f06f8b..2eb5700bf0 100644 --- a/demos/ima/src/main/res/values/strings.xml +++ b/demos/ima/src/main/res/values/strings.xml @@ -17,7 +17,7 @@ Exo IMA Demo - + From 2cab83e6e4ed3b0a90b170e540f5021d3407100b Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 26 Nov 2018 12:10:02 +0000 Subject: [PATCH 085/210] Fix unnecessary media playlist requests when playing live streams Issue: #5059 PiperOrigin-RevId: 222803511 --- RELEASENOTES.md | 2 ++ .../android/exoplayer2/source/hls/HlsChunkSource.java | 8 +++++--- .../source/hls/playlist/DefaultHlsPlaylistTracker.java | 4 ++-- .../source/hls/playlist/HlsPlaylistTracker.java | 4 +++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4ce09dfbb5..fff619bd46 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### 2.9.2 ### +* HLS: Fix issue causing unnecessary media playlist requests when playing live + streams ([#5059](https://github.com/google/ExoPlayer/issues/5059)). * MP4: Support Opus and FLAC in the MP4 container, and in DASH ([#4883](https://github.com/google/ExoPlayer/issues/4883)). * Support seeking for a wider range of MPEG-TS streams diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index a8cf1a1437..c1396de3d6 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -262,7 +262,8 @@ import java.util.List; // Retry when playlist is refreshed. return; } - HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl); + HlsMediaPlaylist mediaPlaylist = + playlistTracker.getPlaylistSnapshot(selectedUrl, /* isForPlayback= */ true); independentSegments = mediaPlaylist.hasIndependentSegments; updateLiveEdgeTimeUs(mediaPlaylist); @@ -279,7 +280,7 @@ import java.util.List; // behind the live window. selectedVariantIndex = oldVariantIndex; selectedUrl = variants[selectedVariantIndex]; - mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl); + mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl, /* isForPlayback= */ true); startOfPlaylistInPeriodUs = mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs(); chunkMediaSequence = previous.getNextChunkIndex(); @@ -435,7 +436,8 @@ import java.util.List; chunkIterators[i] = MediaChunkIterator.EMPTY; continue; } - HlsMediaPlaylist playlist = playlistTracker.getPlaylistSnapshot(variantUrl); + HlsMediaPlaylist playlist = + playlistTracker.getPlaylistSnapshot(variantUrl, /* isForPlayback= */ false); long startOfPlaylistInPeriodUs = playlist.startTimeUs - playlistTracker.getInitialStartTimeUs(); boolean switchingVariant = variantIndex != oldVariantIndex; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java index 4e34c556e0..4269b66d30 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java @@ -162,9 +162,9 @@ public final class DefaultHlsPlaylistTracker } @Override - public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url) { + public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url, boolean isForPlayback) { HlsMediaPlaylist snapshot = playlistBundles.get(url).getPlaylistSnapshot(); - if (snapshot != null) { + if (snapshot != null && isForPlayback) { maybeSetPrimaryUrl(url); } return snapshot; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java index b83ae43f47..c73c9fa835 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -167,11 +167,13 @@ public interface HlsPlaylistTracker { * HlsUrl}. * * @param url The {@link HlsUrl} corresponding to the requested media playlist. + * @param isForPlayback Whether the caller might use the snapshot to request media segments for + * playback. If true, the primary playlist may be updated to the one requested. * @return The most recent snapshot of the playlist referenced by the provided {@link HlsUrl}. May * be null if no snapshot has been loaded yet. */ @Nullable - HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url); + HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url, boolean isForPlayback); /** * Returns the start time of the first loaded primary playlist, or {@link C#TIME_UNSET} if no From 0bcce18976da6065256a12efc4c87ceace1b7ecb Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 26 Nov 2018 12:30:23 +0000 Subject: [PATCH 086/210] Assume text tracks in protected SmoothStreaming are not protected Issue: #4838 PiperOrigin-RevId: 222805051 --- .../source/smoothstreaming/manifest/SsManifestParser.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java index cc09b5ff11..3d5ade403a 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java @@ -378,8 +378,12 @@ public class SsManifestParser implements ParsingLoadable.Parser { DrmInitData drmInitData = new DrmInitData(new SchemeData(protectionElement.uuid, MimeTypes.VIDEO_MP4, protectionElement.data)); for (StreamElement streamElement : streamElementArray) { - for (int i = 0; i < streamElement.formats.length; i++) { - streamElement.formats[i] = streamElement.formats[i].copyWithDrmInitData(drmInitData); + int type = streamElement.type; + if (type == C.TRACK_TYPE_VIDEO || type == C.TRACK_TYPE_AUDIO) { + Format[] formats = streamElement.formats; + for (int i = 0; i < formats.length; i++) { + formats[i] = formats[i].copyWithDrmInitData(drmInitData); + } } } } From d3f4c18401e82c781042d65e5c81462fc7edb595 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 26 Nov 2018 13:21:02 +0000 Subject: [PATCH 087/210] Provide Cronet request and response data for subclasses. Subclasses may want to analyze, log and react to the Cronet-specific connection data. Issue:#5134 PiperOrigin-RevId: 222809441 --- .../exoplayer2/ext/cronet/CronetDataSource.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index af85401100..ab10f41d8f 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.ext.cronet; import android.net.Uri; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; @@ -455,6 +456,18 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { } } + /** Returns current {@link UrlRequest}. May be null if the data source is not opened. */ + @Nullable + protected UrlRequest getCurrentUrlRequest() { + return currentUrlRequest; + } + + /** Returns current {@link UrlResponseInfo}. May be null if the data source is not opened. */ + @Nullable + protected UrlResponseInfo getCurrentUrlResponseInfo() { + return responseInfo; + } + // Internal methods. private UrlRequest.Builder buildRequestBuilder(DataSpec dataSpec) throws IOException { From 1e37d3186771d78404149508847e7af12d8fece5 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 26 Nov 2018 15:28:18 +0000 Subject: [PATCH 088/210] Remove spurious VisibleForTesting annotation It needs to have package visiblity, otherwise nothing can use it. PiperOrigin-RevId: 222821546 --- .../google/android/exoplayer2/ui/spherical/TouchTracker.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java index 335f611b58..c0373c9ca1 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/TouchTracker.java @@ -44,11 +44,10 @@ import android.view.View; * a nicer UI. An even more advanced UI would reproject the user's touch point into 3D and drag the * Mesh as the user moves their finger. However, that requires quaternion interpolation. */ -// @VisibleForTesting -/*package*/ class TouchTracker extends GestureDetector.SimpleOnGestureListener +/* package */ class TouchTracker extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { - /*package*/ interface Listener { + /* package */ interface Listener { void onScrollChange(PointF scrollOffsetDegrees); } From 38c53298b046445f6ee059c99910826d1ec300f3 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 27 Nov 2018 13:14:22 +0000 Subject: [PATCH 089/210] Strip private ID3 data from HLS sample formats Issue: #5063 PiperOrigin-RevId: 222975020 --- RELEASENOTES.md | 7 ++- .../exoplayer2/source/SampleQueue.java | 6 +-- .../exoplayer2/source/hls/HlsMediaChunk.java | 3 +- .../source/hls/HlsSampleStreamWrapper.java | 54 ++++++++++++++++++- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fff619bd46..a58051897f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,8 +2,11 @@ ### 2.9.2 ### -* HLS: Fix issue causing unnecessary media playlist requests when playing live - streams ([#5059](https://github.com/google/ExoPlayer/issues/5059)). +* HLS: + * Fix issue causing unnecessary media playlist requests when playing live + streams ([#5059](https://github.com/google/ExoPlayer/issues/5059)). + * Fix decoder re-instantiation issue for packed audio streams + ([#5063](https://github.com/google/ExoPlayer/issues/5063)). * MP4: Support Opus and FLAC in the MP4 container, and in DASH ([#4883](https://github.com/google/ExoPlayer/issues/4883)). * Support seeking for a wider range of MPEG-TS streams diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java index d6f18665f4..ecc720c656 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java @@ -30,10 +30,8 @@ import java.io.EOFException; import java.io.IOException; import java.nio.ByteBuffer; -/** - * A queue of media samples. - */ -public final class SampleQueue implements TrackOutput { +/** A queue of media samples. */ +public class SampleQueue implements TrackOutput { /** * A listener for changes to the upstream format. diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 20134e9efc..2995f8b0aa 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -42,8 +42,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ /* package */ final class HlsMediaChunk extends MediaChunk { - - private static final String PRIV_TIMESTAMP_FRAME_OWNER = + public static final String PRIV_TIMESTAMP_FRAME_OWNER = "com.apple.streaming.transportStreamTimestamp"; private static final AtomicInteger uidSource = new AtomicInteger(); 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 e60dacb8f4..39598c4cd8 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 @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.source.hls; import android.os.Handler; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; @@ -24,6 +25,8 @@ import com.google.android.exoplayer2.extractor.DummyTrackOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.id3.PrivFrame; import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.SampleQueue; import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener; @@ -791,7 +794,7 @@ import java.util.List; return createDummyTrackOutput(id, type); } } - SampleQueue trackOutput = new SampleQueue(allocator); + SampleQueue trackOutput = new PrivTimestampStrippingSampleQueue(allocator); trackOutput.setSampleOffsetUs(sampleOffsetUs); trackOutput.sourceId(chunkUid); trackOutput.setUpstreamFormatChangeListener(this); @@ -1126,4 +1129,53 @@ import java.util.List; Log.w(TAG, "Unmapped track with id " + id + " of type " + type); return new DummyTrackOutput(); } + + private static final class PrivTimestampStrippingSampleQueue extends SampleQueue { + + public PrivTimestampStrippingSampleQueue(Allocator allocator) { + super(allocator); + } + + @Override + public void format(Format format) { + super.format(format.copyWithMetadata(getAdjustedMetadata(format.metadata))); + } + + /** + * Strips the private timestamp frame from metadata, if present. See: + * https://github.com/google/ExoPlayer/issues/5063 + */ + @Nullable + private Metadata getAdjustedMetadata(@Nullable Metadata metadata) { + if (metadata == null) { + return null; + } + int length = metadata.length(); + int transportStreamTimestampMetadataIndex = C.INDEX_UNSET; + for (int i = 0; i < length; i++) { + Metadata.Entry metadataEntry = metadata.get(i); + if (metadataEntry instanceof PrivFrame) { + PrivFrame privFrame = (PrivFrame) metadataEntry; + if (HlsMediaChunk.PRIV_TIMESTAMP_FRAME_OWNER.equals(privFrame.owner)) { + transportStreamTimestampMetadataIndex = i; + break; + } + } + } + if (transportStreamTimestampMetadataIndex == C.INDEX_UNSET) { + return metadata; + } + if (length == 1) { + return null; + } + Metadata.Entry[] newMetadataEntries = new Metadata.Entry[length - 1]; + for (int i = 0; i < length; i++) { + if (i != transportStreamTimestampMetadataIndex) { + int newIndex = i < transportStreamTimestampMetadataIndex ? i : i - 1; + newMetadataEntries[newIndex] = metadata.get(i); + } + } + return new Metadata(newMetadataEntries); + } + } } From 017923ed81001c9827b452c2b06cbddd07bda776 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 27 Nov 2018 13:58:44 +0000 Subject: [PATCH 090/210] Fall back to TYPE_ROTATION_VECTOR if TYPE_GAME_ROTATION_VECTOR unavailable Issue: #5119 PiperOrigin-RevId: 222978448 --- RELEASENOTES.md | 3 +++ .../ui/spherical/SphericalSurfaceView.java | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a58051897f..eb229a98cc 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,6 +9,9 @@ ([#5063](https://github.com/google/ExoPlayer/issues/5063)). * MP4: Support Opus and FLAC in the MP4 container, and in DASH ([#4883](https://github.com/google/ExoPlayer/issues/4883)). +* Spherical video: Fall back to `TYPE_ROTATION_VECTOR` if + `TYPE_GAME_ROTATION_VECTOR` is unavailable + ([#5119](https://github.com/google/ExoPlayer/issues/5119)). * Support seeking for a wider range of MPEG-TS streams ([#5097](https://github.com/google/ExoPlayer/issues/5097)). * DASH: Fix detecting the end of live events diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java index c6ddb8148b..7b58f54ac2 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/spherical/SphericalSurfaceView.java @@ -104,12 +104,18 @@ public final class SphericalSurfaceView extends GLSurfaceView { // Configure sensors and touch. sensorManager = (SensorManager) Assertions.checkNotNull(context.getSystemService(Context.SENSOR_SERVICE)); - // TYPE_GAME_ROTATION_VECTOR is the easiest sensor since it handles all the complex math for - // fusion. It's used instead of TYPE_ROTATION_VECTOR since the latter uses the magnetometer on - // devices. When used indoors, the magnetometer can take some time to settle depending on the - // device and amount of metal in the environment. - int type = Util.SDK_INT >= 18 ? Sensor.TYPE_GAME_ROTATION_VECTOR : Sensor.TYPE_ROTATION_VECTOR; - orientationSensor = sensorManager.getDefaultSensor(type); + Sensor orientationSensor = null; + if (Util.SDK_INT >= 18) { + // TYPE_GAME_ROTATION_VECTOR is the easiest sensor since it handles all the complex math for + // fusion. It's used instead of TYPE_ROTATION_VECTOR since the latter uses the magnetometer on + // devices. When used indoors, the magnetometer can take some time to settle depending on the + // device and amount of metal in the environment. + orientationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR); + } + if (orientationSensor == null) { + orientationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); + } + this.orientationSensor = orientationSensor; scene = new SceneRenderer(); renderer = new Renderer(scene); From 15d13bdccd17e40a0ca7d26ed541af6b0a0db4cc Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 28 Nov 2018 08:26:41 +0000 Subject: [PATCH 091/210] Handle metadata failing to decode in MetadataRenderer Issue: #5149 PiperOrigin-RevId: 223121651 --- RELEASENOTES.md | 2 ++ .../android/exoplayer2/metadata/MetadataRenderer.java | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index eb229a98cc..7a40269cfd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -20,6 +20,8 @@ ([#4690](https://github.com/google/ExoPlayer/issues/4690)). * Fix issue with applying the `show_buffering` attribute in `PlayerView` ([#5139](https://github.com/google/ExoPlayer/issues/5139)). +* Fix issue where null `Metadata` was output when it failed to decode + ([#5149](https://github.com/google/ExoPlayer/issues/5149)). ### 2.9.1 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java index 152eb97e0c..864616e810 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java @@ -129,9 +129,12 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { buffer.subsampleOffsetUs = formatHolder.format.subsampleOffsetUs; buffer.flip(); int index = (pendingMetadataIndex + pendingMetadataCount) % MAX_PENDING_METADATA_COUNT; - pendingMetadata[index] = decoder.decode(buffer); - pendingMetadataTimestamps[index] = buffer.timeUs; - pendingMetadataCount++; + Metadata metadata = decoder.decode(buffer); + if (metadata != null) { + pendingMetadata[index] = metadata; + pendingMetadataTimestamps[index] = buffer.timeUs; + pendingMetadataCount++; + } } } } From 51344778732bca8f568e9b6ae53cb873b89638a2 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 28 Nov 2018 11:41:33 +0000 Subject: [PATCH 092/210] Bump for 2.9.2 PiperOrigin-RevId: 223141203 --- RELEASENOTES.md | 4 ++-- constants.gradle | 4 ++-- .../com/google/android/exoplayer2/ExoPlayerLibraryInfo.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7a40269cfd..f7c1ec23ba 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,13 +9,13 @@ ([#5063](https://github.com/google/ExoPlayer/issues/5063)). * MP4: Support Opus and FLAC in the MP4 container, and in DASH ([#4883](https://github.com/google/ExoPlayer/issues/4883)). +* DASH: Fix detecting the end of live events + ([#4780](https://github.com/google/ExoPlayer/issues/4780)). * Spherical video: Fall back to `TYPE_ROTATION_VECTOR` if `TYPE_GAME_ROTATION_VECTOR` is unavailable ([#5119](https://github.com/google/ExoPlayer/issues/5119)). * Support seeking for a wider range of MPEG-TS streams ([#5097](https://github.com/google/ExoPlayer/issues/5097)). -* DASH: Fix detecting the end of live events - ([#4780](https://github.com/google/ExoPlayer/issues/4780)). * Include channel count in audio capabilities check ([#4690](https://github.com/google/ExoPlayer/issues/4690)). * Fix issue with applying the `show_buffering` attribute in `PlayerView` diff --git a/constants.gradle b/constants.gradle index dd277c722c..cac4f6d78b 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.9.1' - releaseVersionCode = 2009001 + releaseVersion = '2.9.2' + releaseVersionCode = 2009002 // Important: ExoPlayer specifies a minSdkVersion of 14 because various // components provided by the library may be of use on older devices. // However, please note that the core media playback functionality provided diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index c4dda9e957..c30fe160c9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo { /** The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - public static final String VERSION = "2.9.1"; + public static final String VERSION = "2.9.2"; /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.1"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.2"; /** * The version of the library expressed as an integer, for example 1002003. @@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final int VERSION_INT = 2009001; + public static final int VERSION_INT = 2009002; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} From 6d232f5b2b71bc8f90f4269d9d14a5435f67d876 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 28 Nov 2018 18:31:53 +0000 Subject: [PATCH 093/210] Replace remaining stbl assertions with warnings Issue: #5162 PiperOrigin-RevId: 223193019 --- RELEASENOTES.md | 3 ++ .../exoplayer2/extractor/mp4/AtomParsers.java | 43 ++++++++++++------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f7c1ec23ba..b3c62c447b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -22,6 +22,9 @@ ([#5139](https://github.com/google/ExoPlayer/issues/5139)). * Fix issue where null `Metadata` was output when it failed to decode ([#5149](https://github.com/google/ExoPlayer/issues/5149)). +* Fix playback of some invalid but playable MP4 streams by replacing assertions + with logged warnings in sample table parsing code + ([#5162](https://github.com/google/ExoPlayer/issues/5162)). ### 2.9.1 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index e3d56489a6..d085156f2b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -236,8 +236,6 @@ import java.util.List; sizes = Arrays.copyOf(sizes, sampleCount); timestamps = Arrays.copyOf(timestamps, sampleCount); flags = Arrays.copyOf(flags, sampleCount); - remainingSamplesAtTimestampOffset = 0; - remainingTimestampOffsetChanges = 0; break; } @@ -293,23 +291,38 @@ import java.util.List; } duration = timestampTimeUnits + timestampOffset; - Assertions.checkArgument(remainingSamplesAtTimestampOffset == 0); - // Remove trailing ctts entries with 0-valued sample counts. + // If the stbl's child boxes are not consistent the container is malformed, but the stream may + // still be playable. + boolean isCttsValid = true; while (remainingTimestampOffsetChanges > 0) { - Assertions.checkArgument(ctts.readUnsignedIntToInt() == 0); + if (ctts.readUnsignedIntToInt() != 0) { + isCttsValid = false; + break; + } ctts.readInt(); // Ignore offset. remainingTimestampOffsetChanges--; } - - // If the stbl's child boxes are not consistent the container is malformed, but the stream may - // still be playable. - if (remainingSynchronizationSamples != 0 || remainingSamplesAtTimestampDelta != 0 - || remainingSamplesInChunk != 0 || remainingTimestampDeltaChanges != 0) { - Log.w(TAG, "Inconsistent stbl box for track " + track.id - + ": remainingSynchronizationSamples " + remainingSynchronizationSamples - + ", remainingSamplesAtTimestampDelta " + remainingSamplesAtTimestampDelta - + ", remainingSamplesInChunk " + remainingSamplesInChunk - + ", remainingTimestampDeltaChanges " + remainingTimestampDeltaChanges); + if (remainingSynchronizationSamples != 0 + || remainingSamplesAtTimestampDelta != 0 + || remainingSamplesInChunk != 0 + || remainingTimestampDeltaChanges != 0 + || remainingSamplesAtTimestampOffset != 0 + || !isCttsValid) { + Log.w( + TAG, + "Inconsistent stbl box for track " + + track.id + + ": remainingSynchronizationSamples " + + remainingSynchronizationSamples + + ", remainingSamplesAtTimestampDelta " + + remainingSamplesAtTimestampDelta + + ", remainingSamplesInChunk " + + remainingSamplesInChunk + + ", remainingTimestampDeltaChanges " + + remainingTimestampDeltaChanges + + ", remainingSamplesAtTimestampOffset " + + remainingSamplesAtTimestampOffset + + (!isCttsValid ? ", ctts invalid" : "")); } } else { long[] chunkOffsetsBytes = new long[chunkIterator.length]; From 9d50a6191e1c3d720a353b768407b44a710b974d Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Wed, 28 Nov 2018 19:39:04 +0000 Subject: [PATCH 094/210] Add missing dot at the end of RELEASENOTES item PiperOrigin-RevId: 223206504 --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b3c62c447b..446a433e9f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -46,7 +46,7 @@ * DASH: Parse ProgramInformation element if present in the manifest. * HLS: * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload - reader factory flags + reader factory flags. * Fix bug in segment sniffing ([#5039](https://github.com/google/ExoPlayer/issues/5039)). ([#4861](https://github.com/google/ExoPlayer/issues/4861)). From 66f7e984ff855b146d52487417562b9a24722682 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 29 Nov 2018 10:30:39 +0000 Subject: [PATCH 095/210] Specify a version for the FFmpeg dependency Issue: #5154 PiperOrigin-RevId: 223314749 --- extensions/ffmpeg/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/ffmpeg/README.md b/extensions/ffmpeg/README.md index d5a37db013..52dacf8166 100644 --- a/extensions/ffmpeg/README.md +++ b/extensions/ffmpeg/README.md @@ -46,7 +46,7 @@ HOST_PLATFORM="linux-x86_64" be supported. See the [Supported formats][] page for more details of the available flags. -For example, to fetch and build for armeabi-v7a, +For example, to fetch and build FFmpeg release 4.0 for armeabi-v7a, arm64-v8a and x86 on Linux x86_64: ``` @@ -71,7 +71,7 @@ COMMON_OPTIONS="\ " && \ cd "${FFMPEG_EXT_PATH}/jni" && \ (git -C ffmpeg pull || git clone git://source.ffmpeg.org/ffmpeg ffmpeg) && \ -cd ffmpeg && \ +cd ffmpeg && git checkout release/4.0 && \ ./configure \ --libdir=android-libs/armeabi-v7a \ --arch=arm \ From f8ad6d309e9296544c1a78caf5dd721ef1811344 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 29 Nov 2018 12:15:51 +0000 Subject: [PATCH 096/210] Fix clearkey DRM UUID passed to MediaCrypto PiperOrigin-RevId: 223324279 --- RELEASENOTES.md | 1 + .../android/exoplayer2/drm/FrameworkMediaDrm.java | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 446a433e9f..6b3fbc4f27 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -25,6 +25,7 @@ * Fix playback of some invalid but playable MP4 streams by replacing assertions with logged warnings in sample table parsing code ([#5162](https://github.com/google/ExoPlayer/issues/5162)). +* Fix UUID passed to `MediaCrypto` when using `C.CLEARKEY_UUID` before API 27. ### 2.9.1 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java index 254b524b30..d2a5b6a0d6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java @@ -70,9 +70,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm schemeDatas) { @@ -269,6 +266,11 @@ public final class FrameworkMediaDrm implements ExoMediaDrm Date: Fri, 30 Nov 2018 22:57:29 +0000 Subject: [PATCH 097/210] Add several devices to setOutputSurface workaround: - Asus ZenFone GO (ASUS_X00AD_2) - Sugar S9 (i9031) - Redmi Note 3 (kate) These devices trigger native crashes similar to https://github.com/google/ExoPlayer/issues/4460 I'm not sure why Asus Zenfone Go (model: ZB500KL, device: ASUS_X00AD_2) was removed here https://github.com/google/ExoPlayer/commit/73af056da39b6ebead767d3d4c6e3162cc4c344c PiperOrigin-RevId: 223580393 --- .../android/exoplayer2/video/MediaCodecVideoRenderer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index d217b47785..79adc87509 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -1329,6 +1329,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { case "A7010a48": case "A7020a48": case "AquaPowerM": + case "ASUS_X00AD_2": case "Aura_Note_2": case "BLACK-1X": case "BRAVIA_ATV2": @@ -1369,6 +1370,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { case "HWBLN-H": case "HWCAM-H": case "HWVNS-H": + case "i9031": case "iball8735_9806": case "Infinix-X572": case "iris60": @@ -1376,6 +1378,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { case "j2xlteins": case "JGZ": case "K50a40": + case "kate": case "le_x6": case "LS-5017": case "M5c": From a94fa330f5d093285779900a238ae1d25588f9e1 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 30 Nov 2018 23:01:07 +0000 Subject: [PATCH 098/210] Prevent Cea608Decoder from generating Subtitles with null Cues list. PiperOrigin-RevId: 223580953 --- .../google/android/exoplayer2/text/cea/Cea608Decoder.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index 60cdda06c4..3c39fdc6c1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer2.text.SubtitleInputBuffer; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -451,7 +452,7 @@ public final class Cea608Decoder extends CeaDecoder { switch (cc2) { case CTRL_ERASE_DISPLAYED_MEMORY: - cues = null; + cues = Collections.emptyList(); if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { resetCueBuilders(); } @@ -506,7 +507,7 @@ public final class Cea608Decoder extends CeaDecoder { if (oldCaptionMode == CC_MODE_PAINT_ON || captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_UNKNOWN) { // When switching from paint-on or to roll-up or unknown, we also need to clear the caption. - cues = null; + cues = Collections.emptyList(); } } From ddda2eef7eb8a02af7a61dd49a53f7460fcdd62f Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 4 Dec 2018 17:30:08 +0000 Subject: [PATCH 099/210] Add no-op defaults to Video(Audio)RendererEventListener. This is in line with how Player.EventListener and AnalyticsListener methods are defined and helps to only implement the callbacks needed. PiperOrigin-RevId: 223991262 --- .../audio/AudioRendererEventListener.java | 18 ++++++----- .../video/VideoRendererEventListener.java | 32 +++++++++---------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java index 7c3c1481fc..eff7bc8de2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java @@ -25,7 +25,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.util.Assertions; /** - * Listener of audio {@link Renderer} events. + * Listener of audio {@link Renderer} events. All methods have no-op default implementations to + * allow selective overrides. */ public interface AudioRendererEventListener { @@ -35,14 +36,14 @@ public interface AudioRendererEventListener { * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it * remains enabled. */ - void onAudioEnabled(DecoderCounters counters); + default void onAudioEnabled(DecoderCounters counters) {} /** * Called when the audio session is set. * * @param audioSessionId The audio session id. */ - void onAudioSessionId(int audioSessionId); + default void onAudioSessionId(int audioSessionId) {} /** * Called when a decoder is created. @@ -52,15 +53,15 @@ public interface AudioRendererEventListener { * finished. * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. */ - void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, - long initializationDurationMs); + default void onAudioDecoderInitialized( + String decoderName, long initializedTimestampMs, long initializationDurationMs) {} /** * Called when the format of the media being consumed by the renderer changes. * * @param format The new format. */ - void onAudioInputFormatChanged(Format format); + default void onAudioInputFormatChanged(Format format) {} /** * Called when an {@link AudioSink} underrun occurs. @@ -71,14 +72,15 @@ public interface AudioRendererEventListener { * as the buffered media can have a variable bitrate so the duration may be unknown. * @param elapsedSinceLastFeedMs The time since the {@link AudioSink} was last fed data. */ - void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); + default void onAudioSinkUnderrun( + int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {} /** * Called when the renderer is disabled. * * @param counters {@link DecoderCounters} that were updated by the renderer. */ - void onAudioDisabled(DecoderCounters counters); + default void onAudioDisabled(DecoderCounters counters) {} /** * Dispatches events to a {@link AudioRendererEventListener}. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java index 617211afb7..7d78ba03c7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java @@ -26,7 +26,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.util.Assertions; /** - * Listener of video {@link Renderer} events. + * Listener of video {@link Renderer} events. All methods have no-op default implementations to + * allow selective overrides. */ public interface VideoRendererEventListener { @@ -36,7 +37,7 @@ public interface VideoRendererEventListener { * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it * remains enabled. */ - void onVideoEnabled(DecoderCounters counters); + default void onVideoEnabled(DecoderCounters counters) {} /** * Called when a decoder is created. @@ -46,15 +47,15 @@ public interface VideoRendererEventListener { * finished. * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. */ - void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, - long initializationDurationMs); + default void onVideoDecoderInitialized( + String decoderName, long initializedTimestampMs, long initializationDurationMs) {} /** * Called when the format of the media being consumed by the renderer changes. * * @param format The new format. */ - void onVideoInputFormatChanged(Format format); + default void onVideoInputFormatChanged(Format format) {} /** * Called to report the number of frames dropped by the renderer. Dropped frames are reported @@ -62,12 +63,11 @@ public interface VideoRendererEventListener { * reaches a specified threshold whilst the renderer is started. * * @param count The number of dropped frames. - * @param elapsedMs The duration in milliseconds over which the frames were dropped. This - * duration is timed from when the renderer was started or from when dropped frames were - * last reported (whichever was more recent), and not from when the first of the reported - * drops occurred. + * @param elapsedMs The duration in milliseconds over which the frames were dropped. This duration + * is timed from when the renderer was started or from when dropped frames were last reported + * (whichever was more recent), and not from when the first of the reported drops occurred. */ - void onDroppedFrames(int count, long elapsedMs); + default void onDroppedFrames(int count, long elapsedMs) {} /** * Called before a frame is rendered for the first time since setting the surface, and each time @@ -82,12 +82,12 @@ public interface VideoRendererEventListener { * this is not possible. Applications that use {@link TextureView} can apply the rotation by * calling {@link TextureView#setTransform}. Applications that do not expect to encounter * rotated videos can safely ignore this parameter. - * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case - * of square pixels this will be equal to 1.0. Different values are indicative of anamorphic + * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of + * square pixels this will be equal to 1.0. Different values are indicative of anamorphic * content. */ - void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, - float pixelWidthHeightRatio); + default void onVideoSizeChanged( + int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {} /** * Called when a frame is rendered for the first time since setting the surface, and when a frame @@ -96,14 +96,14 @@ public interface VideoRendererEventListener { * @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if * the renderer renders to something that isn't a {@link Surface}. */ - void onRenderedFirstFrame(@Nullable Surface surface); + default void onRenderedFirstFrame(@Nullable Surface surface) {} /** * Called when the renderer is disabled. * * @param counters {@link DecoderCounters} that were updated by the renderer. */ - void onVideoDisabled(DecoderCounters counters); + default void onVideoDisabled(DecoderCounters counters) {} /** * Dispatches events to a {@link VideoRendererEventListener}. From 5f33c7fcf5dee242eb04627720ab2b75d8906a5b Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 5 Dec 2018 17:49:05 +0000 Subject: [PATCH 100/210] Merge pull request #5187 from BrainCrumbz:feat/get-tag PiperOrigin-RevId: 224166374 --- .../exoplayer2/ext/ima/ImaAdsMediaSource.java | 6 ++++++ .../exoplayer2/source/ClippingMediaSource.java | 6 ++++++ .../exoplayer2/source/ConcatenatingMediaSource.java | 12 ++++++++++++ .../exoplayer2/source/ExtractorMediaSource.java | 6 ++++++ .../exoplayer2/source/LoopingMediaSource.java | 6 ++++++ .../android/exoplayer2/source/MediaSource.java | 6 ++++++ .../exoplayer2/source/MergingMediaSource.java | 6 ++++++ .../exoplayer2/source/SingleSampleMediaSource.java | 8 ++++++++ .../exoplayer2/source/ads/AdsMediaSource.java | 6 ++++++ .../exoplayer2/source/dash/DashMediaSource.java | 6 ++++++ .../exoplayer2/source/hls/HlsMediaSource.java | 6 ++++++ .../source/smoothstreaming/SsMediaSource.java | 6 ++++++ .../android/exoplayer2/testutil/FakeMediaSource.java | 7 +++++++ 13 files changed, 87 insertions(+) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java index 400061d019..85042c4354 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java @@ -76,6 +76,12 @@ public final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn adUiViewGroup, eventHandler, eventListener); } + @Override + @Nullable + public Object getTag() { + return adsMediaSource.getTag(); + } + @Override public void prepareSourceInternal( final ExoPlayer player, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index 78e37c1869..3ed18049bf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -216,6 +216,12 @@ public final class ClippingMediaSource extends CompositeMediaSource { window = new Timeline.Window(); } + @Override + @Nullable + public Object getTag() { + return mediaSource.getTag(); + } + @Override public void prepareSourceInternal( ExoPlayer player, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 88cd4a1595..af4a9f8000 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -453,6 +453,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource { mediaPeriodToChildMediaPeriodId = new HashMap<>(); } + @Override + @Nullable + public Object getTag() { + return childSource.getTag(); + } + @Override public void prepareSourceInternal( ExoPlayer player, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index 6b0f5c8eeb..74449ba16b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -220,6 +220,12 @@ public interface MediaSource { */ void removeEventListener(MediaSourceEventListener eventListener); + /** Returns the tag set on the media source, or null if none was set. */ + @Nullable + default Object getTag() { + return null; + } + /** @deprecated Will be removed in the next release. */ @Deprecated void prepareSource( diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index ecb4b10c6a..573e97cb13 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -98,6 +98,12 @@ public final class MergingMediaSource extends CompositeMediaSource { timelines = new Timeline[mediaSources.length]; } + @Override + @Nullable + public Object getTag() { + return mediaSources.length > 0 ? mediaSources[0].getTag() : null; + } + @Override public void prepareSourceInternal( ExoPlayer player, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index 1ac6207454..66097970c7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -185,6 +185,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource { private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final boolean treatLoadErrorsAsEndOfStream; private final Timeline timeline; + @Nullable private final Object tag; private @Nullable TransferListener transferListener; @@ -287,6 +288,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource { this.durationUs = durationUs; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream; + this.tag = tag; dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); timeline = @@ -295,6 +297,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource { // MediaSource implementation. + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 7fc0f22bf3..19ddbd2c54 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -319,6 +319,12 @@ public final class AdsMediaSource extends CompositeMediaSource { adsLoader.setSupportedContentTypes(adMediaSourceFactory.getSupportedTypes()); } + @Override + @Nullable + public Object getTag() { + return contentMediaSource.getTag(); + } + @Override public void prepareSourceInternal( final ExoPlayer player, diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 8ee859b8bd..c8de8f02b1 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -607,6 +607,12 @@ public final class DashMediaSource extends BaseMediaSource { // MediaSource implementation. + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index a075dacf3a..a9b0c579ac 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -390,6 +390,12 @@ public final class HlsMediaSource extends BaseMediaSource this.tag = tag; } + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index a756b7f4f1..103a52a55a 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -503,6 +503,12 @@ public final class SsMediaSource extends BaseMediaSource // MediaSource implementation. + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java index 2fca4f42c7..1f0c0c1a40 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java @@ -88,6 +88,13 @@ public class FakeMediaSource extends BaseMediaSource { this.trackGroupArray = trackGroupArray; } + @Override + @Nullable + public Object getTag() { + boolean hasTimeline = timeline != null && !timeline.isEmpty(); + return hasTimeline ? timeline.getWindow(0, new Timeline.Window()).tag : null; + } + @Override public synchronized void prepareSourceInternal( ExoPlayer player, From ee1ec8d3d6c89dfa11ca5504fd4eae919fcd83ac Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 6 Dec 2018 09:05:09 +0000 Subject: [PATCH 101/210] Disable post processing on Nvidia devices PiperOrigin-RevId: 224291309 --- .../video/MediaCodecVideoRenderer.java | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 79adc87509..176f0f5765 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -98,7 +98,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private final EventDispatcher eventDispatcher; private final long allowedJoiningTimeMs; private final int maxDroppedFramesToNotify; - private final boolean deviceNeedsAutoFrcWorkaround; + private final boolean deviceNeedsNoPostProcessWorkaround; private final long[] pendingOutputStreamOffsetsUs; private final long[] pendingOutputStreamSwitchTimesUs; @@ -226,7 +226,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { this.context = context.getApplicationContext(); frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(this.context); eventDispatcher = new EventDispatcher(eventHandler, eventListener); - deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround(); + deviceNeedsNoPostProcessWorkaround = deviceNeedsNoPostProcessWorkaround(); pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT]; pendingOutputStreamSwitchTimesUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT]; outputStreamOffsetUs = C.TIME_UNSET; @@ -471,7 +471,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { format, codecMaxValues, codecOperatingRate, - deviceNeedsAutoFrcWorkaround, + deviceNeedsNoPostProcessWorkaround, tunnelingAudioSessionId); if (surface == null) { Assertions.checkState(shouldUseDummySurface(codecInfo)); @@ -1027,8 +1027,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { * @param codecMaxValues Codec max values that should be used when configuring the decoder. * @param codecOperatingRate The codec operating rate, or {@link #CODEC_OPERATING_RATE_UNSET} if * no codec operating rate should be set. - * @param deviceNeedsAutoFrcWorkaround Whether the device is known to enable frame-rate conversion - * logic that negatively impacts ExoPlayer. + * @param deviceNeedsNoPostProcessWorkaround Whether the device is known to do post processing by + * default that isn't compatible with ExoPlayer. * @param tunnelingAudioSessionId The audio session id to use for tunneling, or {@link * C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled. * @return The framework {@link MediaFormat} that should be used to configure the decoder. @@ -1038,7 +1038,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { Format format, CodecMaxValues codecMaxValues, float codecOperatingRate, - boolean deviceNeedsAutoFrcWorkaround, + boolean deviceNeedsNoPostProcessWorkaround, int tunnelingAudioSessionId) { MediaFormat mediaFormat = new MediaFormat(); // Set format parameters that should always be set. @@ -1062,7 +1062,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate); } } - if (deviceNeedsAutoFrcWorkaround) { + if (deviceNeedsNoPostProcessWorkaround) { + mediaFormat.setInteger("no-post-process", 1); mediaFormat.setInteger("auto-frc", 0); } if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { @@ -1256,21 +1257,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } /** - * Returns whether the device is known to enable frame-rate conversion logic that negatively - * impacts ExoPlayer. - *

- * If true is returned then we explicitly disable the feature. + * Returns whether the device is known to do post processing by default that isn't compatible with + * ExoPlayer. * - * @return True if the device is known to enable frame-rate conversion logic that negatively - * impacts ExoPlayer. False otherwise. + * @return Whether the device is known to do post processing by default that isn't compatible with + * ExoPlayer. */ - private static boolean deviceNeedsAutoFrcWorkaround() { - // nVidia Shield prior to M tries to adjust the playback rate to better map the frame-rate of + private static boolean deviceNeedsNoPostProcessWorkaround() { + // Nvidia devices prior to M try to adjust the playback rate to better map the frame-rate of // content to the refresh rate of the display. For example playback of 23.976fps content is // adjusted to play at 1.001x speed when the output display is 60Hz. Unfortunately the // implementation causes ExoPlayer's reported playback position to drift out of sync. Captions - // also lose sync [Internal: b/26453592]. - return Util.SDK_INT <= 22 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER); + // also lose sync [Internal: b/26453592]. Even after M, the devices may apply post processing + // operations that can modify frame output timestamps, which is incompatible with ExoPlayer's + // logic for skipping decode-only frames. + return "NVIDIA".equals(Util.MANUFACTURER); } /* From 0e139e994544467907a703be6872a60ff44e4c4c Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 6 Dec 2018 13:48:09 +0000 Subject: [PATCH 102/210] Use media source tag in dummy timeline. This is now possible as it's directly accessible from the media source. Issue:#5177 Issue:#5155 PiperOrigin-RevId: 224321917 --- .../source/ConcatenatingMediaSource.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index af4a9f8000..03ccd56645 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -826,7 +826,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource(); this.uid = new Object(); } @@ -951,10 +951,18 @@ public class ConcatenatingMediaSource extends CompositeMediaSource Date: Sun, 9 Dec 2018 19:05:09 +0000 Subject: [PATCH 103/210] Apply EOS flush workaround to stvm8 devices Issue:#5203 PiperOrigin-RevId: 224726041 --- .../android/exoplayer2/mediacodec/MediaCodecRenderer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 86bbb330b7..6a813332e3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -1622,7 +1622,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { */ private static boolean codecNeedsEosFlushWorkaround(String name) { return (Util.SDK_INT <= 23 && "OMX.google.vorbis.decoder".equals(name)) - || (Util.SDK_INT <= 19 && "hb2000".equals(Util.DEVICE) + || (Util.SDK_INT <= 19 + && ("hb2000".equals(Util.DEVICE) || "stvm8".equals(Util.DEVICE)) && ("OMX.amlogic.avc.decoder.awesome".equals(name) || "OMX.amlogic.avc.decoder.awesome.secure".equals(name))); } From db5083d18ac3d54874dc48e9eef44317b2fe0591 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 11 Dec 2018 18:19:07 +0000 Subject: [PATCH 104/210] Enable setOutputSurfaceWorkaround for dangal Issue: #5169 PiperOrigin-RevId: 225025357 --- .../video/MediaCodecVideoRenderer.java | 312 +++++++++--------- 1 file changed, 159 insertions(+), 153 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 176f0f5765..5d6fba5b0d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -1297,163 +1297,169 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { * incorrectly. */ protected boolean codecNeedsSetOutputSurfaceWorkaround(String name) { - if (Util.SDK_INT >= 27 || name.startsWith("OMX.google")) { - // Devices running API level 27 or later should also be unaffected. Google OMX decoders are - // not known to have this issue on any API level. + if (name.startsWith("OMX.google")) { + // Google OMX decoders are not known to have this issue on any API level. return false; } - // Work around: - // https://github.com/google/ExoPlayer/issues/3236, - // https://github.com/google/ExoPlayer/issues/3355, - // https://github.com/google/ExoPlayer/issues/3439, - // https://github.com/google/ExoPlayer/issues/3724, - // https://github.com/google/ExoPlayer/issues/3835, - // https://github.com/google/ExoPlayer/issues/4006, - // https://github.com/google/ExoPlayer/issues/4084, - // https://github.com/google/ExoPlayer/issues/4104, - // https://github.com/google/ExoPlayer/issues/4134, - // https://github.com/google/ExoPlayer/issues/4315, - // https://github.com/google/ExoPlayer/issues/4419, - // https://github.com/google/ExoPlayer/issues/4460, - // https://github.com/google/ExoPlayer/issues/4468. synchronized (MediaCodecVideoRenderer.class) { if (!evaluatedDeviceNeedsSetOutputSurfaceWorkaround) { - switch (Util.DEVICE) { - case "1601": - case "1713": - case "1714": - case "A10-70F": - case "A1601": - case "A2016a40": - case "A7000-a": - case "A7000plus": - case "A7010a48": - case "A7020a48": - case "AquaPowerM": - case "ASUS_X00AD_2": - case "Aura_Note_2": - case "BLACK-1X": - case "BRAVIA_ATV2": - case "C1": - case "ComioS1": - case "CP8676_I02": - case "CPH1609": - case "CPY83_I00": - case "cv1": - case "cv3": - case "deb": - case "E5643": - case "ELUGA_A3_Pro": - case "ELUGA_Note": - case "ELUGA_Prim": - case "ELUGA_Ray_X": - case "EverStar_S": - case "F3111": - case "F3113": - case "F3116": - case "F3211": - case "F3213": - case "F3215": - case "F3311": - case "flo": - case "GiONEE_CBL7513": - case "GiONEE_GBL7319": - case "GIONEE_GBL7360": - case "GIONEE_SWW1609": - case "GIONEE_SWW1627": - case "GIONEE_SWW1631": - case "GIONEE_WBL5708": - case "GIONEE_WBL7365": - case "GIONEE_WBL7519": - case "griffin": - case "htc_e56ml_dtul": - case "hwALE-H": - case "HWBLN-H": - case "HWCAM-H": - case "HWVNS-H": - case "i9031": - case "iball8735_9806": - case "Infinix-X572": - case "iris60": - case "itel_S41": - case "j2xlteins": - case "JGZ": - case "K50a40": - case "kate": - case "le_x6": - case "LS-5017": - case "M5c": - case "manning": - case "marino_f": - case "MEIZU_M5": - case "mh": - case "mido": - case "MX6": - case "namath": - case "nicklaus_f": - case "NX541J": - case "NX573J": - case "OnePlus5T": - case "p212": - case "P681": - case "P85": - case "panell_d": - case "panell_dl": - case "panell_ds": - case "panell_dt": - case "PB2-670M": - case "PGN528": - case "PGN610": - case "PGN611": - case "Phantom6": - case "Pixi4-7_3G": - case "Pixi5-10_4G": - case "PLE": - case "PRO7S": - case "Q350": - case "Q4260": - case "Q427": - case "Q4310": - case "Q5": - case "QM16XE_U": - case "QX1": - case "santoni": - case "Slate_Pro": - case "SVP-DTV15": - case "s905x018": - case "taido_row": - case "TB3-730F": - case "TB3-730X": - case "TB3-850F": - case "TB3-850M": - case "tcl_eu": - case "V1": - case "V23GB": - case "V5": - case "vernee_M5": - case "watson": - case "whyred": - case "woods_f": - case "woods_fn": - case "X3_HK": - case "XE2X": - case "XT1663": - case "Z12_PRO": - case "Z80": - deviceNeedsSetOutputSurfaceWorkaround = true; - break; - default: - // Do nothing. - break; - } - switch (Util.MODEL) { - case "AFTA": - case "AFTN": - deviceNeedsSetOutputSurfaceWorkaround = true; - break; - default: - // Do nothing. - break; + if (Util.SDK_INT <= 27 && "dangal".equals(Util.DEVICE)) { + // Dangal is affected on API level 27: https://github.com/google/ExoPlayer/issues/5169. + deviceNeedsSetOutputSurfaceWorkaround = true; + } else if (Util.SDK_INT >= 27) { + // In general, devices running API level 27 or later should be unaffected. Do nothing. + } else { + // Enable the workaround on a per-device basis. Works around: + // https://github.com/google/ExoPlayer/issues/3236, + // https://github.com/google/ExoPlayer/issues/3355, + // https://github.com/google/ExoPlayer/issues/3439, + // https://github.com/google/ExoPlayer/issues/3724, + // https://github.com/google/ExoPlayer/issues/3835, + // https://github.com/google/ExoPlayer/issues/4006, + // https://github.com/google/ExoPlayer/issues/4084, + // https://github.com/google/ExoPlayer/issues/4104, + // https://github.com/google/ExoPlayer/issues/4134, + // https://github.com/google/ExoPlayer/issues/4315, + // https://github.com/google/ExoPlayer/issues/4419, + // https://github.com/google/ExoPlayer/issues/4460, + // https://github.com/google/ExoPlayer/issues/4468. + switch (Util.DEVICE) { + case "1601": + case "1713": + case "1714": + case "A10-70F": + case "A1601": + case "A2016a40": + case "A7000-a": + case "A7000plus": + case "A7010a48": + case "A7020a48": + case "AquaPowerM": + case "ASUS_X00AD_2": + case "Aura_Note_2": + case "BLACK-1X": + case "BRAVIA_ATV2": + case "C1": + case "ComioS1": + case "CP8676_I02": + case "CPH1609": + case "CPY83_I00": + case "cv1": + case "cv3": + case "deb": + case "E5643": + case "ELUGA_A3_Pro": + case "ELUGA_Note": + case "ELUGA_Prim": + case "ELUGA_Ray_X": + case "EverStar_S": + case "F3111": + case "F3113": + case "F3116": + case "F3211": + case "F3213": + case "F3215": + case "F3311": + case "flo": + case "GiONEE_CBL7513": + case "GiONEE_GBL7319": + case "GIONEE_GBL7360": + case "GIONEE_SWW1609": + case "GIONEE_SWW1627": + case "GIONEE_SWW1631": + case "GIONEE_WBL5708": + case "GIONEE_WBL7365": + case "GIONEE_WBL7519": + case "griffin": + case "htc_e56ml_dtul": + case "hwALE-H": + case "HWBLN-H": + case "HWCAM-H": + case "HWVNS-H": + case "i9031": + case "iball8735_9806": + case "Infinix-X572": + case "iris60": + case "itel_S41": + case "j2xlteins": + case "JGZ": + case "K50a40": + case "kate": + case "le_x6": + case "LS-5017": + case "M5c": + case "manning": + case "marino_f": + case "MEIZU_M5": + case "mh": + case "mido": + case "MX6": + case "namath": + case "nicklaus_f": + case "NX541J": + case "NX573J": + case "OnePlus5T": + case "p212": + case "P681": + case "P85": + case "panell_d": + case "panell_dl": + case "panell_ds": + case "panell_dt": + case "PB2-670M": + case "PGN528": + case "PGN610": + case "PGN611": + case "Phantom6": + case "Pixi4-7_3G": + case "Pixi5-10_4G": + case "PLE": + case "PRO7S": + case "Q350": + case "Q4260": + case "Q427": + case "Q4310": + case "Q5": + case "QM16XE_U": + case "QX1": + case "santoni": + case "Slate_Pro": + case "SVP-DTV15": + case "s905x018": + case "taido_row": + case "TB3-730F": + case "TB3-730X": + case "TB3-850F": + case "TB3-850M": + case "tcl_eu": + case "V1": + case "V23GB": + case "V5": + case "vernee_M5": + case "watson": + case "whyred": + case "woods_f": + case "woods_fn": + case "X3_HK": + case "XE2X": + case "XT1663": + case "Z12_PRO": + case "Z80": + deviceNeedsSetOutputSurfaceWorkaround = true; + break; + default: + // Do nothing. + break; + } + switch (Util.MODEL) { + case "AFTA": + case "AFTN": + deviceNeedsSetOutputSurfaceWorkaround = true; + break; + default: + // Do nothing. + break; + } } evaluatedDeviceNeedsSetOutputSurfaceWorkaround = true; } From 84ad3f7b667d6f462d3d91e618f1cfc2c9ebb8dc Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 12 Dec 2018 13:56:21 +0000 Subject: [PATCH 105/210] Fix release notes PiperOrigin-RevId: 225170404 --- RELEASENOTES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6b3fbc4f27..6435f4209d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -47,10 +47,10 @@ * DASH: Parse ProgramInformation element if present in the manifest. * HLS: * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload - reader factory flags. + reader factory flags + ([#4861](https://github.com/google/ExoPlayer/issues/4861)). * Fix bug in segment sniffing ([#5039](https://github.com/google/ExoPlayer/issues/5039)). - ([#4861](https://github.com/google/ExoPlayer/issues/4861)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). * Fix issue with blind seeking to windows with non-zero offset in a From 1851d5e193db3b3050a022bc23f7b08cc5495d3f Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 14 Dec 2018 15:40:57 +0000 Subject: [PATCH 106/210] Merge pull request #5245 from natario1:videosize-override PiperOrigin-RevId: 225187852 --- .../android/exoplayer2/ui/PlayerView.java | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) 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 310d04a064..bb8395b317 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 @@ -29,7 +29,6 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Looper; import android.support.annotation.IntDef; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; @@ -271,13 +270,13 @@ public class PlayerView extends FrameLayout { private static final int SURFACE_TYPE_MONO360_VIEW = 3; // LINT.ThenChange(../../../../../../res/values/attrs.xml) - private final AspectRatioFrameLayout contentFrame; + @Nullable private final AspectRatioFrameLayout contentFrame; private final View shutterView; - private final View surfaceView; + @Nullable private final View surfaceView; private final ImageView artworkView; private final SubtitleView subtitleView; - private final @Nullable View bufferingView; - private final @Nullable TextView errorMessageView; + @Nullable private final View bufferingView; + @Nullable private final TextView errorMessageView; private final PlayerControlView controller; private final ComponentListener componentListener; private final FrameLayout overlayFrameLayout; @@ -285,11 +284,11 @@ public class PlayerView extends FrameLayout { private Player player; private boolean useController; private boolean useArtwork; - private @Nullable Drawable defaultArtwork; + @Nullable private Drawable defaultArtwork; private @ShowBuffering int showBuffering; private boolean keepContentOnPlayerReset; - private @Nullable ErrorMessageProvider errorMessageProvider; - private @Nullable CharSequence customErrorMessage; + @Nullable private ErrorMessageProvider errorMessageProvider; + @Nullable private CharSequence customErrorMessage; private int controllerShowTimeoutMs; private boolean controllerAutoShow; private boolean controllerHideDuringAds; @@ -474,9 +473,7 @@ public class PlayerView extends FrameLayout { * @param newPlayerView The new view to attach to the player. */ public static void switchTargetView( - @NonNull Player player, - @Nullable PlayerView oldPlayerView, - @Nullable PlayerView newPlayerView) { + Player player, @Nullable PlayerView oldPlayerView, @Nullable PlayerView newPlayerView) { if (oldPlayerView == newPlayerView) { return; } @@ -1074,6 +1071,26 @@ public class PlayerView extends FrameLayout { } } + /** + * Called when there's a change in the aspect ratio of the content being displayed. The default + * implementation sets the aspect ratio of the content frame to that of the content, unless the + * content view is a {@link SphericalSurfaceView} in which case the frame's aspect ratio is + * cleared. + * + * @param contentAspectRatio The aspect ratio of the content. + * @param contentFrame The content frame, or {@code null}. + * @param contentView The view that holds the content being displayed, or {@code null}. + */ + protected void onContentAspectRatioChanged( + float contentAspectRatio, + @Nullable AspectRatioFrameLayout contentFrame, + @Nullable View contentView) { + if (contentFrame != null) { + contentFrame.setAspectRatio( + contentView instanceof SphericalSurfaceView ? 0 : contentAspectRatio); + } + } + private boolean toggleControllerVisibility() { if (!useController || player == null) { return false; @@ -1187,9 +1204,8 @@ public class PlayerView extends FrameLayout { int drawableWidth = drawable.getIntrinsicWidth(); int drawableHeight = drawable.getIntrinsicHeight(); if (drawableWidth > 0 && drawableHeight > 0) { - if (contentFrame != null) { - contentFrame.setAspectRatio((float) drawableWidth / drawableHeight); - } + float artworkAspectRatio = (float) drawableWidth / drawableHeight; + onContentAspectRatioChanged(artworkAspectRatio, contentFrame, artworkView); artworkView.setImageDrawable(drawable); artworkView.setVisibility(VISIBLE); return true; @@ -1322,9 +1338,6 @@ public class PlayerView extends FrameLayout { @Override public void onVideoSizeChanged( int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { - if (contentFrame == null) { - return; - } float videoAspectRatio = (height == 0 || width == 0) ? 1 : (width * pixelWidthHeightRatio) / height; @@ -1345,11 +1358,9 @@ public class PlayerView extends FrameLayout { surfaceView.addOnLayoutChangeListener(this); } applyTextureViewRotation((TextureView) surfaceView, textureViewRotation); - } else if (surfaceView instanceof SphericalSurfaceView) { - videoAspectRatio = 0; } - contentFrame.setAspectRatio(videoAspectRatio); + onContentAspectRatioChanged(videoAspectRatio, contentFrame, surfaceView); } @Override From 942ac78a0c74f0fa16c712317fd7fdf22d88f4c9 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 13 Dec 2018 11:23:55 +0000 Subject: [PATCH 107/210] Add 4K Bravia to output surface workaroud PiperOrigin-RevId: 225344232 --- .../google/android/exoplayer2/video/MediaCodecVideoRenderer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 5d6fba5b0d..f16d77b250 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -1339,6 +1339,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { case "Aura_Note_2": case "BLACK-1X": case "BRAVIA_ATV2": + case "BRAVIA_ATV3_4K": case "C1": case "ComioS1": case "CP8676_I02": From fd6874809a7d5383636cbe5e6d5b360118e4f972 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 13 Dec 2018 16:17:14 +0000 Subject: [PATCH 108/210] Clarify that the shutter can prevent flicker PiperOrigin-RevId: 225374071 --- .../java/com/google/android/exoplayer2/ui/PlayerView.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 bb8395b317..d9989370b4 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 @@ -186,8 +186,9 @@ import java.util.List; *

  • Type: {@link AspectRatioFrameLayout} * *
  • {@code exo_shutter} - A view that's made visible when video should be hidden. This - * view is typically an opaque view that covers the video surface view, thereby obscuring it - * when visible. + * view is typically an opaque view that covers the video surface, thereby obscuring it when + * visible. Obscuring the surface in this way also helps to prevent flicker at the start of + * playback when {@code surface_type="surface_view"} *
      *
    • Type: {@link View} *
    From e7e2cbdc909c4aa8a8cb39d25607dfdc382a7545 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 13 Dec 2018 16:58:39 +0000 Subject: [PATCH 109/210] Add missing .. PiperOrigin-RevId: 225379305 --- .../main/java/com/google/android/exoplayer2/ui/PlayerView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d9989370b4..88eabfed07 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 @@ -188,7 +188,7 @@ import java.util.List; *
  • {@code exo_shutter} - A view that's made visible when video should be hidden. This * view is typically an opaque view that covers the video surface, thereby obscuring it when * visible. Obscuring the surface in this way also helps to prevent flicker at the start of - * playback when {@code surface_type="surface_view"} + * playback when {@code surface_type="surface_view"}. *
      *
    • Type: {@link View} *
    From 479841f65f58d4d5341b5db07a82ee3617b846bb Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 13 Dec 2018 17:25:43 +0000 Subject: [PATCH 110/210] Add Nexus Player to output surface workaround PiperOrigin-RevId: 225383173 --- .../google/android/exoplayer2/video/MediaCodecVideoRenderer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index f16d77b250..40b25c2b2e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -1362,6 +1362,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { case "F3215": case "F3311": case "flo": + case "fugu": case "GiONEE_CBL7513": case "GiONEE_GBL7319": case "GIONEE_GBL7360": From f41dadca5851d965cab5f46607de9e4c207b9284 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 14 Dec 2018 13:55:12 +0000 Subject: [PATCH 111/210] Remove unused interface method PiperOrigin-RevId: 225528632 --- .../exoplayer2/ext/mediasession/TimelineQueueEditor.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java index 4f9c553a15..7c00fcdf17 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java @@ -64,13 +64,6 @@ public final class TimelineQueueEditor * {@link MediaSessionConnector}. */ public interface QueueDataAdapter { - /** - * Gets the {@link MediaDescriptionCompat} for a {@code position}. - * - * @param position The position in the queue for which to provide a description. - * @return A {@link MediaDescriptionCompat}. - */ - MediaDescriptionCompat getMediaDescription(int position); /** * Adds a {@link MediaDescriptionCompat} at the given {@code position}. * From abdb58485f9abddafd367c90a730b830df2e1c0a Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 17 Dec 2018 10:48:01 +0000 Subject: [PATCH 112/210] Add Player.MetadataComponent for completeness PiperOrigin-RevId: 225795581 --- .../exoplayer2/ext/cast/CastPlayer.java | 9 +++++++ .../android/exoplayer2/ExoPlayerImpl.java | 9 +++++++ .../com/google/android/exoplayer2/Player.java | 25 +++++++++++++++++ .../android/exoplayer2/SimpleExoPlayer.java | 27 +++++++++++-------- .../exoplayer2/testutil/StubExoPlayer.java | 5 ++++ 5 files changed, 64 insertions(+), 11 deletions(-) diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 6cf6309796..71322de87e 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -283,20 +283,29 @@ public final class CastPlayer extends BasePlayer { // Player implementation. @Override + @Nullable public AudioComponent getAudioComponent() { return null; } @Override + @Nullable public VideoComponent getVideoComponent() { return null; } @Override + @Nullable public TextComponent getTextComponent() { return null; } + @Override + @Nullable + public MetadataComponent getMetadataComponent() { + return null; + } + @Override public Looper getApplicationLooper() { return Looper.getMainLooper(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index ffdadb78f7..35fa85e467 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -144,20 +144,29 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override + @Nullable public AudioComponent getAudioComponent() { return null; } @Override + @Nullable public VideoComponent getVideoComponent() { return null; } @Override + @Nullable public TextComponent getTextComponent() { return null; } + @Override + @Nullable + public MetadataComponent getMetadataComponent() { + return null; + } + @Override public Looper getPlaybackLooper() { return internalPlayer.getPlaybackLooper(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 16f8aa2878..e3441fb2a7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.C.VideoScalingMode; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.audio.AuxEffectInfo; +import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; @@ -299,6 +300,24 @@ public interface Player { void removeTextOutput(TextOutput listener); } + /** The metadata component of a {@link Player}. */ + interface MetadataComponent { + + /** + * Adds a {@link MetadataOutput} to receive metadata. + * + * @param output The output to register. + */ + void addMetadataOutput(MetadataOutput output); + + /** + * Removes a {@link MetadataOutput}. + * + * @param output The output to remove. + */ + void removeMetadataOutput(MetadataOutput output); + } + /** * Listener of changes in player state. All methods have no-op default implementations to allow * selective overrides. @@ -533,6 +552,12 @@ public interface Player { @Nullable TextComponent getTextComponent(); + /** + * Returns the component of this player for metadata output, or null if metadata is not supported. + */ + @Nullable + MetadataComponent getMetadataComponent(); + /** * Returns the {@link Looper} associated with the application thread that's used to access the * player and on which player events are received. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 8517556887..fe52cc7e8c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -65,7 +65,11 @@ import java.util.concurrent.CopyOnWriteArraySet; */ @TargetApi(16) public class SimpleExoPlayer extends BasePlayer - implements ExoPlayer, Player.AudioComponent, Player.VideoComponent, Player.TextComponent { + implements ExoPlayer, + Player.AudioComponent, + Player.VideoComponent, + Player.TextComponent, + Player.MetadataComponent { /** @deprecated Use {@link com.google.android.exoplayer2.video.VideoListener}. */ @Deprecated @@ -243,20 +247,29 @@ public class SimpleExoPlayer extends BasePlayer } @Override + @Nullable public AudioComponent getAudioComponent() { return this; } @Override + @Nullable public VideoComponent getVideoComponent() { return this; } @Override + @Nullable public TextComponent getTextComponent() { return this; } + @Override + @Nullable + public MetadataComponent getMetadataComponent() { + return this; + } + /** * Sets the video scaling mode. * @@ -713,20 +726,12 @@ public class SimpleExoPlayer extends BasePlayer removeTextOutput(output); } - /** - * Adds a {@link MetadataOutput} to receive metadata. - * - * @param listener The output to register. - */ + @Override public void addMetadataOutput(MetadataOutput listener) { metadataOutputs.add(listener); } - /** - * Removes a {@link MetadataOutput}. - * - * @param listener The output to remove. - */ + @Override public void removeMetadataOutput(MetadataOutput listener) { metadataOutputs.remove(listener); } diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 156b573df8..724ed366bc 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -49,6 +49,11 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } + @Override + public MetadataComponent getMetadataComponent() { + throw new UnsupportedOperationException(); + } + @Override public Looper getPlaybackLooper() { throw new UnsupportedOperationException(); From 78cdd5fee1ecdb0724620753637a8d719c9482e1 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 18 Dec 2018 19:46:21 +0000 Subject: [PATCH 113/210] Merge pull request #5216 from mseroczynski:dev-v2 PiperOrigin-RevId: 225966289 --- .../exoplayer2/mediacodec/MediaCodecUtil.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index 893601a859..f6cc6a8344 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -318,7 +318,20 @@ public final class MediaCodecUtil { } // Work around https://github.com/google/ExoPlayer/issues/4519. - if ("OMX.SEC.mp3.dec".equals(name) && "SM-T530".equals(Util.MODEL)) { + if ("OMX.SEC.mp3.dec".equals(name) + && ("GT-I9152".equals(Util.MODEL) + || "GT-I9515".equals(Util.MODEL) + || "GT-P5220".equals(Util.MODEL) + || "GT-S7580".equals(Util.MODEL) + || "SM-G350".equals(Util.MODEL) + || "SM-T231".equals(Util.MODEL) + || "SM-T530".equals(Util.MODEL))) { + return false; + } + if ("OMX.brcm.audio.mp3.decoder".equals(name) + && ("GT-I9152".equals(Util.MODEL) + || "GT-S7580".equals(Util.MODEL) + || "SM-G350".equals(Util.MODEL))) { return false; } From 975ed6c2772cadf8238f5cf5967f1a7cc92813f4 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 18 Dec 2018 14:58:01 +0000 Subject: [PATCH 114/210] Use the true bitrate for CBR MP3 seeking PiperOrigin-RevId: 225989898 --- .../exoplayer2/extractor/MpegAudioHeader.java | 59 ++++++++++++------- .../test/assets/mp3/play-trimmed.mp3.0.dump | 2 +- .../test/assets/mp3/play-trimmed.mp3.1.dump | 2 +- .../test/assets/mp3/play-trimmed.mp3.2.dump | 2 +- .../test/assets/mp3/play-trimmed.mp3.3.dump | 2 +- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java index ab49ca5454..87bb992082 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java @@ -34,16 +34,26 @@ public final class MpegAudioHeader { private static final String[] MIME_TYPE_BY_LAYER = new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG}; private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000}; - private static final int[] BITRATE_V1_L1 = - {32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}; - private static final int[] BITRATE_V2_L1 = - {32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}; - private static final int[] BITRATE_V1_L2 = - {32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}; - private static final int[] BITRATE_V1_L3 = - {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}; - private static final int[] BITRATE_V2 = - {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}; + private static final int[] BITRATE_V1_L1 = { + 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, + 416000, 448000 + }; + private static final int[] BITRATE_V2_L1 = { + 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, + 224000, 256000 + }; + private static final int[] BITRATE_V1_L2 = { + 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, + 320000, 384000 + }; + private static final int[] BITRATE_V1_L3 = { + 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, + 320000 + }; + private static final int[] BITRATE_V2 = { + 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, + 160000 + }; /** * Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it @@ -89,7 +99,7 @@ public final class MpegAudioHeader { if (layer == 3) { // Layer I (layer == 3) bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; - return (12000 * bitrate / samplingRate + padding) * 4; + return (12 * bitrate / samplingRate + padding) * 4; } else { // Layer II (layer == 2) or III (layer == 1) if (version == 3) { @@ -102,10 +112,10 @@ public final class MpegAudioHeader { if (version == 3) { // Version 1 - return 144000 * bitrate / samplingRate + padding; + return 144 * bitrate / samplingRate + padding; } else { // Version 2 or 2.5 - return (layer == 1 ? 72000 : 144000) * bitrate / samplingRate + padding; + return (layer == 1 ? 72 : 144) * bitrate / samplingRate + padding; } } @@ -159,7 +169,7 @@ public final class MpegAudioHeader { if (layer == 3) { // Layer I (layer == 3) bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; - frameSize = (12000 * bitrate / sampleRate + padding) * 4; + frameSize = (12 * bitrate / sampleRate + padding) * 4; samplesPerFrame = 384; } else { // Layer II (layer == 2) or III (layer == 1) @@ -167,19 +177,22 @@ public final class MpegAudioHeader { // Version 1 bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1]; samplesPerFrame = 1152; - frameSize = 144000 * bitrate / sampleRate + padding; + frameSize = 144 * bitrate / sampleRate + padding; } else { // Version 2 or 2.5. bitrate = BITRATE_V2[bitrateIndex - 1]; samplesPerFrame = layer == 1 ? 576 : 1152; - frameSize = (layer == 1 ? 72000 : 144000) * bitrate / sampleRate + padding; + frameSize = (layer == 1 ? 72 : 144) * bitrate / sampleRate + padding; } } + // Calculate the bitrate in the same way Mp3Extractor calculates sample timestamps so that + // seeking to a given timestamp and playing from the start up to that timestamp give the same + // results for CBR streams. See also [internal: b/120390268]. + bitrate = 8 * frameSize * sampleRate / samplesPerFrame; String mimeType = MIME_TYPE_BY_LAYER[3 - layer]; int channels = ((headerData >> 6) & 3) == 3 ? 1 : 2; - header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate * 1000, - samplesPerFrame); + header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate, samplesPerFrame); return true; } @@ -198,8 +211,14 @@ public final class MpegAudioHeader { /** Number of samples stored in the frame. */ public int samplesPerFrame; - private void setValues(int version, String mimeType, int frameSize, int sampleRate, int channels, - int bitrate, int samplesPerFrame) { + private void setValues( + int version, + String mimeType, + int frameSize, + int sampleRate, + int channels, + int bitrate, + int samplesPerFrame) { this.version = version; this.mimeType = mimeType; this.frameSize = frameSize; diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump index 96b0cd259c..d4df3ffeba 100644 --- a/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump +++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump @@ -1,6 +1,6 @@ seekMap: isSeekable = true - duration = 26125 + duration = 26122 getPosition(0) = [[timeUs=0, position=0]] numberOfTracks = 1 track 0: diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump index 96b0cd259c..d4df3ffeba 100644 --- a/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump +++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump @@ -1,6 +1,6 @@ seekMap: isSeekable = true - duration = 26125 + duration = 26122 getPosition(0) = [[timeUs=0, position=0]] numberOfTracks = 1 track 0: diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump index 96b0cd259c..d4df3ffeba 100644 --- a/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump +++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump @@ -1,6 +1,6 @@ seekMap: isSeekable = true - duration = 26125 + duration = 26122 getPosition(0) = [[timeUs=0, position=0]] numberOfTracks = 1 track 0: diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump index 96b0cd259c..d4df3ffeba 100644 --- a/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump +++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump @@ -1,6 +1,6 @@ seekMap: isSeekable = true - duration = 26125 + duration = 26122 getPosition(0) = [[timeUs=0, position=0]] numberOfTracks = 1 track 0: From ca9ecaa4480fc74ebd5c32b07e4fd17be10da21c Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 19 Dec 2018 19:30:09 +0000 Subject: [PATCH 115/210] Blacklist OMX.SEC.mp3.dec for more devices Issue #4519 PiperOrigin-RevId: 226205245 --- .../exoplayer2/mediacodec/MediaCodecUtil.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index f6cc6a8344..4d971d461e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -319,19 +319,20 @@ public final class MediaCodecUtil { // Work around https://github.com/google/ExoPlayer/issues/4519. if ("OMX.SEC.mp3.dec".equals(name) - && ("GT-I9152".equals(Util.MODEL) - || "GT-I9515".equals(Util.MODEL) - || "GT-P5220".equals(Util.MODEL) - || "GT-S7580".equals(Util.MODEL) - || "SM-G350".equals(Util.MODEL) - || "SM-T231".equals(Util.MODEL) - || "SM-T530".equals(Util.MODEL))) { + && (Util.MODEL.startsWith("GT-I9152") + || Util.MODEL.startsWith("GT-I9515") + || Util.MODEL.startsWith("GT-P5220") + || Util.MODEL.startsWith("GT-S7580") + || Util.MODEL.startsWith("SM-G350") + || Util.MODEL.startsWith("SM-G386") + || Util.MODEL.startsWith("SM-T231") + || Util.MODEL.startsWith("SM-T530"))) { return false; } if ("OMX.brcm.audio.mp3.decoder".equals(name) - && ("GT-I9152".equals(Util.MODEL) - || "GT-S7580".equals(Util.MODEL) - || "SM-G350".equals(Util.MODEL))) { + && (Util.MODEL.startsWith("GT-I9152") + || Util.MODEL.startsWith("GT-S7580") + || Util.MODEL.startsWith("SM-G350"))) { return false; } From fa82004fa3423021a25149bce5f2a50e884ab804 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 14 Dec 2018 15:42:57 +0000 Subject: [PATCH 116/210] Merge pull request #5066 from szaboa:feature/1583_support_png_ttml PiperOrigin-RevId: 225531695 --- .../exoplayer2/text/ttml/TtmlDecoder.java | 140 ++++++++++++-- .../exoplayer2/text/ttml/TtmlNode.java | 117 +++++++++--- .../exoplayer2/text/ttml/TtmlSubtitle.java | 11 +- .../assets/ttml/bitmap_percentage_region.xml | 26 +++ .../test/assets/ttml/bitmap_pixel_region.xml | 23 +++ .../assets/ttml/bitmap_unsupported_region.xml | 23 +++ .../exoplayer2/text/ttml/TtmlDecoderTest.java | 178 +++++++++++++----- 7 files changed, 429 insertions(+), 89 deletions(-) create mode 100644 library/core/src/test/assets/ttml/bitmap_percentage_region.xml create mode 100644 library/core/src/test/assets/ttml/bitmap_pixel_region.xml create mode 100644 library/core/src/test/assets/ttml/bitmap_unsupported_region.xml diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java index 2e868077a5..b39f467968 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java @@ -68,6 +68,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { private static final String ATTR_END = "end"; private static final String ATTR_STYLE = "style"; private static final String ATTR_REGION = "region"; + private static final String ATTR_IMAGE = "backgroundImage"; private static final Pattern CLOCK_TIME = Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])" @@ -77,6 +78,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { private static final Pattern FONT_SIZE = Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$"); private static final Pattern PERCENTAGE_COORDINATES = Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$"); + private static final Pattern PIXEL_COORDINATES = + Pattern.compile("^(\\d+\\.?\\d*?)px (\\d+\\.?\\d*?)px$"); private static final Pattern CELL_RESOLUTION = Pattern.compile("^(\\d+) (\\d+)$"); private static final int DEFAULT_FRAME_RATE = 30; @@ -105,6 +108,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { XmlPullParser xmlParser = xmlParserFactory.newPullParser(); Map globalStyles = new HashMap<>(); Map regionMap = new HashMap<>(); + Map imageMap = new HashMap<>(); regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(null)); ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length); xmlParser.setInput(inputStream, null); @@ -114,6 +118,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { int eventType = xmlParser.getEventType(); FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE; CellResolution cellResolution = DEFAULT_CELL_RESOLUTION; + TtsExtent ttsExtent = null; while (eventType != XmlPullParser.END_DOCUMENT) { TtmlNode parent = nodeStack.peek(); if (unsupportedNodeDepth == 0) { @@ -122,12 +127,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { if (TtmlNode.TAG_TT.equals(name)) { frameAndTickRate = parseFrameAndTickRates(xmlParser); cellResolution = parseCellResolution(xmlParser, DEFAULT_CELL_RESOLUTION); + ttsExtent = parseTtsExtent(xmlParser); } if (!isSupportedTag(name)) { Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName()); unsupportedNodeDepth++; } else if (TtmlNode.TAG_HEAD.equals(name)) { - parseHeader(xmlParser, globalStyles, regionMap, cellResolution); + parseHeader(xmlParser, globalStyles, cellResolution, ttsExtent, regionMap, imageMap); } else { try { TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate); @@ -145,7 +151,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { parent.addChild(TtmlNode.buildTextNode(xmlParser.getText())); } else if (eventType == XmlPullParser.END_TAG) { if (xmlParser.getName().equals(TtmlNode.TAG_TT)) { - ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap); + ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap, imageMap); } nodeStack.pop(); } @@ -226,11 +232,34 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } } + private TtsExtent parseTtsExtent(XmlPullParser xmlParser) { + String ttsExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); + if (ttsExtent == null) { + return null; + } + + Matcher extentMatcher = PIXEL_COORDINATES.matcher(ttsExtent); + if (!extentMatcher.matches()) { + Log.w(TAG, "Ignoring non-pixel tts extent: " + ttsExtent); + return null; + } + try { + int width = Integer.parseInt(extentMatcher.group(1)); + int height = Integer.parseInt(extentMatcher.group(2)); + return new TtsExtent(width, height); + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed tts extent: " + ttsExtent); + return null; + } + } + private Map parseHeader( XmlPullParser xmlParser, Map globalStyles, + CellResolution cellResolution, + TtsExtent ttsExtent, Map globalRegions, - CellResolution cellResolution) + Map imageMap) throws IOException, XmlPullParserException { do { xmlParser.next(); @@ -246,23 +275,41 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { globalStyles.put(style.getId(), style); } } else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) { - TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser, cellResolution); + TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser, cellResolution, ttsExtent); if (ttmlRegion != null) { globalRegions.put(ttmlRegion.id, ttmlRegion); } + } else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_METADATA)) { + parseMetadata(xmlParser, imageMap); } } while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD)); return globalStyles; } + private void parseMetadata(XmlPullParser xmlParser, Map imageMap) + throws IOException, XmlPullParserException { + do { + xmlParser.next(); + if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_IMAGE)) { + String id = XmlPullParserUtil.getAttributeValue(xmlParser, "id"); + if (id != null) { + String encodedBitmapData = xmlParser.nextText(); + imageMap.put(id, encodedBitmapData); + } + } + } while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_METADATA)); + } + /** * Parses a region declaration. * - *

    If the region defines an origin and extent, it is required that they're defined as - * percentages of the viewport. Region declarations that define origin and extent in other formats - * are unsupported, and null is returned. + *

    Supports both percentage and pixel defined regions. In case of pixel defined regions the + * passed {@code ttsExtent} is used as a reference window to convert the pixel values to + * fractions. In case of missing tts:extent the pixel defined regions can't be parsed, and null is + * returned. */ - private TtmlRegion parseRegionAttributes(XmlPullParser xmlParser, CellResolution cellResolution) { + private TtmlRegion parseRegionAttributes( + XmlPullParser xmlParser, CellResolution cellResolution, TtsExtent ttsExtent) { String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID); if (regionId == null) { return null; @@ -270,13 +317,30 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { float position; float line; + String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN); if (regionOrigin != null) { - Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); - if (originMatcher.matches()) { + Matcher originPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); + Matcher originPixelMatcher = PIXEL_COORDINATES.matcher(regionOrigin); + if (originPercentageMatcher.matches()) { try { - position = Float.parseFloat(originMatcher.group(1)) / 100f; - line = Float.parseFloat(originMatcher.group(2)) / 100f; + position = Float.parseFloat(originPercentageMatcher.group(1)) / 100f; + line = Float.parseFloat(originPercentageMatcher.group(2)) / 100f; + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin); + return null; + } + } else if (originPixelMatcher.matches()) { + if (ttsExtent == null) { + Log.w(TAG, "Ignoring region with missing tts:extent: " + regionOrigin); + return null; + } + try { + int width = Integer.parseInt(originPixelMatcher.group(1)); + int height = Integer.parseInt(originPixelMatcher.group(2)); + // Convert pixel values to fractions. + position = width / (float) ttsExtent.width; + line = height / (float) ttsExtent.height; } catch (NumberFormatException e) { Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin); return null; @@ -299,11 +363,27 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { float height; String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); if (regionExtent != null) { - Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent); - if (extentMatcher.matches()) { + Matcher extentPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent); + Matcher extentPixelMatcher = PIXEL_COORDINATES.matcher(regionExtent); + if (extentPercentageMatcher.matches()) { try { - width = Float.parseFloat(extentMatcher.group(1)) / 100f; - height = Float.parseFloat(extentMatcher.group(2)) / 100f; + width = Float.parseFloat(extentPercentageMatcher.group(1)) / 100f; + height = Float.parseFloat(extentPercentageMatcher.group(2)) / 100f; + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin); + return null; + } + } else if (extentPixelMatcher.matches()) { + if (ttsExtent == null) { + Log.w(TAG, "Ignoring region with missing tts:extent: " + regionOrigin); + return null; + } + try { + int extentWidth = Integer.parseInt(extentPixelMatcher.group(1)); + int extentHeight = Integer.parseInt(extentPixelMatcher.group(2)); + // Convert pixel values to fractions. + width = extentWidth / (float) ttsExtent.width; + height = extentHeight / (float) ttsExtent.height; } catch (NumberFormatException e) { Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin); return null; @@ -457,6 +537,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { long startTime = C.TIME_UNSET; long endTime = C.TIME_UNSET; String regionId = TtmlNode.ANONYMOUS_REGION_ID; + String imageId = null; String[] styleIds = null; int attributeCount = parser.getAttributeCount(); TtmlStyle style = parseStyleAttributes(parser, null); @@ -487,6 +568,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { regionId = value; } break; + case ATTR_IMAGE: + // Parse URI reference only if refers to an element in the same document (it must start + // with '#'). Resolving URIs from external sources is not supported. + if (value.startsWith("#")) { + imageId = value.substring(1); + } + break; default: // Do nothing. break; @@ -509,7 +597,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { endTime = parent.endTimeUs; } } - return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId); + return TtmlNode.buildNode( + parser.getName(), startTime, endTime, style, styleIds, regionId, imageId); } private static boolean isSupportedTag(String tag) { @@ -525,9 +614,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { || tag.equals(TtmlNode.TAG_LAYOUT) || tag.equals(TtmlNode.TAG_REGION) || tag.equals(TtmlNode.TAG_METADATA) - || tag.equals(TtmlNode.TAG_SMPTE_IMAGE) - || tag.equals(TtmlNode.TAG_SMPTE_DATA) - || tag.equals(TtmlNode.TAG_SMPTE_INFORMATION); + || tag.equals(TtmlNode.TAG_IMAGE) + || tag.equals(TtmlNode.TAG_DATA) + || tag.equals(TtmlNode.TAG_INFORMATION); } private static void parseFontSize(String expression, TtmlStyle out) throws @@ -651,4 +740,15 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { this.rows = rows; } } + + /** Represents the tts:extent for a TTML file. */ + private static final class TtsExtent { + final int width; + final int height; + + TtsExtent(int width, int height) { + this.width = width; + this.height = height; + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java index c8b9a59de4..020bbe201b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java @@ -15,7 +15,12 @@ */ package com.google.android.exoplayer2.text.ttml; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.support.annotation.Nullable; import android.text.SpannableStringBuilder; +import android.util.Base64; +import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.util.Assertions; @@ -44,9 +49,9 @@ import java.util.TreeSet; public static final String TAG_LAYOUT = "layout"; public static final String TAG_REGION = "region"; public static final String TAG_METADATA = "metadata"; - public static final String TAG_SMPTE_IMAGE = "smpte:image"; - public static final String TAG_SMPTE_DATA = "smpte:data"; - public static final String TAG_SMPTE_INFORMATION = "smpte:information"; + public static final String TAG_IMAGE = "image"; + public static final String TAG_DATA = "data"; + public static final String TAG_INFORMATION = "information"; public static final String ANONYMOUS_REGION_ID = ""; public static final String ATTR_ID = "id"; @@ -75,34 +80,57 @@ import java.util.TreeSet; public static final String START = "start"; public static final String END = "end"; - public final String tag; - public final String text; + @Nullable public final String tag; + @Nullable public final String text; public final boolean isTextNode; public final long startTimeUs; public final long endTimeUs; - public final TtmlStyle style; + @Nullable public final TtmlStyle style; + @Nullable private final String[] styleIds; public final String regionId; + @Nullable public final String imageId; - private final String[] styleIds; private final HashMap nodeStartsByRegion; private final HashMap nodeEndsByRegion; private List children; public static TtmlNode buildTextNode(String text) { - return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), C.TIME_UNSET, - C.TIME_UNSET, null, null, ANONYMOUS_REGION_ID); + return new TtmlNode( + /* tag= */ null, + TtmlRenderUtil.applyTextElementSpacePolicy(text), + /* startTimeUs= */ C.TIME_UNSET, + /* endTimeUs= */ C.TIME_UNSET, + /* style= */ null, + /* styleIds= */ null, + ANONYMOUS_REGION_ID, + /* imageId= */ null); } - public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs, - TtmlStyle style, String[] styleIds, String regionId) { - return new TtmlNode(tag, null, startTimeUs, endTimeUs, style, styleIds, regionId); + public static TtmlNode buildNode( + @Nullable String tag, + long startTimeUs, + long endTimeUs, + @Nullable TtmlStyle style, + @Nullable String[] styleIds, + String regionId, + @Nullable String imageId) { + return new TtmlNode( + tag, /* text= */ null, startTimeUs, endTimeUs, style, styleIds, regionId, imageId); } - private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs, - TtmlStyle style, String[] styleIds, String regionId) { + private TtmlNode( + @Nullable String tag, + @Nullable String text, + long startTimeUs, + long endTimeUs, + @Nullable TtmlStyle style, + @Nullable String[] styleIds, + String regionId, + @Nullable String imageId) { this.tag = tag; this.text = text; + this.imageId = imageId; this.style = style; this.styleIds = styleIds; this.isTextNode = text != null; @@ -151,7 +179,8 @@ import java.util.TreeSet; private void getEventTimes(TreeSet out, boolean descendsPNode) { boolean isPNode = TAG_P.equals(tag); - if (descendsPNode || isPNode) { + boolean isDivNode = TAG_DIV.equals(tag); + if (descendsPNode || isPNode || (isDivNode && imageId != null)) { if (startTimeUs != C.TIME_UNSET) { out.add(startTimeUs); } @@ -171,13 +200,46 @@ import java.util.TreeSet; return styleIds; } - public List getCues(long timeUs, Map globalStyles, - Map regionMap) { - TreeMap regionOutputs = new TreeMap<>(); - traverseForText(timeUs, false, regionId, regionOutputs); - traverseForStyle(timeUs, globalStyles, regionOutputs); + public List getCues( + long timeUs, + Map globalStyles, + Map regionMap, + Map imageMap) { + + List> regionImageOutputs = new ArrayList<>(); + traverseForImage(timeUs, regionId, regionImageOutputs); + + TreeMap regionTextOutputs = new TreeMap<>(); + traverseForText(timeUs, false, regionId, regionTextOutputs); + traverseForStyle(timeUs, globalStyles, regionTextOutputs); + List cues = new ArrayList<>(); - for (Entry entry : regionOutputs.entrySet()) { + + // Create image based cues. + for (Pair regionImagePair : regionImageOutputs) { + String encodedBitmapData = imageMap.get(regionImagePair.second); + if (encodedBitmapData == null) { + // Image reference points to an invalid image. Do nothing. + continue; + } + + byte[] bitmapData = Base64.decode(encodedBitmapData, Base64.DEFAULT); + Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, /* offset= */ 0, bitmapData.length); + TtmlRegion region = regionMap.get(regionImagePair.first); + + cues.add( + new Cue( + bitmap, + region.position, + Cue.ANCHOR_TYPE_MIDDLE, + region.line, + region.lineAnchor, + region.width, + /* height= */ Cue.DIMEN_UNSET)); + } + + // Create text based cues. + for (Entry entry : regionTextOutputs.entrySet()) { TtmlRegion region = regionMap.get(entry.getKey()); cues.add( new Cue( @@ -192,9 +254,22 @@ import java.util.TreeSet; region.textSizeType, region.textSize)); } + return cues; } + private void traverseForImage( + long timeUs, String inheritedRegion, List> regionImageList) { + String resolvedRegionId = ANONYMOUS_REGION_ID.equals(regionId) ? inheritedRegion : regionId; + if (isActive(timeUs) && TAG_DIV.equals(tag) && imageId != null) { + regionImageList.add(new Pair<>(resolvedRegionId, imageId)); + return; + } + for (int i = 0; i < getChildCount(); ++i) { + getChild(i).traverseForImage(timeUs, resolvedRegionId, regionImageList); + } + } + private void traverseForText( long timeUs, boolean descendsPNode, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java index 50916aa841..7b30461750 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java @@ -32,11 +32,16 @@ import java.util.Map; private final long[] eventTimesUs; private final Map globalStyles; private final Map regionMap; + private final Map imageMap; - public TtmlSubtitle(TtmlNode root, Map globalStyles, - Map regionMap) { + public TtmlSubtitle( + TtmlNode root, + Map globalStyles, + Map regionMap, + Map imageMap) { this.root = root; this.regionMap = regionMap; + this.imageMap = imageMap; this.globalStyles = globalStyles != null ? Collections.unmodifiableMap(globalStyles) : Collections.emptyMap(); this.eventTimesUs = root.getEventTimesUs(); @@ -65,7 +70,7 @@ import java.util.Map; @Override public List getCues(long timeUs) { - return root.getCues(timeUs, globalStyles, regionMap); + return root.getCues(timeUs, globalStyles, regionMap, imageMap); } /* @VisibleForTesting */ diff --git a/library/core/src/test/assets/ttml/bitmap_percentage_region.xml b/library/core/src/test/assets/ttml/bitmap_percentage_region.xml new file mode 100644 index 0000000000..9631650178 --- /dev/null +++ b/library/core/src/test/assets/ttml/bitmap_percentage_region.xml @@ -0,0 +1,26 @@ + + + + + iVBORw0KGgoAAAANSUhEUgAAAXAAAABICAYAAADvR65LAAAACXBIWXMAAAsTAAALEwEAmpwYAAANIUlEQVR4nO2d/5GjSA+GNVVfALMh4EyucAIXxDiGCWFiwKngukwghA1h74/vxMia/t3qNtjvU7W1u8Ygtbp53agFvL2/v/8hAAAAh+N/j3YAAABAGRBwAAA4KBBwAAA4KC8j4MMwEBHRuq4P9qQ/vdv+yrFOBTECFhxawFNPgnEc6Xw+ExHRPM90u92a+7YXerZ9HEc6nU50Op2IiGhZFprnGSKleOXxCGwxE/BxHLd/uwYkb1+WpeqE1iLBLMuy/XEdX54wRyEW01RCbbeyMQwDnc/nzRYRBfvjWUmN517Ho9V4eDTP0o4YJgLOJ+/pdHLOKMZxpMvlQkRE0zQVn9B8HC3eknmeq2zsBSmI8zw3EUJLG1K85Y/psiyWLu+aHn3WkqP7zzxLO1IwEXB92ezbzsEsQYu3Fge2wX+etcNKaC2iwzDc9cs0TU896wFgL5gKuEug9cldIqxyhk/0c5bNNng7xOMbGYuWcZF9jPgD0IdqAY8JdGx2nkJsYWxdV1rXFcLhoXVcQiktAEA7igScqz+I6MeCotwmt7N4l5ZP+VIntZT4k7NP7Luu7fqKQsc4N3Ytbej+1p9pm67PYrYs4l3SDzlYx7PVeAwdI8f/Hn1Zsm9tP9SOtdz21WYosgVclkAR3QdIpjnkdv77crls4ltaPlU72/N1bKzkTadxUvaRsXItrLrKyfgzPQhLY9fShus4cmzIdms/2Cbvp+NTG2+XDT4Gp3lcNlLbHotDajx7jkcr/3v0Zcm+pf1gNdb02A+NI+mrhO2mjr9sAT+dTj8cldtCAqtn4yVwh5T+ALDvLj9Pp5NTaIdhoMvl4my3r/KGbZ3PZ1qWxbuw6ion89kpzTO3suEbC7IaRbbb98Ovv1cab2lDnsCaZVl+nOjaBlFe6qk0nj3Ho6X/PfqyZN/cdliNtZxxFKqmy52NZws4/0IwoXpWKdhStHMFiG2yLT75SsqE2B9XG/h4evYgO1jvJ39FLXLNXMWhxVHarU0hWdmQcdQlhPrfEletuEyxWcQ71M/yhJPb+XP+k9qfNfFsMR7ZXuo5UeN/bV/6fC3ZN7cd1mONjy3HEJdP8/5sU44/uV8u2QJ+u902Zz4+PojIPe2XjnJgS3N067rSPM/O3JYMXsol2TzPd6I/DAMty7IFWp+4crDI6he5Hw8YCwFf15Wu1+uWS+OT2LK23coGjwV5c1VKX8v+4v/LWbpFvGP9zPblmJEzo9PplJTTJaqLp+V4lNuXZaHr9Rr1vdb/0r6M+Vqyb247rMeaFmk50eRtevLgSjdxW1KoqkKJXR5KR2vFh4/vynHJf8cuH7Wv67pug1BfCukFBtkO/lGR/ozjiEoYig8+LZyMZbxj/ewSDbm9FzXjUZ78epLjmr23ILUvc3zt0c7WY0366JsMuK48mi9iMjIAOemTGm632zawXTnMlEueHF907kzvyyebLwcG3Pgu7y3jbVmp1JKa8ejL70vhaC3gqX2Z42uPdrYea/pHWPooNYy/V9pPxQIuBVrDq7rsrCWy5lteusv8plU6g48nbWtk+3LypsAN4h2G48OTFR0PGb9Hx6fG1x7tbDnWfIKshf3r62ubAJfc9p8s4PohUvJvmUti5Hadd7SaFXAOVua82GavdIbuZNAWxPubI1351fj6qHa2GGvrutI0TbQsy12OnOg7Z9+kjNAl0nKbD520b4Er5wTAM5OSmtxLGqnG1yO1MxVebNV5dpkaJkqraksWcLnSHMofhbbV5HpS/Ou9AEV0/8t8tIF0RBDv/1Nb2dWTGl8f2c6asea6Q1nDQk70fWOPq3IlRLKAy/IcLq/hMhgJp0x4u5x1t+4Ea/HW+Sq9kiwXcvn7oBzEO8yjJikl1Pjao509xlpokVQjq+ykDzHNzFrElHWY7Jg2oJ22EG25KOrLoctLD6vKF70SfT6f70rPdHrIZ9M1EGWbYvSoKOhVtRDCKt57oEU8ZXxC5XMWz0ap9b/GV8t2+tphOdZ4kVXa0Hokt43jGNTOHIpupWeHXY3i7ZYnmMwNuWzLhQAim7pzeSxd6cK29fPJXXWejBbr0JoC0f2g1O2z+mHsYSOXmng/mh7xlPGRN8oxfJ7M85x8I08r/2t8rdk3tR1WY01mHLRNmXom+r5ZjO3IK4GSeBcLuEugLZ79HUM3kn1ipmkyXSzlSxvuJA5+ik3dOVz3mfpL63p8AH+ee3I+0kYONfHeA63j6YsP0f154EoL9Pa/xtfadqa0w3Ks+c5v12STv+9Dp55DFAl4LD1ilcJg5G2orudZsL1QmWLIH+mv63syP6UvjXx3ovF+8upB2tPPEPH5patrSuIaa3utjVj8UvyQlMY7xb6ln759U+LZajzGzoMe/lv5WrNvajtqxpq0JdMxof154it1TB8np+/e3t/f/yR98z94lu0TcIv8W4p9TWzGzy85DT35LNQul+3UqwzffvLzmF+S3Pr21LbX2EiJX8yPmF8p8a7t55R25Prt8qfFeCSyufK18D/lmKXnT+q+OeM6d6yN40hfX19E9D1Lzz2HdMaCKF83swUcAABeHSngn5+fD7vj1eSdmAAAAPoDAQcAgIMCAQcAgIMCAQcAgIMCAQcAgAL2cCcwBBwAADKRVSePfOY6yggBAOCgvP39B/od4p9fvxAgAB7EX79/vz3ahz2DFAoAABwUCDgAABwUCDgAABwUCPhOaP0QsBb08Hnvcdm7f6141XbvDQh4Q1IHOb8Pj4iy3kj9SHr4vPe47N2/Vrxqu/cIBNyYcRzvngvMyGcY+14JR0S7fVGBi5DP/LhRoro62UfFJdX/I/abBa/a7r0BATeEX5cUeuMOvwj6mS89+X2f/D7DPb7+LMTR/QevAwTcCC3erlcpyT+h92cehR4+HzEurwD6ZR9AwA3gGZt8756cZfObN3xv39nLbbk59PD5iHF5BdAv+wECboDrXXhyhr2uK63rGhzsRzwRevh8xLi8AuiXfQABN8KXOkklVLHi25ZbyuX6fk05mO948gdNL+jm2ukRF72vhf8lPliW5vn6JnRs3ifFh979AtxAwI0JLWD6CJVl6W1sw/WW+9DLhF37SH9zy8FcPvNnWgAvl8tmL8dO67j47JX47xP8mA86/Vbit68d7K/2Sy+iy+9LH5Zlcba1d78APxBwY/iEzxXEUFkWb5Mi4bLrqm5JqYzx2S3xWQsB+yavUPYQl5g9fYyY/9qXFB+GYaDL5eK1WVNjLY+p/ZeL6LLiRsM/WqH29uoX4AYCbgDPKHjg8ozKugztdDptthhpU89q9OxOpndcteq1LMtC0zRtbWekvy2qF3Lj4qPG/5K+keKt9+PPa8eObIe8F0GjhViO4VIfrPoF+IGAG7CuK83z7Myd8iC2uGyc5/nuB2EYBlqWhS6Xy2ZTzpakEPC+t9tty/P6Zl6lrOtK1+t1y3XySdp6ppUblxb+1/YN25C2WTyv12t+UP5Djj3+v15gn6Zp+zcR3flQ8yNv1S/ADwTcCB6Irhyq/HfNZbG+fF/XdTtB9YyaRZr3k3a5KsZ6Bv4ocuKyBx9038gfCD0ZqJ2psoiG9tfb2Hei7/FbYn8P/fLsQMANud1u2+DUQk50P6MpEfGc9INv0bL0eHtmD+0o7RseL67jhW78yvErp3ImlLcusQ3aAgE3RtZ8y+oPubBzPp+7XDpKkUCucV9w3/CPuuuuXfn/VuNFVyhZCjhoDwS8Ibfbbcs5E92vzo/jiPwfIKI2C8opyAol1wInRHz/QMA7oPOaODEAk3LjV4tUhBbvaZrurtQ+Pj62xUawXyDgnZCLNwAwehGzF/rGHn01iPz1MYCAd6S3eMsfjNht1KAfe/gxl+sj4LhAwA3gG2aIyFuy5buhphVSJHw3kvQQkNoqikfTwn8up4uVCbZ8doguE9QzcFwpHgMIuAG6bNC1GKTv7GstaLKWl4ju8p1E9TdpxGwzuu1HqIjp4b9cE9F9Q/TdP/M8V93I40Pbkp/pNoP9AgE3Rp/sRPezmWmaur2GikWCxYAfytRjduV6tAB/3kKQrGntP894WbzlA7N0CWGL9Jd8/EPIPtg3EHAD+GTU9d46ZRK6nT6UUolt4+36e/I2adet/UTuhzelEvNLV96UpI1axCXVbor/NT7Iu3ddKbaaxy/E2sxjY1mWH1eP8imCJcdv2S/gnre///x5tA+75p9fv7IC5Mstxy69+SW6vsd3+rZJmyEb8iW97M/5fN5KxT4/P7Pr0lP9kljasIhLiBT/LXxw2alN1cT8cn1X2ib6FvDc2Fv2y1+/f79F3H9pIOARcgX8SIzjSF9fX0RUJuAAtAYCHgYplBcGuU4Ajg0E/ImRl8b6clU/EQ8AcDwg4E+MrECRz2Ym+vnSAIg4AMcDAv7E6OdK88KRrpDBm1EAOCYQ8CeGH6JF9PNFE0Q/X/QAADgWqEKJ8AxVKJzv1nXgR7grErw2qEIJAwGP8AwCDsBRgYCH+RdHEwkWLXE/8gAAAABJRU5ErkJggg== + + + iVBORw0KGgoAAAANSUhEUgAAAaAAAAAkCAIAAABAAnl0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAIBklEQVR4nO1d65GrPAz1nfkKSAu4FWglqeXWQFohrZhW7o8zaBS/kB+QhE/nx86uA5YsnT1+CHb/3G43o1AoFFfEf592QKFQKI6CCpxCobgsVOAUCsVloQL3SxiGwRizruuht5TiBBM/hJ+Lxs85XAQVuJ/BOI7TNBljlmV5vV4H3XKCVxfGz0Xj5xwuRaXAjeOIb7ygoN05J58QqCuOoh7+PyAu8sZULjK3lOIEE5fBB6ORT1MK105fjcANwzBNk7XWU/1xHB+PhzFmnmehPOEWa63X7pxblsU5d8lZpSOQi2maEK4jZoUTTCjaoWmKokbgrLWQJOdc2I74VnRLd6Gfx+OBWUU1jlAXWMWZ0Bx9FeoFzhOyYRhI9Spmj2VZaJGM/jEdoU/VOMOCoNH4WmiOvg3FApcSstSyTg5ODpwmQOCmaVK6ABqH74fm6KsgFTjUkg0TMt5I7VC39sIzWGI3jOMY5Q05kLIVeiL0TXJZx4c2hO3hj5QOnpeObh9qopon8rTmTVTctbtT2R1U46hTDlekqdHDipHWpanuSkAkcFRLNix8tH+kdnx9PB6QucbC8+v1ggkgLNeSS8YY51xYkeAlcPOeeBQxUkShnqPdUo0l31WIYRhQhHHOPZ/PqJ/c1v1+hxUUbbzL8COndSbyIfUlbh9kojqAYbhCi8iXZyLqMPXj1cRS6aBoUAs8D6+JmpNQa3fI0XuL0pRC9/T1SlOR0RAigeOJ4Y3cM6+9y1Hrsiywa60dhoGvXHBCxy+GRS86dDt95X56zIYAhd1aa0mPotfYoKCcAepcfDh8LNQJL1XzKw2r6Idu0OIiE4dMBKLe9jXRHkCTTatzjv+2cxPmnR4IO/LrBSF8ciJa7o8u5aJPXUiolYKE7fI0pXBE+rqkqdSoB5HAQS+5017+SNRI17o84YEOU0rKfaDTuujGAb55Q4DQcNGkINJAaP0IPeLX8N7orrxY0KfoEz9623wSPs7RVHDmeeZzD7kU3iKJwAkmGgMosUhx82pWQB0n4TZ1S9wouj1Prd1OMmwvSlMKjemLClOmZ3maGjkjErjX6wWT9/vdBCtzSj8C3fcBHAicMYZPs+u6zvNsNsmARnhrXQ6UaBGLYRicc9gq8lDyiNPo6MAFhiisy7JgpGQabNudjbEm5Vnk8s2Fj9QtxdF1XZ/PJzlALlVH4AQT7QGUWAQ/6TeTFl9yNfHAucEjIDwPklArg122F6Upher04UqbLgY2pqmRM2VVVJtY91L+Tnu8kBvCJGbeBYKDO4yLvYUh7Qc97V7X1TvRN9u6Bu3rui7Lgq52F0TkTChq9Cn4RLZMp+eqdiNwgoleAdy16JGe2ruM0SPe7i0Sau2iiO11KEofpQnpozk7WgxsSVM7ZwoEjozt7k9PA7mUp++uY5TIzCj4NZ45osIu4XgWMU3xkIIomKDIVhcGn5CaoiBXB7DIYkfAQywZipIioZYQQrbXofp3BJqVmSxbRt3OmQKBIyHjjZigzGFsiy5kxnGkkfdKOfWQCZZl5WPyx1uO5U95vOnXMCHzevPar4H2AH4EfDnz9+9fzEbyN6Ik1MrgCLbXITUQnsru6WvkzI7ARYPr1XSonW+tu6w7UupGR358Mjkz65n5aheOHVHTnIFGbyb8yKL4HLQE8HzgFMxthT9+AN/4LNQuvoHtuzjHqzrO7AhcWJ82QU3aM790/bME3qTB8w3Oof1+v0NeT0BGdyR6RMsBCqPX4eUFrjGAHwEO8vl5EzJo09XDdnwD278E1ZzZETi31Xf5ZjhcKPLGXsdGtPk1QS3ZGDPPM2fVyccxjcXicKWG3kLhO61ocybaA/gpQObM9hTrdPCrhN/AdgmiO62+qObMjsBR+RkzCSq19Cm2pWgnDepFXL4k9NbA3ePID1lSTxLw+kAL+DEc9ex9E/3x19ErgN8AyA1NRZmnTyTUSuGrzmFTA+le8Y8are5hv8hAD56YYE3Bl299J2T+4Dg/0eMn9HxOa/y14ZWgaZqiT9bQNdHKdNErcjxt3uKXtxcNAc4fuixqNNExgB3hMYfvGzjGcYySfxcSaqVQx/aDmJAaSHQh0t1oNWekr2rZ2IMgaO8yMGvt/X53rPRLi3PeOX3PS7c29iZZKfiTNaEhLI/pGjoZoWsQByF9vRFx+Y4KXwb87YiD9rYdTfQKYBd4KabvQy5hunVbIaj0JEFCLYmHebafwIToQGghctCJSiNnCgRu6foH4EIT3rmpe3/QmTdibMS5Lrue1+tlt2Njr2fafWMWRaBD6/I9CM1L5l3saPdqSqK6bG/s0pl3d6XoZaJXALuAS9W0vZQavdJuTyqEH/HDmRQk1Ep5WMT2o5kQpo+cmee5b3UxY9SUcEYqcKl9qHChkULqdrf9yXLPolewt+w1t2jiU53TbMzbn8+ne38HGFdykaXDF+KQ21D0cAzmpdCHlG/54dAsZ4MHFYsikEJHE10CWDGosJHrDh+mCbQMVJzeX0dP+Ry1LqFWiAq2Z9KUQnv6woVIRc+ZW1o48+d2u2U+BrBYC+Wmy7kJP6QEJIsX/q9quKhH/wlOWORKjSj0J/XHW/g18tWW51vKAZOIan44UZ8rIhBFXxONASy1KPTEbe9LRrlk3nctpjBHRkatKIRsrzPRmL5M7jqmKRyakDMigVMoFIpfhP5fVIVCcVmowCkUistCBU6hUFwWKnAKheKyUIFTKBSXhQqcQqG4LFTgFArFZfEPuuTdBr3uWzgAAAAASUVORK5CYII= + + + +