From ab054084193f210d01c6f1a5a0dd73d600fbfd62 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Oct 2018 15:24:20 +0100 Subject: [PATCH 01/61] 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 12b4bb48727..7a7560820dc 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 02/61] Update RELEASENOTES.md Fix markdown --- RELEASENOTES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7a7560820dc..dd7c9c95a95 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 03/61] 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 0915ee4b030..d0cde5f6937 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 interface RatingCallback extends CommandReceiver { /** 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 void onSetRating(RatingCompat rating) { 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 04/61] 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 f5a347b3517..4a75b6f722b 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 @@ private static Point getMaxVideoSizeInViewport(boolean orientationMayChange, int } /** 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 05/61] 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 dd39ea2822b..829b53f863f 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.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 a1fbcc69d61..8f5dcef16b7 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.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 224f5aa6eef..95a3a588b40 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.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 ImaAdsLoader buildForAdsResponse(String adsResponse) { 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 c09d2fe55a4..e3081cd2d2e 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.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 @@ */ 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 0cbdc14b1ce..6c72dd8d0a1 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.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 @@ private 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 @@ private 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 @@ private 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 @@ private 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 @@ private 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 @@ private 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 @@ private C() {} * #USAGE_UNKNOWN}, {@link #USAGE_VOICE_COMMUNICATION} or {@link * #USAGE_VOICE_COMMUNICATION_SIGNALLING}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ USAGE_ALARM, @@ -427,6 +435,7 @@ private 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 @@ private 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 @@ private 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 @@ private 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 @@ private 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 @@ private 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 @@ private 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 @@ private 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 @@ private 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 @@ private 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 4e69bc316e2..cc16c43b057 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.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 d591876a51e..6b84245141a 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 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 d4b965dbd6d..83014c6b104 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.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 void onTimelineChanged(Timeline timeline, @Nullable Object manifest) { * 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 void onTimelineChanged(Timeline timeline, @Nullable Object manifest) { * {@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 void onTimelineChanged(Timeline timeline, @Nullable Object manifest) { * 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 d1e1541cdc8..c6456e5f7f2 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 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 3e00bcc9023..230b96d01f0 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.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 static final class SyncFrameInfo { * 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 5fb571d1950..ca4e0c299ef 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.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 interface PlayerControl { * 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 interface PlayerControl { 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 47120e73750..569260efeb6 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.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 @@ /* 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 00950012991..62b120f00a5 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 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 @@ void onSystemTimeUsMismatch( } /** {@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 fbd5b027c1a..4ce34ad41a1 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.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 long getSkippedOutputFrameCount() { 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 7c4eacecfb5..a1ff7028c1d 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 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 a527a58be41..cecb17d96c6 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.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 @@ */ 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 7a32ef128b2..983c96f89d1 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 @@ 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 1c49011ee41..6062a6652a4 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.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 @@ private MissingSchemeDataException(UUID uuid) { * 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 bed3545d784..f2fbe94895a 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 @@ 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 DrmSessionException(Throwable cause) { * 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 5bea83d0207..7f4a0f5f035 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 435fb13648c..3b0b8344279 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.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 static final class TimestampSearchResult { 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 26d0788b339..05f5d98d3c4 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 @@ 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 dfdce024500..b93969acfe7 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.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 604a520526c..4211cab4894 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.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 c0494e1ee04..0987bc473f0 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.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 @@ */ /* 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 067c88b552c..cc17af56321 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 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 @@ * 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 35b69690840..63fee487718 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.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 26a8bcce75c..92cf590a495 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.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 d8adcde28c0..0f1fd8f6495 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.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 7cf61b4ff39..17c82c2c5b1 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.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 867e037f4b1..59cd6022092 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.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 4141f833709..2ef9704a7ab 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.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 @@ */ 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 0c2a0545dc3..04a6b571bd0 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.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 06a60776c2a..94fd7ceb138 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.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 978b1e8813a..f47a481d7e0 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.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 7e933e94741..86bbb330b7f 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.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 @@ private static String buildCustomDiagnosticInfo(int errorCode) { * The possible return values for {@link #canKeepCodec(MediaCodec, MediaCodecInfo, Format, * Format)}. */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ KEEP_CODEC_RESULT_NO, @@ -199,9 +201,13 @@ private static String buildCustomDiagnosticInfo(int errorCode) { */ 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 @@ private static String buildCustomDiagnosticInfo(int errorCode) { */ 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 @@ private static String buildCustomDiagnosticInfo(int errorCode) { */ 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 3b26741897d..409f79f30b5 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 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 static final class TaskState { * -> failed * */ + @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_QUEUED, STATE_STARTED, STATE_COMPLETED, STATE_CANCELED, STATE_FAILED}) public @interface State {} @@ -621,6 +623,7 @@ private static final class Task implements Runnable { * +-----------+------+-------+---------+-----------+-----------+--------+--------+------+ * */ + @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 0ee8d76bc75..4d6dbd83be2 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.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 88e98e811f4..5f80725805b 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.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 static final class IllegalClippingException extends IOException { * 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 746af5719e8..ecb4b10c6ac 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.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 72fc162bc34..41adb78906e 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.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 @@ private static long[] copyDurationsUsWithSpaceForAdCount(long[] durationsUs, int * #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 3d5c41e8bc7..7fc0f22bf39 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.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 static final class AdLoadException extends IOException { * 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 87dcb97a810..e7bb0e16bfb 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.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 e1305acd148..a5c666c44a0 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.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 5b74bd15056..16f82a7293b 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.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 90f93d5b21d..a4f0cca955c 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.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 @@ 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 0e46fa0d2ff..fe274a62419 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.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 c2fda677286..59a4f96fb00 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.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 static final class MappedTrackInfo { * {@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 c9689218228..4a4cc021f40 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.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 daf5d3281a2..0be7b857dff 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 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 @@ protected abstract HttpDataSource createDataSourceInternal(RequestProperties */ 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 03219380c79..ac3b3c5c5e3 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.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 interface ReleaseCallback { } /** 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 a91e3246cca..fa2068b99df 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.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 90e37de8281..deb981f0e82 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.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 interface TextureImageListener { * 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 6a1e686dece..34fb684d252 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 @@ 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 e70f5767547..e45ab0952ea 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.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 de92e1ad93b..5816e406237 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 @@ 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 0a9d04bf0fd..3d4879d50a6 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 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 @@ 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 a5014352626..5c9a933508e 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.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 @@ private static ChunkSampleStream[] newSampleStreamArray(int len 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 a29808933bb..81d4e7a8180 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 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 int compareTo(@NonNull Long relativeStartTimeUs) { * 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 9d977d63b3f..0d4c6a40382 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.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 @@ void onAspectRatioUpdated( * #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 778f81375e3..47025d9bba1 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.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 void onBitmap(final Bitmap bitmap) { * 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 void onBitmap(final Bitmap bitmap) { * 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 8127fd617a4..e429f5bfa00 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.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 06/61] 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 dd7c9c95a95..a74af7fb59b 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 e0b7da85063..7418e844491 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 @@ 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 int getPeriodCount() { } /** - * Timeline used as placeholder for an unprepared media source. After preparation, a copy of the - * DeferredTimeline is used to keep the originally assigned first period ID. + * Timeline used as placeholder for an unprepared media source. After preparation, a + * DeferredTimeline is used to keep the originally assigned dummy period ID. */ private static final class DeferredTimeline extends ForwardingTimeline { private static final Object DUMMY_ID = new Object(); - private static final DummyTimeline dummyTimeline = new DummyTimeline(); + private static final DummyTimeline DUMMY_TIMELINE = new DummyTimeline(); private final Object replacedId; + /** + * Returns an instance with a real timeline, replacing the provided period ID with the already + * assigned dummy period ID. + * + * @param timeline The real timeline. + * @param firstPeriodUid The period UID in the timeline which will be replaced by the already + * assigned dummy period UID. + */ + public static DeferredTimeline createWithRealTimeline( + Timeline timeline, Object firstPeriodUid) { + return new DeferredTimeline(timeline, firstPeriodUid); + } + + /** Creates deferred timeline exposing a {@link DummyTimeline}. */ public DeferredTimeline() { - this(dummyTimeline, DUMMY_ID); + this(DUMMY_TIMELINE, DUMMY_ID); } private DeferredTimeline(Timeline timeline, Object replacedId) { @@ -916,14 +965,16 @@ private DeferredTimeline(Timeline timeline, Object replacedId) { this.replacedId = replacedId; } - public DeferredTimeline cloneWithNewTimeline(Timeline timeline) { - return new DeferredTimeline( - timeline, - replacedId == DUMMY_ID && timeline.getPeriodCount() > 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 5f84731b8d8..26c25a749eb 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 void setPrepareErrorListener(PrepareErrorListener listener) { 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 void setDefaultPreparePositionUs(long defaultPreparePositionUs) { 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 TrackGroupArray getTrackGroups() { @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 8414be15882..407e9a38271 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.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 void testRestartAfterEmptyTimelineWithShuffleModeEnabledUsesCorrectFirstP () -> concatenatingMediaSource.addMediaSources( Arrays.asList(mediaSource, mediaSource))) - .waitForTimelineChanged(null) + .waitForTimelineChanged() .executeRunnable( new PlayerRunnable() { @Override @@ -2192,14 +2193,14 @@ public void testClippedLoopedPeriodsArePlayedFully() throws Exception { 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 void removingLoopingLastPeriodFromPlaylistDoesNotThrow() throws Exception .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 e0feae6f49a..3649685f3e6 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 void testDynamicTimelineChange() throws Exception { () -> 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 6e37d7d0705..54d97fb9052 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 Builder sendMessage( 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 Builder sendMessage( * 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 07/61] 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 fda5f0085df..54701eea1db 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 ff54c1b3c0e..69520a16e58 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 59bf5f8f870..eba20352f45 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 08/61] 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 a74af7fb59b..eb405260df5 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 74266710410..50a923bced1 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 void setBottomPaddingFraction(float bottomPaddingFraction) { @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 09/61] 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 d8c61f5e050..4f22362de66 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 @@ private void setupTextLayout() { 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 10/61] 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 5598e063a6c..182f1cf4b0b 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.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 @@ protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { } // 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 @@ protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { 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 @@ private static long parseTimecode(Matcher matcher, int groupOffset) { 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 00000000000..af196f8a042 --- /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 e9abaca0752..554184da5d4 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 @@ 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 void testDecodeNoEndTimecodes() throws IOException { .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 11/61] 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 eb405260df5..e5a04470a25 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 182f1cf4b0b..3b039061b08 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.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 @@ */ 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 @@ protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { 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 @@ protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { } 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 @@ protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { } /** - * 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 @@ private static long parseTimecode(Matcher matcher, int groupOffset) { 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 af196f8a042..85e304b4985 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 554184da5d4..1430c70e09f 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 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 void testDecodeNoEndTimecodes() throws IOException { } @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 @@ private static void assertTypicalCue3(SubripSubtitle subtitle, int eventIndex) { .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 12/61] 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 e5a04470a25..1a424e502e2 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 027f316493d..7912878e202 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 @@ private void handlePlaybackInfo( 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 d861020d26a..83b1243f426 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 @@ private void seekToCurrentPosition(boolean sendDiscontinuity) throws ExoPlayback 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 @@ private void updatePlaybackPositions() throws ExoPlaybackException { // 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 @@ private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackExcepti 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 @@ private void reselectTracksInternal() throws ExoPlaybackException { 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 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo) 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 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo) 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 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo) 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 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo) 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 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo) } // 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 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo) // 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 @@ private void updatePeriods() throws ExoPlaybackException, IOException { 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 @@ private void handleLoadingMediaPeriodChanged(boolean loadingTrackSelectionChange 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 02058c04842..8c73fde3be0 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 @@ 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 @@ public static PlaybackInfo createDummy( 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 @@ public PlaybackInfo( 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 @@ public PlaybackInfo fromNewPosition( 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 @@ public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) { 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 @@ public PlaybackInfo copyWithPlaybackState(int playbackState) { 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 @@ public PlaybackInfo copyWithIsLoading(boolean isLoading) { 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 @@ public PlaybackInfo copyWithTrackInfo( 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 407e9a38271..d131ed0f51e 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 void run(SimpleExoPlayer player) { 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 13/61] 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 a993c79b4a2..9fac69b2816 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 void seekToUs(long positionUs) { // 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 boolean continueLoading(long positionUs) { 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 37c9e313aee..1ea25ecc36b 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 @@ protected Chunk newMediaChunk( 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 14/61] 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 1a424e502e2..a5af7506e42 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 2edf7bb8c61..c51c1cc1492 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 @@ private MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) { // 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 @@ private MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) { 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 @@ private MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) { 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 15/61] 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 63fee487718..86b750e821f 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 int read(ExtractorInput input, PositionHolder seekPosition) throws IOExce 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 int getElementType(int id) { 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 @@ private static final class Track { 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 void initializeOutput(ExtractorOutput output, int trackId) throws ParserE 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 16/61] 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 153856af8ce..31ff7d283ea 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 @@ protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, : (period.startMs + periodDurationMs); periods.add(period); } + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "MPD")); @@ -221,6 +223,8 @@ protected Pair parsePeriod(XmlPullParser xpp, String baseUrl, long 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 @@ protected Pair parseContentProtection(XmlPullParser xpp) } 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 @@ protected int parseRole(XmlPullParser xpp) throws XmlPullParserException, IOExce */ protected void parseAdaptationSetChild(XmlPullParser xpp) throws XmlPullParserException, IOException { - // pass + maybeSkipTag(xpp); } // Representation parsing. @@ -526,6 +534,8 @@ protected RepresentationInfo parseRepresentation( 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 @@ protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, SingleSegmentBas xpp.next(); if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { initialization = parseInitialization(xpp); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase")); @@ -701,6 +713,8 @@ protected SegmentList parseSegmentList(XmlPullParser xpp, SegmentList parent) segments = new ArrayList<>(); } segments.add(parseSegmentUrl(xpp)); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList")); @@ -747,6 +761,8 @@ protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, SegmentTemplat initialization = parseInitialization(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { timeline = parseSegmentTimeline(xpp); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTemplate")); @@ -794,6 +810,8 @@ protected EventStream parseEventStream(XmlPullParser xpp) EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale, scratchOutputStream); eventMessages.add(event); + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "EventStream")); @@ -932,6 +950,8 @@ protected List parseSegmentTimeline(XmlPullParser xpp) segmentTimeline.add(buildSegmentTimelineElement(elapsedTime, duration)); elapsedTime += duration; } + } else { + maybeSkipTag(xpp); } } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTimeline")); return segmentTimeline; @@ -995,6 +1015,29 @@ protected int parseAudioChannelConfiguration(XmlPullParser xpp) // 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 17/61] 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 06ca83dd93f..71e2d8d19fa 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 synchronized void release(Allocation[] allocations) { 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 18/61] 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 a5af7506e42..cd8309d9022 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 0742d96a06d..a0effc0df88 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 @@ 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 @@ */ 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 00000000000..ff607b9482e --- /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 92cf590a495..84f620734b8 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.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 int read(ExtractorInput input, PositionHolder seekPosition) } } 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 @@ private boolean synchronize(ExtractorInput input, boolean sniffing) 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 @@ private static int getSeekFrameHeader(ParsableByteArray frame, int xingBase) { 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 0bf9d3b2493..63bf30dd116 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.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 @@ private static boolean validateFrames(ParsableByteArray id3Data, int majorVersio } 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 @@ private static ChapterTocFrame decodeChapterTOCFrame( 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 00000000000..06a4dd9d2d8 --- /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 00000000000..3e6520becaf --- /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 19/61] 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 acb2c59e0ce..6504562c58a 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.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 int read(byte[] buffer, int offset, int readLength) throws IOException { @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 fa2068b99df..3a96544c54d 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.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 Map> getResponseHeaders() { // 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 20/61] 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 bee73cac128..30fe10085f4 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 21/61] Update release notes --- RELEASENOTES.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cd8309d9022..65c9b6fb3ad 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 +* 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)). +* 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 have a wrong position if SubtitleView has a non-zero - offset to its parent +* 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)). -* 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 +* 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 22/61] 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 65c9b6fb3ad..cd8caa0c8ab 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 6e0fba27aed..e9f70ec92a5 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 Builder setSlidingWindowMaxWeight(int slidingWindowMaxWeight) { * @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 Builder setClock(Clock clock) { * @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 DefaultBandwidthMeter build() { } 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 DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, } } - 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 synchronized long getBitrateEstimate() { } @Override - public @Nullable TransferListener getTransferListener() { + @Nullable + public TransferListener getTransferListener() { return this; } @@ -237,4 +340,249 @@ public synchronized void onTransferEnd(DataSource source, DataSpec dataSpec, boo 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 00000000000..ebdb45909ba --- /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 23/61] 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 7912878e202..04bc1c611db 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 @@ private PlaybackInfo getResetPlaybackInfo( 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 83b1243f426..fbf1536f573 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 @@ private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackExcepti 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 @@ private void releaseInternal() { } } - 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 @@ private void resetInternal( 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 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo) 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 8c73fde3be0..4333f51bf77 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 @@ * 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 @@ public PlaybackInfo( 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 24/61] 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 d458df55bb7..4165a425688 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 25/61] 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 cd8caa0c8ab..ba1734bc309 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 7418e844491..1b614e18a99 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 final void releasePeriod(MediaPeriod mediaPeriod) { Assertions.checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod)); ((DeferredMediaPeriod) mediaPeriod).releasePeriod(); holder.activeMediaPeriods.remove(mediaPeriod); - if (holder.activeMediaPeriods.isEmpty() && holder.isRemoved) { - releaseChildSource(holder); - } + maybeReleaseChildSource(holder); } @Override @@ -747,9 +745,7 @@ private void removeMediaSourceInternal(int index) { -oldTimeline.getWindowCount(), -oldTimeline.getPeriodCount()); holder.isRemoved = true; - if (holder.activeMediaPeriods.isEmpty()) { - releaseChildSource(holder); - } + maybeReleaseChildSource(holder); } private void moveMediaSourceInternal(int currentIndex, int newIndex) { @@ -778,6 +774,16 @@ private void correctOffsets( } } + private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) { + // Release if the source has been removed from the playlist, but only if it has been previously + // prepared and only if we are not waiting for an existing media period to be released. + if (mediaSourceHolder.isRemoved + && mediaSourceHolder.hasStartedPreparing + && mediaSourceHolder.activeMediaPeriods.isEmpty()) { + releaseChildSource(mediaSourceHolder); + } + } + /** Return uid of media source holder from period uid of concatenated source. */ private static Object getMediaSourceHolderUid(Object periodUid) { return ConcatenatedTimeline.getChildTimelineUidFromConcatenatedUid(periodUid); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java index d3d3b39ea42..dd1221f1605 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ConcatenatingMediaSourceTest.java @@ -925,6 +925,22 @@ public void testChildSourceWithLazyPreparationOnlyPreparesSourceOnce() throws IO testRunner.createPeriod(mediaPeriodId); } + @Test + public void testRemoveUnpreparedChildSourceWithLazyPreparation() throws IOException { + FakeMediaSource[] childSources = createMediaSources(/* count= */ 2); + mediaSource = + new ConcatenatingMediaSource( + /* isAtomic= */ false, + /* useLazyPreparation= */ true, + new DefaultShuffleOrder(0), + childSources); + testRunner = new MediaSourceTestRunner(mediaSource, /* allocator= */ null); + testRunner.prepareSource(); + + // Check that removal doesn't throw even though the child sources are unprepared. + mediaSource.removeMediaSource(0); + } + @Test public void testSetShuffleOrderBeforePreparation() throws Exception { mediaSource.setShuffleOrder(new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 0)); From 5ae60a6f18d564e7b174f4ec593f85f42eb97298 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 23 Oct 2018 05:41:48 -0700 Subject: [PATCH 26/61] 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 13dfaddab3a..b69be03ae4c 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 ba1734bc309..051a8451e97 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 a013f4fb840..96eade1aa3d 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 27/61] 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 26c02d8ae95..07f278c8084 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 interface Event { /** 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 void removeListener(T eventListener) { */ 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 28/61] 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 051a8451e97..bf734489b9d 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 bffc43a540f..f4007207728 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 @@ public ConstantBitrateSeeker( 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 ff607b9482e..868c1d9fbff 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 @@ private static Pair linearlyInterpolate( } } + @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 84f620734b8..e8848bf9837 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 int read(ExtractorInput input, PositionHolder seekPosition) 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 @@ private boolean synchronize(ExtractorInput input, boolean sniffing) } } 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 @@ private boolean synchronize(ExtractorInput input, boolean sniffing) 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 @@ private static MlltSeeker maybeHandleSeekMetadata(Metadata metadata, long firstF } /** - * {@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 @@ private static MlltSeeker maybeHandleSeekMetadata(Metadata metadata, long firstF */ 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 774505f622c..15e778115d1 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 @@ 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 @@ public long getDurationUs() { 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 c2971e47ce1..42752e55fb4 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 @@ 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 @@ 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 @@ private XingSeeker( 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 @@ public long getDurationUs() { 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 29/61] 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 bf734489b9d..16b65aa09af 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 95a3a588b40..cc621c6218d 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 static final class Builder { private @Nullable AdEventListener adEventListener; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; + private boolean focusSkipButtonWhenAvailable; private ImaFactory imaFactory; /** @@ -104,6 +105,7 @@ public Builder(Context context) { this.context = Assertions.checkNotNull(context); vastLoadTimeoutMs = TIMEOUT_UNSET; mediaLoadTimeoutMs = TIMEOUT_UNSET; + focusSkipButtonWhenAvailable = true; imaFactory = new DefaultImaFactory(); } @@ -159,6 +161,20 @@ public Builder setMediaLoadTimeoutMs(int mediaLoadTimeoutMs) { 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 ImaAdsLoader buildForAdTag(Uri adTagUri) { null, vastLoadTimeoutMs, mediaLoadTimeoutMs, + focusSkipButtonWhenAvailable, adEventListener, imaFactory); } @@ -200,6 +217,7 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { adsResponse, vastLoadTimeoutMs, mediaLoadTimeoutMs, + focusSkipButtonWhenAvailable, adEventListener, imaFactory); } @@ -252,6 +270,7 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { 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 ImaAdsLoader(Context context, Uri adTagUri) { /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* focusSkipButtonWhenAvailable= */ true, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -362,6 +382,7 @@ public ImaAdsLoader(Context context, Uri adTagUri, ImaSdkSettings imaSdkSettings /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* focusSkipButtonWhenAvailable= */ true, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -373,6 +394,7 @@ private 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 @@ private 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 @@ private void startAdPlayback() { 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 30/61] 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 7104630a23e..0cda3fafa8a 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 @@ public static TrackSampleTable parseStbl( 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 31/61] 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 b69be03ae4c..2b6a508aaa0 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 32/61] 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 49826902cde..bd2bea0197a 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 @@ private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, Stri String name = parseStringAttr(line, REGEX_NAME, variableDefinitions); String language = parseOptionalStringAttr(line, REGEX_LANGUAGE, variableDefinitions); String groupId = parseOptionalStringAttr(line, REGEX_GROUP_ID, variableDefinitions); + String id = String.format("%s:%s", groupId, name); Format format; switch (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) { case TYPE_AUDIO: @@ -348,7 +349,7 @@ private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, Stri String sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null; format = Format.createAudioContainerFormat( - /* id= */ name, + /* id= */ id, /* label= */ name, /* containerMimeType= */ MimeTypes.APPLICATION_M3U8, sampleMimeType, @@ -368,7 +369,7 @@ private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, Stri case TYPE_SUBTITLES: format = Format.createTextContainerFormat( - /* id= */ name, + /* id= */ id, /* label= */ name, /* containerMimeType= */ MimeTypes.APPLICATION_M3U8, /* sampleMimeType= */ MimeTypes.TEXT_VTT, @@ -394,7 +395,7 @@ private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, Stri } muxedCaptionFormats.add( Format.createTextContainerFormat( - /* id= */ name, + /* id= */ id, /* label= */ name, /* containerMimeType= */ null, /* sampleMimeType= */ mimeType, diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java index d818111eec7..d03049efb33 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java @@ -75,7 +75,7 @@ public class HlsMasterPlaylistParserTest { private static final String PLAYLIST_WITH_CC = " #EXTM3U \n" - + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS," + + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID=\"cc1\"," + "LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n" + "#EXT-X-STREAM-INF:BANDWIDTH=1280000," + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" @@ -90,6 +90,14 @@ public class HlsMasterPlaylistParserTest { + "CLOSED-CAPTIONS=NONE\n" + "http://example.com/low.m3u8\n"; + private static final String PLAYLIST_WITH_SUBTITLES = + " #EXTM3U \n" + + "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\"," + + "LANGUAGE=\"es\",NAME=\"Eng\"\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=1280000," + + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" + + "http://example.com/low.m3u8\n"; + private static final String PLAYLIST_WITH_AUDIO_MEDIA_TAG = "#EXTM3U\n" + "#EXT-X-STREAM-INF:BANDWIDTH=2227464,CODECS=\"avc1.640020,mp4a.40.2\",AUDIO=\"aud1\"\n" @@ -216,6 +224,33 @@ public void testCodecPropagation() throws IOException { assertThat(secondAudioFormat.sampleMimeType).isEqualTo(MimeTypes.AUDIO_AC3); } + @Test + public void testAudioIdPropagation() throws IOException { + HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG); + + Format firstAudioFormat = playlist.audios.get(0).format; + assertThat(firstAudioFormat.id).isEqualTo("aud1:English"); + + Format secondAudioFormat = playlist.audios.get(1).format; + assertThat(secondAudioFormat.id).isEqualTo("aud2:English"); + } + + @Test + public void testCCIdPropagation() throws IOException { + HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC); + + Format firstTextFormat = playlist.muxedCaptionFormats.get(0); + assertThat(firstTextFormat.id).isEqualTo("cc1:Eng"); + } + + @Test + public void testSubtitleIdPropagation() throws IOException { + HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_SUBTITLES); + + Format firstTextFormat = playlist.subtitles.get(0).format; + assertThat(firstTextFormat.id).isEqualTo("sub1:Eng"); + } + @Test public void testIndependentSegments() throws IOException { HlsMasterPlaylist playlistWithIndependentSegments = From e4989d1743fa825dd0d57dcc915b325b47d3fc3b Mon Sep 17 00:00:00 2001 From: ojw28 Date: Wed, 31 Oct 2018 19:17:25 +0000 Subject: [PATCH 33/61] 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 4d6dbd83be2..5acd31ee0de 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 @@ private boolean checkIdleRequirement(Context context) { } 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 34/61] 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 9525983491b..9f381d0053c 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 long open(DataSpec dataSpec) throws HttpDataSourceException { // 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 synchronized void onRedirectReceived( // 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 2707f539bc2..778277fdbc0 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 long open(DataSpec dataSpec) throws HttpDataSourceException { 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 3673af5540a..c6749e6c8f1 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 long open(DataSpec dataSpec) throws HttpDataSourceException { } 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 long open(DataSpec dataSpec) throws HttpDataSourceException { 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 0be7b857dff..e73901eccb8 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 @@ final class InvalidResponseCodeException extends HttpDataSourceException { */ 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 e1700e3b203..2a35a31e747 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 void getBlacklistDurationMsFor_blacklist404() { @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 void getBlacklistDurationMsFor_blacklist410() { @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 35/61] 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 cc621c6218d..6ca3bfd8817 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.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.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 static final class Builder { 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 Builder(Context context) { this.context = Assertions.checkNotNull(context); vastLoadTimeoutMs = TIMEOUT_UNSET; mediaLoadTimeoutMs = TIMEOUT_UNSET; + mediaBitrate = BITRATE_UNSET; focusSkipButtonWhenAvailable = true; imaFactory = new DefaultImaFactory(); } @@ -135,6 +141,18 @@ public Builder setAdEventListener(AdEventListener adEventListener) { 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 Builder setAdEventListener(AdEventListener adEventListener) { * @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 Builder setVastLoadTimeoutMs(int vastLoadTimeoutMs) { * @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 ImaAdsLoader buildForAdTag(Uri adTagUri) { null, vastLoadTimeoutMs, mediaLoadTimeoutMs, + mediaBitrate, focusSkipButtonWhenAvailable, + adUiElements, adEventListener, imaFactory); } @@ -217,7 +250,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { adsResponse, vastLoadTimeoutMs, mediaLoadTimeoutMs, + mediaBitrate, focusSkipButtonWhenAvailable, + adUiElements, adEventListener, imaFactory); } @@ -247,6 +282,7 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) { 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 ImaAdsLoader buildForAdsResponse(String adsResponse) { 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 ImaAdsLoader(Context context, Uri adTagUri) { /* 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 ImaAdsLoader(Context context, Uri adTagUri, ImaSdkSettings imaSdkSettings /* adsResponse= */ null, /* vastLoadTimeoutMs= */ TIMEOUT_UNSET, /* mediaLoadTimeoutMs= */ TIMEOUT_UNSET, + /* mediaBitrate= */ BITRATE_UNSET, /* focusSkipButtonWhenAvailable= */ true, + /* adUiElements= */ null, /* adEventListener= */ null, /* imaFactory= */ new DefaultImaFactory()); } @@ -394,7 +436,9 @@ private 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 @@ private 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 @@ private void startAdPlayback() { 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 36/61] 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 1fdb137be97..6446909808b 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 DashManifest(long availabilityStartTimeMs, long durationMs, long minBuffe this.publishTimeMs = publishTimeMs; this.utcTiming = utcTiming; this.location = location; + this.programInformation = programInformation; this.periods = periods == null ? Collections.emptyList() : periods; } @@ -150,7 +156,7 @@ public final DashManifest copy(List streamKeys) { 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 31ff7d283ea..d048e22b33b 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 @@ protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, 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 @@ protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, 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 @@ protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, 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 @@ protected RangedUri buildRangedUri(String urlText, long rangeStart, long rangeLe 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 00000000000..71b4af3a21d --- /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 07bcdd4f50b..ccd3ab4dd6c 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 37/61] 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 9f381d0053c..af854011005 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 synchronized void onRedirectReceived( 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 e73901eccb8..e3e93bd6fbb 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 @@ final class InvalidResponseCodeException extends HttpDataSourceException { */ public final int responseCode; - /** - * The HTTP status message. - */ + /** The http status message. */ @Nullable public final String responseMessage; /** @@ -305,6 +303,13 @@ final class InvalidResponseCodeException extends HttpDataSourceException { */ 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 2a35a31e747..55765888570 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 void getBlacklistDurationMsFor_blacklist404() { @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 6446909808b..3637b80ecb5 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 DashManifest(long availabilityStartTimeMs, long durationMs, long minBuffe 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 final DashManifest copy(List streamKeys) { } } 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 d048e22b33b..f017ae64add 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 @@ protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, 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 @@ protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, 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 @@ protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, 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 @@ protected RangedUri buildRangedUri(String urlText, long rangeStart, long rangeLe 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 71b4af3a21d..73e8ef986e1 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 @@ 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 ProgramInformation(String title, String source, String copyright, String @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 3183e3c6723..a1693f69856 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.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 void testParseMediaPresentationDescriptionCanParseEventStream() throws IO @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 17a96fded3f..0d08df42e9c 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 @@ private static Representation newRepresentation() { 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 bd2bea0197a..65f47961878 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 @@ private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, Stri String name = parseStringAttr(line, REGEX_NAME, variableDefinitions); String language = parseOptionalStringAttr(line, REGEX_LANGUAGE, variableDefinitions); String groupId = parseOptionalStringAttr(line, REGEX_GROUP_ID, variableDefinitions); - String id = String.format("%s:%s", groupId, name); + String id = groupId + ":" + name; Format format; switch (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) { case TYPE_AUDIO: From c9c4bd89c74d10b44fc6562c9212da102dfa2274 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 31 Oct 2018 20:40:57 +0000 Subject: [PATCH 38/61] 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 73e8ef986e1..e3072c86bd2 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 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 39/61] 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 fbf1536f573..7f41719d1db 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 @@ private void updatePlaybackPositions() throws ExoPlaybackException { // 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 @@ private boolean shouldTransitionToReadyState(boolean renderersReadyOrEnded) { } // 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 @@ private void maybeContinueLoading() { return; } long bufferedDurationUs = - nextLoadPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs); + getTotalBufferedDurationUs(/* bufferedPositionInLoadingPeriodUs= */ nextLoadPositionUs); boolean continueLoading = loadControl.shouldContinueLoading( bufferedDurationUs, mediaClock.getPlaybackParameters().speed); @@ -1699,6 +1695,17 @@ private void handleLoadingMediaPeriodChanged(boolean loadingTrackSelectionChange } } + 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 4941b4efc67..5925c8f3830 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 @@ public long getDurationUs() { } /** - * 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 40/61] 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 14d62e85c8a..c988c0c1720 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 @@ protected void doActionImpl( } } + /** + * 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 54d97fb9052..71f5fdeae10 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.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 Builder waitForPlaybackState(int targetPlaybackState) { 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 41/61] Update release notes --- RELEASENOTES.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 16b65aa09af..b0fc16b86db 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 42/61] Fix release notes typo --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b0fc16b86db..a67de32fea2 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 43/61] 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 97b05e3f0a6..6cf63097962 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 @@ 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.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 @@ * *

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 interface SessionAvailabilityListener { 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 interface SessionAvailabilityListener { 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 boolean getPlayWhenReady() { 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 PlaybackParameters getPlaybackParameters() { return PlaybackParameters.DEFAULT; } - @Override - public void stop() { - stop(/* reset= */ false); - } - @Override public void stop(boolean reset) { playbackState = STATE_IDLE; @@ -486,32 +464,11 @@ public int getCurrentWindowIndex() { 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 long getBufferedPosition() { 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 long getTotalBufferedDuration() { : 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 int getCurrentAdIndexInAdGroup() { 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 0c35c9b66df..b8024d6534d 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 @@ /* 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 @@ public FakePlayer() { listeners = new ArrayList<>(); - window = new Timeline.Window(); period = new Timeline.Period(); state = Player.STATE_IDLE; playWhenReady = true; @@ -151,16 +149,6 @@ public int getCurrentWindowIndex() { 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 00000000000..6ff0853d9b2 --- /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 04bc1c611db..ffdadb78f7b 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.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 @@ 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 @@ public ExoPlayerImpl( 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 @@ public boolean isLoading() { 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 @@ public SeekParameters getSeekParameters() { 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 @@ public int getCurrentWindowIndex() { } } - @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 @@ public long getBufferedPosition() { 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 @@ public int getCurrentAdIndexInAdGroup() { 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 39f1655ab5a..85175568872 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 @@ * 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 boolean isLoading() { 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 SeekParameters getSeekParameters() { 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 int getCurrentWindowIndex() { 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 long getBufferedPosition() { 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 int getCurrentAdIndexInAdGroup() { 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 f1349c11589..156b573df8b 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 @@ * 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 boolean isLoading() { 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 SeekParameters getSeekParameters() { 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 int getCurrentWindowIndex() { 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 long getBufferedPosition() { 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 int getCurrentAdIndexInAdGroup() { 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 44/61] 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 a67de32fea2..194a49db885 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 6cf63097962..1573401521c 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 void seekTo(int windowIndex, long positionMs) { } } + @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 ffdadb78f7b..fb27452f89c 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 @@ public void seekTo(int windowIndex, long positionMs) { } } + @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 83014c6b104..16f8aa28783 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 void onTimelineChanged(Timeline timeline, @Nullable Object manifest) { */ 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 85175568872..36fd0868f90 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 void seekTo(int windowIndex, long positionMs) { 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 156b573df8b..d1236a7a2e5 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 void seekTo(int windowIndex, long positionMs) { 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 45/61] 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 a95f1de738e..b3be88b8511 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 @@ private void processCurrentPacket() { 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 46/61] 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 194a49db885..850b0089b8d 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 6db6d6310b5..dd277c722c4 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 f5ad677d779..c4dda9e9574 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 47/61] 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 94fd7ceb138..a5506e2cfbe 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 951e6c95e0a..3d75923553e 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 Pair createExtractor( } 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 @@ private Extractor createExtractorByFileExtension( 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 @@ private static TsExtractor createTsExtractor( // 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 48/61] 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 1573401521c..6cf63097962 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 void seekTo(int windowIndex, long positionMs) { } } - @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 fb27452f89c..ffdadb78f7b 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 @@ public void seekTo(int windowIndex, long positionMs) { } } - @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 36fd0868f90..85175568872 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 void seekTo(int windowIndex, long positionMs) { 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 d1236a7a2e5..156b573df8b 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 void seekTo(int windowIndex, long positionMs) { 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 49/61] 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 4ce34ad41a1..83f5b7dc52c 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 long getSkippedOutputFrameCount() { */ 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 @@ private int getDefaultBufferSize() { 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 50/61] 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 850b0089b8d..3d4c3cd63ed 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 c3f63040916..450cca42b07 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 void peekFully(byte[] target, int offset, int length) 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 a96dfaf2f88..8b263615784 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 void testPeekFully() throws Exception { } } + @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 51/61] 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 3d75923553e..8a403c37592 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 @@ private Extractor createExtractorByFileExtension( 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 52/61] 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 2b6a508aaa0..37967dd527a 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 53/61] 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 6c72dd8d0a1..fac9818d9ec 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 static long msToUs(long timeMs) { } /** - * 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 54/61] 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 3d4c3cd63ed..18d42c0c9b2 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 624e698ad63..7fdce350988 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.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 @@ protected void configureCodec( 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 @@ protected void onQueueInputBuffer(DecoderInputBuffer buffer) { 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 @@ protected boolean processOutputBuffer( 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 @@ private static boolean codecNeedsDiscardChannelsWorkaround(String codecName) { || 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 55/61] 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 56/61] 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 18d42c0c9b2..fef5e3ddb7a 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 ca4e0c299ef..4740e5d6e7f 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 float getVolumeMultiplier() { */ 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 1cf812559c0..b175828d442 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 void setAudioAttributes_withNullUsage_doesNotManageAudioFocus() { 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 void handleStop_withoutHandlingAudioFocus_isNoOp() { 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 57/61] 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 5f4864d7838..f0e824afed3 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 void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatTog 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 58/61] 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 fef5e3ddb7a..9eae825c61f 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 6671add7e5a..d55f8e04f0a 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 TimelineQueueNavigator(MediaSessionCompat mediaSession, int maxQueueSize) this.mediaSession = mediaSession; this.maxQueueSize = maxQueueSize; activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; + window = new Timeline.Window(); } /** @@ -81,25 +83,24 @@ public TimelineQueueNavigator(MediaSessionCompat mediaSession, int maxQueueSize) @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; + } + if (window.isSeekable || !window.isDynamic || player.hasPrevious()) { + actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; } - return actions | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + if (window.isDynamic || player.hasNext()) { + actions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; + } + return actions; } @Override @@ -125,22 +126,25 @@ public final long getActiveQueueItemId(@Nullable Player player) { @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 void onSkipToQueueItem(Player player, long id) { @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 5f80725805b..78e37c1869d 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 ClippingTimeline(Timeline timeline, long startUs, long endUs) 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 f0e824afed3..8ab42104658 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 @@ private void updateNavigation() { 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 @@ private void setButtonEnabled(boolean enabled, View view) { private void previous() { Timeline timeline = player.getCurrentTimeline(); - if (timeline.isEmpty()) { + if (timeline.isEmpty() || player.isPlayingAd()) { return; } int windowIndex = player.getCurrentWindowIndex(); @@ -848,14 +847,14 @@ private void previous() { 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 59/61] 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 6ff0853d9b2..f1b54153a11 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 final int getNextWindowIndex() { return timeline.isEmpty() ? C.INDEX_UNSET : timeline.getNextWindowIndex( - getCurrentWindowIndex(), getRepeatMode(), getShuffleModeEnabled()); + getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled()); } @Override @@ -88,7 +88,7 @@ public final int getPreviousWindowIndex() { return timeline.isEmpty() ? C.INDEX_UNSET : timeline.getPreviousWindowIndex( - getCurrentWindowIndex(), getRepeatMode(), getShuffleModeEnabled()); + getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled()); } @Override @@ -129,4 +129,10 @@ public final long getContentDuration() { ? 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 60/61] 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 f1b54153a11..eb3bd4f91a6 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 final boolean hasNext() { @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 61/61] 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 4740e5d6e7f..7146426a4a4 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 float getVolumeMultiplier() { } } - 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 float getVolumeMultiplier() { 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 void handleStop() { // 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 b175828d442..086c4ebc7fc 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 void setAudioAttributes_withNullUsage_doesNotManageAudioFocus() { 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 void handlePrepare_afterSetAudioAttributes_setsPlayerCommandPlayWhenReady 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);